psemek/libs/ui/source/scroller.cpp

368 lines
7.5 KiB
C++

#include <psemek/ui/scroller.hpp>
#include <psemek/geom/contains.hpp>
namespace psemek::ui
{
void scroller::set_preferred_direction(direction d)
{
preferred_direction_ = d;
}
bool scroller::set_horizontal_scroll(bool enabled)
{
std::swap(horizontal_, enabled);
return enabled;
}
bool scroller::set_vertical_scroll(bool enabled)
{
std::swap(vertical_, enabled);
return enabled;
}
bool scroller::on_event(mouse_move const & e)
{
mouse_ = e.position;
auto pos = geom::cast<float>(e.position);
if (horizontal_scroll())
{
auto box = horizontal_box();
bool over = geom::contains(box, pos);
switch (horizontal_state_)
{
case state_t::normal:
if (over)
horizontal_state_ = state_t::mouseover;
break;
case state_t::mouseover:
if (!over)
horizontal_state_ = state_t::normal;
break;
case state_t::mousedown:
{
if (!child_)
return false;
auto sc = child_->size_constraints();
float c = geom::unlerp<float>(box[0], e.position[0]);
c = geom::clamp(c, {0.f, 1.f});
auto old = shift_tgt_;
shift_tgt_[0] = shape_.box[0].length() / 2.f - c * sc[0].min;
clamp_shift();
on_scroll(shift_tgt_ - old);
}
break;
}
}
if (vertical_scroll())
{
auto box = vertical_box();
bool over = geom::contains(box, pos);
switch (vertical_state_)
{
case state_t::normal:
if (over)
vertical_state_ = state_t::mouseover;
break;
case state_t::mouseover:
if (!over)
vertical_state_ = state_t::normal;
break;
case state_t::mousedown:
{
if (!child_)
return false;
auto sc = child_->size_constraints();
float c = geom::unlerp<float>(box[1], e.position[1]);
c = geom::clamp(c, {0.f, 1.f});
auto old = shift_tgt_;
shift_tgt_[1] = shape_.box[1].length() / 2.f - c * sc[1].min;
clamp_shift();
on_scroll(shift_tgt_ - old);
}
break;
}
}
return false;
}
bool scroller::on_event(mouse_click const & e)
{
if (e.button != mouse_button::left)
return false;
bool result = false;
if (horizontal_scroll())
{
switch (horizontal_state_)
{
case state_t::normal:
break;
case state_t::mouseover:
if (e.down)
{
horizontal_state_ = state_t::mousedown;
result = true;
}
break;
case state_t::mousedown:
if (!e.down)
{
horizontal_state_ = state_t::mouseover;
result = true;
}
break;
}
}
if (vertical_scroll())
{
switch (vertical_state_)
{
case state_t::normal:
break;
case state_t::mouseover:
if (e.down)
{
vertical_state_ = state_t::mousedown;
result = true;
}
break;
case state_t::mousedown:
if (!e.down)
{
vertical_state_ = state_t::mouseover;
result = true;
}
break;
}
}
return result;
}
bool scroller::on_event(mouse_wheel const & e)
{
if (mouse_ && geom::contains(shape_.box, geom::cast<float>(*mouse_)))
{
geom::vector<float, 2> delta = {0.f, 0.f};
auto pos = geom::cast<float>(*mouse_);
auto vcontains = geom::contains(vertical_box(), pos);
auto hcontains = geom::contains(horizontal_box(), pos);
if (vertical_scroll() && horizontal_scroll())
{
if (vcontains)
delta[1] = e.delta;
else if (hcontains)
delta[0] = e.delta;
else if (preferred_direction() == direction::vertical)
delta[1] = e.delta;
else
delta[0] = e.delta;
}
else if (vertical_scroll())
{
delta[1] = e.delta;
}
else if (horizontal_scroll())
{
delta[0] = e.delta;
}
auto old = shift_tgt_;
shift_tgt_ += delta * 50.f;
clamp_shift();
on_scroll(shift_tgt_ - old);
post_reshape();
return true;
}
return false;
}
void scroller::reshape(geom::box<float, 2> const & bbox)
{
shape_.box = bbox;
auto st = merged_own_style();
if (child_)
{
auto child_constraints = child_->size_constraints();
auto child_bbox = bbox;
if (horizontal_scroll())
child_bbox[1].max -= width() * *st->scale;
if (vertical_scroll())
child_bbox[0].max -= width() * *st->scale;
if (horizontal_scroll())
{
horizontal_stick_ |= child_bbox[0].length() > child_constraints[0].min;
if (horizontal_stick_)
{
shift_tgt_[0] = std::min(child_bbox[0].length() - child_constraints[0].min, 0.f);
shift_[0] = shift_tgt_[0];
}
child_bbox[0].min += shift_[0];
child_bbox[0].max = child_bbox[0].min + child_constraints[0].min;
}
if (vertical_scroll())
{
vertical_stick_ |= child_bbox[1].length() > child_constraints[1].min;
if (vertical_stick_)
{
shift_tgt_[1] = std::min(child_bbox[1].length() - child_constraints[1].min, 0.f);
shift_[1] = shift_tgt_[1];
}
child_bbox[1].min += shift_[1];
child_bbox[1].max = child_bbox[1].min + child_constraints[1].min;
}
child_->reshape(child_bbox);
}
}
geom::box<float, 2> scroller::size_constraints() const
{
geom::box<float, 2> result;
result[0] = {0.f, std::numeric_limits<float>::infinity()};
result[1] = {0.f, std::numeric_limits<float>::infinity()};
if (child_)
{
auto child_constraints = child_->size_constraints();
if (!horizontal_scroll())
result[0] = child_constraints[0];
if (!vertical_scroll())
result[1] = child_constraints[1];
}
auto st = merged_own_style();
if (horizontal_scroll())
result[1].min = std::max<float>(result[1].min, width() * *st->scale);
if (vertical_scroll())
result[0].min = std::max<float>(result[0].min, width() * *st->scale);
return result;
}
void scroller::update(float dt)
{
shift_ += (shift_tgt_ - shift_) * std::min(25.f * dt, 1.f);
post_reshape();
}
void scroller::draw(painter & p) const
{
auto st = merged_own_style();
float w = width() * *st->scale;
auto box = shape_.box;
if (horizontal_scroll())
box[1].max -= w;
if (vertical_scroll())
box[0].max -= w;
p.begin_stencil();
p.draw_rect(box, {0, 0, 0, 255});
p.commit_stencil();
}
void scroller::post_draw(painter & p) const
{
p.end_stencil();
}
void scroller::on_scroll(geom::vector<float, 2> const &)
{}
float scroller::width() const
{
return 0;
}
geom::box<float, 2> scroller::visible_range() const
{
if (!child_)
return {{{0.f, 1.f}, {0.f, 1.f}}};
geom::box<float, 2> result;
auto c = child_->size_constraints();
result[0] = {- shift_[0] / c[0].min, (shape_.box[0].length() - shift_[0]) / c[0].min};
result[1] = {- shift_[1] / c[1].min, (shape_.box[1].length() - shift_[1]) / c[1].min};
result[0].max = std::min(result[0].max, 1.f);
result[1].max = std::min(result[1].max, 1.f);
return result;
}
geom::box<float, 2> scroller::horizontal_box() const
{
auto box = shape_.box;
float w = width() * *merged_own_style()->scale;
box[1].min = box[1].max - w;
if (vertical_scroll())
box[0].max -= w;
return box;
}
geom::box<float, 2> scroller::vertical_box() const
{
auto box = shape_.box;
float w = width() * *merged_own_style()->scale;
box[0].min = box[0].max - w;
if (horizontal_scroll())
box[1].max -= w;
return box;
}
void scroller::clamp_shift()
{
if (!child_)
return;
auto c = child_->size_constraints();
geom::vector<float, 2> min;
min[0] = shape_.box[0].length() - c[0].min;
min[1] = shape_.box[1].length() - c[1].min;
shift_tgt_[0] = std::max(shift_tgt_[0], min[0]);
horizontal_stick_ = (shift_tgt_[0] == min[0]) || (min[0] > 0.f);
shift_tgt_[0] = std::min(shift_tgt_[0], 0.f);
shift_tgt_[1] = std::max(shift_tgt_[1], min[1]);
vertical_stick_ = (shift_tgt_[1] == min[1]) || (min[1] > 0.f);
shift_tgt_[1] = std::min(shift_tgt_[1], 0.f);
}
}