diff --git a/libs/ui/include/psemek/ui/scroller.hpp b/libs/ui/include/psemek/ui/scroller.hpp index 3d54e7f4..ea820e31 100644 --- a/libs/ui/include/psemek/ui/scroller.hpp +++ b/libs/ui/include/psemek/ui/scroller.hpp @@ -32,6 +32,11 @@ namespace psemek::ui void reshape(geom::box const & bbox) override; geom::box size_constraints() const override; + geom::interval width_constraints(float height) const override; + geom::interval height_constraints(float width) const override; + + virtual float position(direction dir) const; + virtual void set_position(direction dir, float position, bool animate); void update(float dt) override; @@ -67,9 +72,6 @@ namespace psemek::ui bool horizontal_ = false; bool vertical_ = true; - bool horizontal_stick_ = false; - bool vertical_stick_ = false; - geom::vector shift_{0.f, 0.f}; geom::vector shift_tgt_{0.f, 0.f}; diff --git a/libs/ui/source/file_dialog.cpp b/libs/ui/source/file_dialog.cpp index 32862bd2..c4b24b1c 100644 --- a/libs/ui/source/file_dialog.cpp +++ b/libs/ui/source/file_dialog.cpp @@ -268,6 +268,7 @@ namespace psemek::ui contents_str += entry.path.filename().string() + "[/link]\n"; } directory_view->set_tagged_text(std::move(contents_str)); + directory_view_scroller->set_position(scroller::direction::vertical, 0.f, false); directory_view->on_link_click([=, entries = std::move(entries)](std::string_view const & value){ int index = util::from_string(std::string{value}); diff --git a/libs/ui/source/scroller.cpp b/libs/ui/source/scroller.cpp index 8e3d1812..5338c12c 100644 --- a/libs/ui/source/scroller.cpp +++ b/libs/ui/source/scroller.cpp @@ -213,31 +213,22 @@ namespace psemek::ui if (vertical_scroll()) child_bbox[0].max -= width() * *st->scale; - if (horizontal_scroll()) + if (width_first()) { - 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_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; } - if (vertical_scroll()) - { - vertical_stick_ |= child_bbox[1].length() > child_constraints[1].min; + if (horizontal_scroll()) + child_bbox[0] += shift_[0]; - 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; - } + if (vertical_scroll()) + child_bbox[1] += shift_[1]; child_->reshape(child_bbox); } @@ -253,14 +244,11 @@ namespace psemek::ui { auto child_constraints = child_->size_constraints(); - result[0].min = child_constraints[0].min; - result[1].min = child_constraints[1].min; - if (!horizontal_scroll()) - result[0].max = child_constraints[0].max; + result[0] = child_constraints[0]; if (!vertical_scroll()) - result[1].max = child_constraints[1].max; + result[1] = child_constraints[1]; } auto st = merged_own_style(); @@ -273,6 +261,78 @@ namespace psemek::ui return result; } + geom::interval scroller::width_constraints(float height) const + { + geom::interval result = {0.f, std::numeric_limits::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 scroller::height_constraints(float width) const + { + geom::interval result = {0.f, std::numeric_limits::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; + + 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()); + if (!animate) + shift_[dim] = shift_tgt_[dim]; + } + void scroller::update(float dt) { shift_ += (shift_tgt_ - shift_) * std::min(25.f * dt, 1.f); @@ -288,7 +348,7 @@ namespace psemek::ui if (horizontal_scroll()) box[1].max -= w; if (vertical_scroll()) - box[0].max -= w; + box[0].max -= w; p.begin_stencil(); p.draw_rect(box, {0, 0, 0, 255}); @@ -353,18 +413,24 @@ namespace psemek::ui if (!child_) return; - auto c = child_->size_constraints(); + geom::vector min = child_->shape().bbox().dimensions(); - geom::vector min; - min[0] = shape_.box[0].length() - c[0].min; - min[1] = shape_.box[1].length() - c[1].min; + 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]); - 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); }