diff --git a/libs/ui/include/psemek/ui/checkbox.hpp b/libs/ui/include/psemek/ui/checkbox.hpp new file mode 100644 index 00000000..bbacc450 --- /dev/null +++ b/libs/ui/include/psemek/ui/checkbox.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include + +namespace psemek::ui +{ + + struct checkbox + : element + { + bool on_event(mouse_move const & e) override; + bool on_event(mouse_click const & e) override; + + bool value() const { return value_; } + void set_value(bool value) { value_ = value; } + + using on_value_changed_callback = std::function; + + void on_value_changed(on_value_changed_callback callback) + { + callback_ = std::move(callback); + } + + protected: + enum class state_t + { + normal, + mouseover, + mousedown, + }; + + virtual state_t state() const { return state_; } + + virtual void on_state_changed(state_t old); + + private: + state_t state_ = state_t::normal; + bool value_ = false; + + on_value_changed_callback callback_; + }; + +} diff --git a/libs/ui/include/psemek/ui/default_element_factory.hpp b/libs/ui/include/psemek/ui/default_element_factory.hpp index 2a2cc60d..794202de 100644 --- a/libs/ui/include/psemek/ui/default_element_factory.hpp +++ b/libs/ui/include/psemek/ui/default_element_factory.hpp @@ -17,6 +17,7 @@ namespace psemek::ui using element_factory::make_button; std::shared_ptr make_frame() override; std::shared_ptr make_window(std::string caption) override; + std::shared_ptr make_checkbox(bool value) override; std::shared_ptr make_slider() override; std::shared_ptr make_spinbox() override; diff --git a/libs/ui/include/psemek/ui/element_factory.hpp b/libs/ui/include/psemek/ui/element_factory.hpp index fa37fad0..fa248b4b 100644 --- a/libs/ui/include/psemek/ui/element_factory.hpp +++ b/libs/ui/include/psemek/ui/element_factory.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ namespace psemek::ui virtual std::shared_ptr make_grid_layout(); virtual std::shared_ptr make_image_view(std::shared_ptr image); virtual std::shared_ptr make_rich_image_view(std::shared_ptr image); + virtual std::shared_ptr make_checkbox(bool value); virtual std::shared_ptr make_slider(); virtual std::shared_ptr make_spinbox(); diff --git a/libs/ui/source/checkbox.cpp b/libs/ui/source/checkbox.cpp new file mode 100644 index 00000000..cfe01b72 --- /dev/null +++ b/libs/ui/source/checkbox.cpp @@ -0,0 +1,71 @@ +#include + +namespace psemek::ui +{ + + bool checkbox::on_event(mouse_move const & e) + { + bool const over = shape().contains(geom::cast(e.position)); + + switch (state_) { + case state_t::normal: + if (over) + { + state_ = state_t::mouseover; + on_state_changed(state_t::normal); + } + break; + case state_t::mouseover: + case state_t::mousedown: + if (!over) + { + auto old = state_; + state_ = state_t::normal; + on_state_changed(old); + } + break; + } + + return false; + } + + bool checkbox::on_event(mouse_click const & e) + { + if (e.button != mouse_button::left) return false; + + switch (state_) { + case state_t::normal: + break; + case state_t::mouseover: + if (e.down) + { + state_ = state_t::mousedown; + value_ = !value_; + if (callback_) + post([cb = callback_, v = value_]{ cb(v); }); + on_state_changed(state_t::mouseover); + return true; + } + break; + case state_t::mousedown: + if (!e.down) + { + state_ = state_t::mouseover; + on_state_changed(state_t::mousedown); + return true; + } + break; + } + + return false; + } + + void checkbox::on_state_changed(state_t old) + { + if (state() == state_t::mousedown || old == state_t::mousedown) + { + post_reshape(); + } + } + +} diff --git a/libs/ui/source/default_element_factory.cpp b/libs/ui/source/default_element_factory.cpp index 9dc2efdc..895ad890 100644 --- a/libs/ui/source/default_element_factory.cpp +++ b/libs/ui/source/default_element_factory.cpp @@ -360,6 +360,84 @@ namespace psemek::ui element * children_[3]; }; + struct checkbox_impl + : checkbox + { + struct shape const & shape() const override + { + return shape_; + } + + void reshape(geom::box const & bbox) override + { + shape_.box = geom::expand(geom::box::singleton(bbox.center()), size() / 2.f); + } + + void draw(painter & p) const override + { + auto st = merged_style(); + if (!st) return; + + geom::box boxes[5]; + + boxes[0] = {{shape_.box[0], {shape_.box[1].min, shape_.box[1].min + outer_width()}}}; + boxes[1] = {{shape_.box[0], {shape_.box[1].max - outer_width(), shape_.box[1].max}}}; + boxes[2] = {{{shape_.box[0].min, shape_.box[0].min + outer_width()}, {shape_.box[1].min + outer_width(), shape_.box[1].max - outer_width()}}}; + boxes[3] = {{{shape_.box[0].max - outer_width(), shape_.box[0].max}, {shape_.box[1].min + outer_width(), shape_.box[1].max - outer_width()}}}; + boxes[4] = geom::shrink(shape_.box, 0.f + outer_width() + outer_margin()); + + int const box_count = value() ? 5 : 4; + + if (st->shadow_offset != geom::vector{0, 0}) + { + auto const offset = geom::cast(*st->shadow_offset); + for (int b = 0; b < box_count; ++b) + p.draw_rect(boxes[b] + offset, *st->shadow_color); + } + + auto offset = geom::vector{0.f, 0.f}; + if (state() == state_t::mousedown) + offset = geom::cast(*st->action_offset); + + gfx::color_rgba color = *st->fg_color; + if (state() == state_t::mouseover) + color = *st->highlight_color; + else if (state() == state_t::mousedown) + color = *st->action_color; + + for (int b = 0; b < box_count; ++b) + p.draw_rect(boxes[b] + offset, color); + } + + geom::box size_constraints() const override + { + auto sc = element::size_constraints(); + + sc[0] &= geom::interval{size(), std::numeric_limits::infinity()}; + sc[1] &= geom::interval{size(), std::numeric_limits::infinity()}; + + return sc; + } + + private: + box_shape shape_; + + int size() const + { + return *merged_style()->ref_height; + } + + int outer_width() const + { + return *merged_style()->ref_height / 8; + } + + int outer_margin() const + { + return *merged_style()->ref_height / 8; + } + }; + struct slider_impl : slider { @@ -627,6 +705,13 @@ namespace psemek::ui return std::make_shared(); } + std::shared_ptr default_element_factory::make_checkbox(bool value) + { + auto c = std::make_shared(); + c->set_value(value); + return c; + } + std::shared_ptr default_element_factory::make_slider() { return std::make_shared(); diff --git a/libs/ui/source/element_factory.cpp b/libs/ui/source/element_factory.cpp index d332912f..1e1d50ff 100644 --- a/libs/ui/source/element_factory.cpp +++ b/libs/ui/source/element_factory.cpp @@ -73,6 +73,11 @@ namespace psemek::ui return i; } + std::shared_ptr element_factory::make_checkbox(bool) + { + return nullptr; + } + std::shared_ptr element_factory::make_slider() { return nullptr; } std::shared_ptr element_factory::make_spinbox() { return nullptr; }