165 lines
5.1 KiB
C++
165 lines
5.1 KiB
C++
#pragma once
|
|
#include "../base/Color.h"
|
|
#include "../base/Geometry.h"
|
|
#include "../base/Style.h"
|
|
#include "Canvas.h"
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <utility>
|
|
namespace renderive {
|
|
enum class Button_State : std::uint8_t {
|
|
Normal,
|
|
Hovered,
|
|
Pressed
|
|
};
|
|
struct Button_Style {
|
|
Color background{48, 48, 48};
|
|
Color hover_background{68, 68, 68};
|
|
Color pressed_background{32, 32, 32};
|
|
Color border{96, 96, 96};
|
|
Color hover_border{64, 142, 255};
|
|
Color pressed_border{32, 104, 220};
|
|
Color text{235, 235, 235};
|
|
Color hover_text{255, 255, 255};
|
|
Color pressed_text{255, 255, 255};
|
|
double horizontal_padding{8.0};
|
|
double vertical_padding{2.0};
|
|
double minimum_width{48.0};
|
|
double minimum_height{20.0};
|
|
};
|
|
struct Button_Event_Result {
|
|
bool consumed{};
|
|
bool changed{};
|
|
bool clicked{};
|
|
};
|
|
class Button {
|
|
public:
|
|
Button() = default;
|
|
explicit Button(std::string text)
|
|
: text_(std::move(text)) {}
|
|
void set_text(std::string text) {
|
|
text_ = std::move(text);
|
|
}
|
|
const std::string& text() const {
|
|
return text_;
|
|
}
|
|
void set_style(Button_Style style) {
|
|
style_ = std::move(style);
|
|
}
|
|
const Button_Style& style() const {
|
|
return style_;
|
|
}
|
|
void set_rect(RectF rect) {
|
|
rect_ = rect;
|
|
}
|
|
const RectF& rect() const {
|
|
return rect_;
|
|
}
|
|
void set_state(Button_State state) {
|
|
state_ = state;
|
|
}
|
|
Button_State state() const {
|
|
return state_;
|
|
}
|
|
void measure(const Canvas& canvas) {
|
|
Text_Metrics metrics = canvas.measure_text(text_);
|
|
preferred_width_ = std::max(style_.minimum_width, metrics.width + style_.horizontal_padding * 2.0);
|
|
preferred_height_ = std::max(style_.minimum_height, metrics.line_height() + style_.vertical_padding * 2.0);
|
|
}
|
|
double preferred_width() const {
|
|
return preferred_width_;
|
|
}
|
|
double preferred_height() const {
|
|
return preferred_height_;
|
|
}
|
|
bool contains(PointF position) const {
|
|
return !rect_.empty() && rect_.contains(position);
|
|
}
|
|
void draw(Canvas& canvas) const {
|
|
if (rect_.empty())
|
|
return;
|
|
canvas.set_brush(Brush{background_color(), Brush_Style::Solid});
|
|
canvas.fill_rect(rect_);
|
|
canvas.set_pen(Pen{border_color()});
|
|
canvas.draw_rect(rect_);
|
|
canvas.set_pen(Pen{text_color()});
|
|
canvas.draw_text(rect_, Text_Align::Center, text_);
|
|
}
|
|
private:
|
|
Color background_color() const {
|
|
if (state_ == Button_State::Pressed)
|
|
return style_.pressed_background;
|
|
if (state_ == Button_State::Hovered)
|
|
return style_.hover_background;
|
|
return style_.background;
|
|
}
|
|
Color border_color() const {
|
|
if (state_ == Button_State::Pressed)
|
|
return style_.pressed_border;
|
|
if (state_ == Button_State::Hovered)
|
|
return style_.hover_border;
|
|
return style_.border;
|
|
}
|
|
Color text_color() const {
|
|
if (state_ == Button_State::Pressed)
|
|
return style_.pressed_text;
|
|
if (state_ == Button_State::Hovered)
|
|
return style_.hover_text;
|
|
return style_.text;
|
|
}
|
|
std::string text_;
|
|
Button_Style style_;
|
|
RectF rect_;
|
|
Button_State state_{Button_State::Normal};
|
|
double preferred_width_{};
|
|
double preferred_height_{};
|
|
};
|
|
class Button_Interaction {
|
|
public:
|
|
Button_Event_Result pointer_move(PointF position, const RectF& rect) {
|
|
bool inside = !rect.empty() && rect.contains(position);
|
|
Button_State next = pressed_ && inside ? Button_State::Pressed : inside ? Button_State::Hovered : Button_State::Normal;
|
|
bool changed = set_state(next);
|
|
return {inside || pressed_ || changed, changed, false};
|
|
}
|
|
Button_Event_Result pointer_press(PointF position, const RectF& rect) {
|
|
if (rect.empty() || !rect.contains(position))
|
|
return {};
|
|
pressed_ = true;
|
|
bool changed = set_state(Button_State::Pressed);
|
|
return {true, changed, false};
|
|
}
|
|
Button_Event_Result pointer_release(PointF position, const RectF& rect) {
|
|
bool inside = !rect.empty() && rect.contains(position);
|
|
if (!pressed_) {
|
|
bool changed = set_state(inside ? Button_State::Hovered : Button_State::Normal);
|
|
return {false, changed, false};
|
|
}
|
|
pressed_ = false;
|
|
bool changed = set_state(inside ? Button_State::Hovered : Button_State::Normal);
|
|
return {true, changed, inside};
|
|
}
|
|
Button_Event_Result pointer_leave() {
|
|
bool changed = set_state(Button_State::Normal);
|
|
return {pressed_ || changed, changed, false};
|
|
}
|
|
bool reset() {
|
|
pressed_ = false;
|
|
return set_state(Button_State::Normal);
|
|
}
|
|
Button_State state() const {
|
|
return state_;
|
|
}
|
|
private:
|
|
bool set_state(Button_State state) {
|
|
if (state_ == state)
|
|
return false;
|
|
state_ = state;
|
|
return true;
|
|
}
|
|
bool pressed_{};
|
|
Button_State state_{Button_State::Normal};
|
|
};
|
|
} // namespace renderive
|