Add ui slider element

This commit is contained in:
Nikita Lisitsa 2021-03-05 15:51:17 +03:00
parent 0bf4324ba4
commit e67b3f1fdc
9 changed files with 268 additions and 1 deletions

View file

@ -25,6 +25,7 @@ namespace psemek::ui
std::shared_ptr<button> make_button() override;
std::shared_ptr<frame> make_frame() override;
std::shared_ptr<window> make_window(std::string caption) override;
std::shared_ptr<slider> make_slider() override;
private:
psemek_declare_pimpl

View file

@ -8,6 +8,7 @@
#include <psemek/ui/grid_layout.hpp>
#include <psemek/ui/image_view.hpp>
#include <psemek/ui/rich_image_view.hpp>
#include <psemek/ui/slider.hpp>
#include <psemek/gfx/texture.hpp>
@ -26,6 +27,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<slider> make_slider();
virtual ~element_factory() {}
};

View file

@ -0,0 +1,55 @@
#pragma once
#include <psemek/ui/element.hpp>
#include <functional>
namespace psemek::ui
{
struct slider
: element
{
bool on_event(mouse_move const & e) override;
bool on_event(mouse_click const & e) override;
bool on_event(mouse_wheel const & e) override;
virtual geom::interval<int> value_range() const { return value_range_; }
virtual void set_value_range(geom::interval<int> i);
virtual int value() const { return value_; }
virtual void set_value(int v);
using on_value_changed_callback = std::function<void(int)>;
void on_value_changed(on_value_changed_callback callback);
protected:
enum class state_t
{
normal,
mouseover,
mousedown,
};
virtual state_t state() const { return state_; }
virtual void on_state_changed(state_t /* old */) {}
virtual geom::interval<float> slider_range() const;
private:
geom::interval<int> value_range_{0, 100};
int value_ = 0;
state_t state_ = state_t::normal;
std::optional<int> mouse_x_;
on_value_changed_callback callback_;
void post_value_changed();
int compute_value(int x) const;
};
}

View file

@ -20,6 +20,8 @@ namespace psemek::ui
std::optional<gfx::color_rgba> border_color;
std::optional<int> border_width;
std::optional<gfx::color_rgba> axis_color;
std::optional<geom::vector<int, 2>> shadow_offset;
std::optional<gfx::color_rgba> shadow_color;

View file

@ -310,6 +310,86 @@ namespace psemek::ui
element * children_[3];
};
struct slider_impl
: slider
{
struct shape const & shape() const override { return shape_; }
void reshape(geom::box<float, 2> const & bbox)
{
shape_.box = bbox;
}
geom::box<float, 2> size_constraints() const
{
static float const inf = std::numeric_limits<float>::infinity();
return {{{button_width(), inf}, {button_height(), inf}}};
}
void draw(painter & p) const
{
auto st = merged_style();
geom::box<float, 2> ab;
ab[0] = shape_.box[0];
ab[1] = geom::expand(geom::interval<float>::singleton(shape_.box[1].center()), axis_width() / 2.f);
gfx::color_rgba c = *st->fg_color;
if (state() == state_t::mouseover)
c = *st->highlight_color;
else if (state() == state_t::mousedown)
c = *st->action_color;
auto const r = slider_range();
float x = geom::lerp(r, geom::unlerp<float>(geom::cast<float>(value_range()), value()));
geom::box<float, 2> sb;
sb[0] = geom::expand(geom::interval<float>::singleton(x), button_width() / 2.f);
sb[1] = geom::expand(geom::interval<float>::singleton(shape_.box[1].center()), button_height() / 2.f);
if (*st->shadow_offset != geom::vector{0, 0})
{
auto off = geom::cast<float>(*st->shadow_offset);
p.draw_rect(ab + off, *st->shadow_color);
p.draw_rect(sb + off, *st->shadow_color);
}
p.draw_rect(ab, *st->axis_color);
if (*st->border_width != 0)
p.draw_rect(sb, *st->border_color);
p.draw_rect(geom::shrink(sb, 1.f * (*st->border_width)), c);
}
protected:
geom::interval<float> slider_range() const override
{
return geom::shrink(shape_.box[0], button_width() / 2.f);
}
private:
box_shape shape_;
int button_width() const
{
auto st = merged_style();
return *st->outer_margin;
}
int button_height() const
{
auto st = merged_style();
return 3 * (*st->outer_margin);
}
int axis_width() const
{
auto st = merged_style();
return *st->inner_margin;
}
};
}
struct default_element_factory::impl
@ -350,4 +430,9 @@ namespace psemek::ui
return r;
}
std::shared_ptr<slider> default_element_factory::make_slider()
{
return std::make_shared<slider_impl>();
}
}

View file

@ -73,4 +73,6 @@ namespace psemek::ui
return i;
}
std::shared_ptr<slider> element_factory::make_slider() { return nullptr; }
}

118
libs/ui/source/slider.cpp Normal file
View file

@ -0,0 +1,118 @@
#include <psemek/ui/slider.hpp>
namespace psemek::ui
{
bool slider::on_event(mouse_move const & e)
{
bool const over = shape().contains(geom::cast<float>(e.position));
mouse_x_ = e.position[0];
switch (state_) {
case state_t::normal:
if (over)
{
state_ = state_t::mouseover;
on_state_changed(state_t::normal);
}
break;
case state_t::mouseover:
if (!over)
{
state_ = state_t::normal;
on_state_changed(state_t::mouseover);
}
break;
case state_t::mousedown:
if (mouse_x_)
set_value(compute_value(*mouse_x_));
break;
}
return false;
}
bool slider::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;
if (mouse_x_)
set_value(compute_value(*mouse_x_));
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;
}
bool slider::on_event(mouse_wheel const & e)
{
if (state_ == state_t::mouseover)
{
set_value(value_ + e.delta);
return true;
}
return false;
}
void slider::set_value_range(geom::interval<int> i)
{
if (i.empty()) throw std::runtime_error("Empty value range for ui::slider");
value_range_ = i;
set_value(value_);
}
void slider::set_value(int v)
{
v = geom::clamp(v, value_range_);
if (value_ != v)
{
value_ = v;
post_value_changed();
}
}
void slider::on_value_changed(on_value_changed_callback callback)
{
callback_ = std::move(callback);
post_value_changed();
}
geom::interval<float> slider::slider_range() const
{
return shape().bbox()[0];
}
void slider::post_value_changed()
{
if (callback_)
post([cb = callback_, value = value_]{
cb(value);
});
}
int slider::compute_value(int x) const
{
auto const range = slider_range();
return std::round(geom::unlerp(range, x * 1.f) * value_range_.length()) + value_range_.min;
}
}

View file

@ -24,6 +24,7 @@ namespace psemek::ui
merge(dst.action_offset, src.action_offset);
merge(dst.border_color, src.border_color);
merge(dst.border_width, src.border_width);
merge(dst.axis_color, src.axis_color);
merge(dst.shadow_offset, src.shadow_offset);
merge(dst.shadow_color, src.shadow_color);
merge(dst.inner_margin, src.inner_margin);
@ -47,6 +48,8 @@ namespace psemek::ui
s.border_color = {255, 255, 255, 255};
s.border_width = 3;
s.axis_color = {255, 255, 255, 255};
s.shadow_offset = {1, 1};
s.shadow_color = {0, 0, 0, 255};

View file

@ -26,4 +26,3 @@
* grid layout quadratic optimization
* draggable window element
* screen children sorting
* slider element