Add ui::checkbox interface & default implementation

This commit is contained in:
Nikita Lisitsa 2021-07-17 17:46:17 +03:00
parent 9f550d7a46
commit 6dd5fd5ee7
6 changed files with 209 additions and 0 deletions

View file

@ -0,0 +1,45 @@
#pragma once
#include <psemek/ui/element.hpp>
#include <functional>
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(bool)>;
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_;
};
}

View file

@ -17,6 +17,7 @@ namespace psemek::ui
using element_factory::make_button;
std::shared_ptr<frame> make_frame() override;
std::shared_ptr<window> make_window(std::string caption) override;
std::shared_ptr<checkbox> make_checkbox(bool value) override;
std::shared_ptr<slider> make_slider() override;
std::shared_ptr<spinbox> make_spinbox() override;

View file

@ -9,6 +9,7 @@
#include <psemek/ui/grid_layout.hpp>
#include <psemek/ui/image_view.hpp>
#include <psemek/ui/rich_image_view.hpp>
#include <psemek/ui/checkbox.hpp>
#include <psemek/ui/slider.hpp>
#include <psemek/ui/spinbox.hpp>
@ -29,6 +30,7 @@ namespace psemek::ui
virtual std::shared_ptr<grid_layout> make_grid_layout();
virtual std::shared_ptr<image_view> make_image_view(std::shared_ptr<gfx::texture_2d> image);
virtual std::shared_ptr<rich_image_view> make_rich_image_view(std::shared_ptr<gfx::texture_2d> image);
virtual std::shared_ptr<checkbox> make_checkbox(bool value);
virtual std::shared_ptr<slider> make_slider();
virtual std::shared_ptr<spinbox> make_spinbox();

View file

@ -0,0 +1,71 @@
#include <psemek/ui/checkbox.hpp>
namespace psemek::ui
{
bool checkbox::on_event(mouse_move const & e)
{
bool const over = shape().contains(geom::cast<float>(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();
}
}
}

View file

@ -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<float, 2> const & bbox) override
{
shape_.box = geom::expand(geom::box<float, 2>::singleton(bbox.center()), size() / 2.f);
}
void draw(painter & p) const override
{
auto st = merged_style();
if (!st) return;
geom::box<float, 2> 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<float>(*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<float>(*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<float, 2> size_constraints() const override
{
auto sc = element::size_constraints();
sc[0] &= geom::interval<float>{size(), std::numeric_limits<float>::infinity()};
sc[1] &= geom::interval<float>{size(), std::numeric_limits<float>::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<spinbox_impl>();
}
std::shared_ptr<checkbox> default_element_factory::make_checkbox(bool value)
{
auto c = std::make_shared<checkbox_impl>();
c->set_value(value);
return c;
}
std::shared_ptr<slider> default_element_factory::make_slider()
{
return std::make_shared<slider_impl>();

View file

@ -73,6 +73,11 @@ namespace psemek::ui
return i;
}
std::shared_ptr<checkbox> element_factory::make_checkbox(bool)
{
return nullptr;
}
std::shared_ptr<slider> element_factory::make_slider() { return nullptr; }
std::shared_ptr<spinbox> element_factory::make_spinbox() { return nullptr; }