#include #include namespace psemek::ui { std::shared_ptr scroller::set_child(std::shared_ptr c) { auto old = std::move(child_); if (old) old->set_parent(nullptr); child_ = std::move(c); if (child_) child_->set_parent(this); children_[0] = child_.get(); return old; } 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(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(box[0], e.position[0]); c = geom::clamp(c, {0.f, 1.f}); shift_tgt_[0] = shape_.box[0].length() / 2.f - c * sc[0].min; clamp_shift(); } 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(box[1], e.position[1]); c = geom::clamp(c, {0.f, 1.f}); shift_tgt_[1] = shape_.box[1].length() / 2.f - c * sc[1].min; clamp_shift(); } 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(*mouse_))) { geom::vector delta = {0.f, 0.f}; auto pos = geom::cast(*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; } shift_tgt_ += delta * 50.f; clamp_shift(); post_reshape(); return true; } return false; } void scroller::reshape(geom::box 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 scroller::size_constraints() const { geom::box result; result[0] = {0.f, std::numeric_limits::infinity()}; result[1] = {0.f, std::numeric_limits::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(result[1].min, width() * *st->scale); if (vertical_scroll()) result[0].min = std::max(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(); } scroller::~scroller() { release_children(); } float scroller::width() const { return 0; } geom::box scroller::visible_range() const { if (!child_) return {{{0.f, 1.f}, {0.f, 1.f}}}; geom::box 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 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 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 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); } }