452 lines
9.5 KiB
C++
452 lines
9.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;
|
|
}
|
|
|
|
geom::box<float, 2> scroller::events_bbox() const
|
|
{
|
|
auto st = merged_own_style();
|
|
auto result = shape_.box;
|
|
|
|
if (horizontal_scroll())
|
|
result[1].max -= width() * *st->scale;
|
|
if (vertical_scroll())
|
|
result[0].max -= width() * *st->scale;
|
|
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
|
|
float c = geom::unlerp<float>(box[0], e.position[0]);
|
|
set_position(direction::horizontal, geom::clamp(c, {0.f, 1.f}), true);
|
|
}
|
|
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;
|
|
|
|
float c = geom::unlerp<float>(box[1], e.position[1]);
|
|
c = geom::clamp(c, {0.f, 1.f});
|
|
set_position(direction::vertical, geom::clamp(c, {0.f, 1.f}), true);
|
|
}
|
|
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 (width_first())
|
|
{
|
|
child_constraints[1] = child_->height_constraints(child_bbox[0].length());
|
|
child_bbox[1].max = child_bbox[1].min + child_constraints[1].min;
|
|
}
|
|
else
|
|
{
|
|
child_constraints[0] = child_->width_constraints(child_bbox[1].length());
|
|
child_bbox[0].max = child_bbox[0].min + child_constraints[0].min;
|
|
}
|
|
|
|
{
|
|
auto old_shift = shift_tgt_;
|
|
clamp_shift();
|
|
if (shift_tgt_[0] != old_shift[0])
|
|
shift_[0] = shift_tgt_[0];
|
|
if (shift_tgt_[1] != old_shift[1])
|
|
shift_[1] = shift_tgt_[1];
|
|
}
|
|
|
|
if (horizontal_scroll())
|
|
child_bbox[0] += shift_[0];
|
|
|
|
if (vertical_scroll())
|
|
child_bbox[1] += shift_[1];
|
|
|
|
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] += width() * *st->scale;
|
|
if (vertical_scroll())
|
|
result[0] += width() * *st->scale;
|
|
|
|
return result;
|
|
}
|
|
|
|
geom::interval<float> scroller::width_constraints(float height) const
|
|
{
|
|
geom::interval<float> result = {0.f, std::numeric_limits<float>::infinity()};
|
|
|
|
auto st = merged_own_style();
|
|
|
|
if (child_ && !horizontal_scroll())
|
|
result = child_->width_constraints(height - width() * *st->scale);
|
|
|
|
if (vertical_scroll())
|
|
result += width() * *st->scale;
|
|
|
|
return result;
|
|
}
|
|
|
|
geom::interval<float> scroller::height_constraints(float width) const
|
|
{
|
|
geom::interval<float> result = {0.f, std::numeric_limits<float>::infinity()};
|
|
|
|
auto st = merged_own_style();
|
|
|
|
if (child_ && !horizontal_scroll())
|
|
result = child_->height_constraints(width - this->width() * *st->scale);
|
|
|
|
if (horizontal_scroll())
|
|
result += this->width() * *st->scale;
|
|
|
|
return result;
|
|
}
|
|
|
|
float scroller::position(direction dir) const
|
|
{
|
|
if (!child_) return 0.f;
|
|
|
|
auto child_box = child_->shape().bbox();
|
|
auto st = merged_own_style();
|
|
auto child_area = shape_.box.dimensions();
|
|
|
|
if (horizontal_scroll())
|
|
child_area[1] -= width() * *st->scale;
|
|
if (vertical_scroll())
|
|
child_area[0] -= width() * *st->scale;
|
|
|
|
if (dir == direction::horizontal)
|
|
return -shift_[0] / (child_box[0].length() - child_area[0]);
|
|
else
|
|
return -shift_[1] / (child_box[1].length() - child_area[1]);
|
|
}
|
|
|
|
void scroller::set_position(direction dir, float position, bool animate)
|
|
{
|
|
if (dir == direction::horizontal && !horizontal_scroll())
|
|
return;
|
|
if (dir == direction::vertical && !vertical_scroll())
|
|
return;
|
|
|
|
auto old_shift = shift_tgt_;
|
|
|
|
int dim = (dir == direction::horizontal) ? 0 : 1;
|
|
|
|
auto child_box = child_->shape().bbox();
|
|
auto st = merged_own_style();
|
|
auto child_area = shape_.box.dimensions();
|
|
|
|
if (horizontal_scroll())
|
|
child_area[1] -= width() * *st->scale;
|
|
if (vertical_scroll())
|
|
child_area[0] -= width() * *st->scale;
|
|
|
|
shift_tgt_[dim] = position * (child_area[dim] - child_box[dim].length());
|
|
clamp_shift();
|
|
|
|
if (!animate)
|
|
shift_[dim] = shift_tgt_[dim];
|
|
|
|
on_scroll(shift_tgt_ - old_shift);
|
|
}
|
|
|
|
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;
|
|
|
|
geom::vector<float, 2> min = child_->shape().bbox().dimensions();
|
|
|
|
auto st = merged_own_style();
|
|
auto child_area = shape_.box.dimensions();
|
|
|
|
if (horizontal_scroll())
|
|
child_area[1] -= width() * *st->scale;
|
|
if (vertical_scroll())
|
|
child_area[0] -= width() * *st->scale;
|
|
|
|
min[0] = child_area[0] - min[0];
|
|
min[1] = child_area[1] - min[1];
|
|
|
|
// Can't use clamp since the order of min & max matters
|
|
shift_tgt_[0] = std::max(shift_tgt_[0], min[0]);
|
|
shift_tgt_[0] = std::min(shift_tgt_[0], 0.f);
|
|
|
|
shift_tgt_[1] = std::max(shift_tgt_[1], min[1]);
|
|
shift_tgt_[1] = std::min(shift_tgt_[1], 0.f);
|
|
}
|
|
|
|
}
|