aboutsummaryrefslogtreecommitdiff
path: root/src/ui/render/ScrollBar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/render/ScrollBar.cpp')
-rw-r--r--src/ui/render/ScrollBar.cpp622
1 files changed, 622 insertions, 0 deletions
diff --git a/src/ui/render/ScrollBar.cpp b/src/ui/render/ScrollBar.cpp
new file mode 100644
index 00000000..7f69c1e2
--- /dev/null
+++ b/src/ui/render/ScrollBar.cpp
@@ -0,0 +1,622 @@
+#include "cru/ui/render/ScrollBar.hpp"
+
+#include "../Helper.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/platform/GraphBase.hpp"
+#include "cru/platform/graphics/Factory.hpp"
+#include "cru/platform/graphics/Geometry.hpp"
+#include "cru/platform/graphics/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/events/UiEvent.hpp"
+#include "cru/ui/render/ScrollRenderObject.hpp"
+#include "gsl/gsl_assert"
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <gsl/pointers>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+
+namespace cru::ui::render {
+using namespace std::chrono_literals;
+constexpr float kScrollBarCollapseThumbWidth = 2;
+constexpr float kScrollBarCollapsedTriggerExpandAreaWidth = 5;
+constexpr float kScrollBarExpandWidth = 10;
+constexpr float kScrollBarArrowHeight = 3.5;
+constexpr auto kScrollBarAutoCollapseDelay = 1500ms;
+
+constexpr std::array<ScrollBarAreaKind, 5> kScrollBarAreaKindList{
+ ScrollBarAreaKind::UpArrow, ScrollBarAreaKind::DownArrow,
+ ScrollBarAreaKind::UpSlot, ScrollBarAreaKind::DownSlot,
+ ScrollBarAreaKind::Thumb};
+
+namespace {
+std::unique_ptr<platform::graphics::IGeometry> CreateScrollBarArrowGeometry() {
+ auto geometry_builder = GetGraphFactory()->CreateGeometryBuilder();
+ geometry_builder->BeginFigure({-kScrollBarArrowHeight / 2, 0});
+ geometry_builder->LineTo({kScrollBarArrowHeight / 2, kScrollBarArrowHeight});
+ geometry_builder->LineTo({kScrollBarArrowHeight / 2, -kScrollBarArrowHeight});
+ geometry_builder->CloseFigure(true);
+ return geometry_builder->Build();
+}
+} // namespace
+
+ScrollBar::ScrollBar(gsl::not_null<ScrollRenderObject*> render_object,
+ Direction direction)
+ : render_object_(render_object), direction_(direction) {
+ // TODO: Use theme resource and delete this.
+
+ auto graphics_factory = GetUiApplication()->GetInstance()->GetGraphFactory();
+
+ collapsed_thumb_brush_ =
+ graphics_factory->CreateSolidColorBrush(colors::gray.WithAlpha(128));
+ expanded_thumb_brush_ = graphics_factory->CreateSolidColorBrush(colors::gray);
+ expanded_slot_brush_ =
+ graphics_factory->CreateSolidColorBrush(colors::seashell);
+ expanded_arrow_brush_ = graphics_factory->CreateSolidColorBrush(colors::gray);
+ expanded_arrow_background_brush_ =
+ graphics_factory->CreateSolidColorBrush(colors::seashell);
+
+ arrow_geometry_ = CreateScrollBarArrowGeometry();
+}
+
+ScrollBar::~ScrollBar() { RestoreCursor(); }
+
+void ScrollBar::SetEnabled(bool value) {
+ if (value == is_enabled_) return;
+ if (!value) {
+ SetExpanded(false);
+ if (move_thumb_start_) {
+ if (const auto control = this->render_object_->GetAttachedControl()) {
+ control->ReleaseMouse();
+ }
+ move_thumb_start_ = std::nullopt;
+ }
+ }
+}
+
+void ScrollBar::SetExpanded(bool value) {
+ if (is_expanded_ == value) return;
+ is_expanded_ = value;
+ render_object_->InvalidatePaint();
+}
+
+void ScrollBar::Draw(platform::graphics::IPainter* painter) {
+ if (is_enabled_) {
+ OnDraw(painter, is_expanded_);
+ }
+}
+
+void ScrollBar::InstallHandlers(controls::Control* control) {
+ event_guard_.Clear();
+ if (control != nullptr) {
+ event_guard_ +=
+ control->MouseDownEvent()->Bubble()->PrependShortCircuitHandler(
+ [control, this](event::MouseButtonEventArgs& event) {
+ if (event.GetButton() == mouse_buttons::left && IsEnabled() &&
+ IsExpanded()) {
+ auto hit_test_result =
+ ExpandedHitTest(event.GetPoint(render_object_));
+ if (!hit_test_result) return false;
+
+ switch (*hit_test_result) {
+ case ScrollBarAreaKind::UpArrow:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Line, -1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::DownArrow:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Line, 1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::UpSlot:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Page, -1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::DownSlot:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Page, 1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::Thumb: {
+ auto thumb_rect =
+ GetExpandedAreaRect(ScrollBarAreaKind::Thumb);
+ assert(thumb_rect);
+
+ if (!control->CaptureMouse()) break;
+ move_thumb_thumb_original_rect_ = *thumb_rect;
+ move_thumb_start_ = event.GetPoint();
+ event.SetHandled();
+ return true;
+ }
+ default:
+ break;
+ }
+ }
+
+ return false;
+ });
+
+ event_guard_ +=
+ control->MouseUpEvent()->Bubble()->PrependShortCircuitHandler(
+ [control, this](event::MouseButtonEventArgs& event) {
+ if (event.GetButton() == mouse_buttons::left &&
+ move_thumb_start_) {
+ move_thumb_start_ = std::nullopt;
+
+ auto hit_test_result =
+ ExpandedHitTest(event.GetPoint(this->render_object_));
+ if (!hit_test_result) {
+ OnMouseLeave();
+ }
+
+ control->ReleaseMouse();
+ event.SetHandled();
+ return true;
+ }
+ return false;
+ });
+
+ event_guard_ +=
+ control->MouseMoveEvent()->Bubble()->PrependShortCircuitHandler(
+ [this](event::MouseEventArgs& event) {
+ if (move_thumb_start_) {
+ auto new_scroll_position = CalculateNewScrollPosition(
+ move_thumb_thumb_original_rect_,
+ event.GetPoint() - *move_thumb_start_);
+
+ this->scroll_attempt_event_.Raise({GetDirection(),
+ ScrollKind::Absolute,
+ new_scroll_position});
+ event.SetHandled();
+ return true;
+ }
+
+ if (IsEnabled()) {
+ if (IsExpanded()) {
+ auto hit_test_result =
+ ExpandedHitTest(event.GetPoint(this->render_object_));
+ if (hit_test_result) {
+ SetCursor();
+ StopAutoCollapseTimer();
+ } else {
+ OnMouseLeave();
+ }
+ } else {
+ auto trigger_expand_area =
+ GetCollapsedTriggerExpandAreaRect();
+ if (trigger_expand_area &&
+ trigger_expand_area->IsPointInside(
+ event.GetPoint(this->render_object_))) {
+ SetExpanded(true);
+ SetCursor();
+ event.SetHandled();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ });
+
+ event_guard_ +=
+ control->MouseLeaveEvent()->Bubble()->PrependShortCircuitHandler(
+ [this](event::MouseEventArgs&) {
+ if (IsExpanded() && !move_thumb_start_) {
+ OnMouseLeave();
+ }
+ return false;
+ });
+ }
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetCollapsedThumbBrush() const {
+ // TODO: Read theme resource.
+ return collapsed_thumb_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedThumbBrush() const {
+ // TODO: Read theme resource.
+ return expanded_thumb_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedSlotBrush() const {
+ // TODO: Read theme resource.
+ return expanded_slot_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedArrowBrush() const {
+ // TODO: Read theme resource.
+ return expanded_arrow_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedArrowBackgroundBrush() const {
+ // TODO: Read theme resource.
+ return expanded_arrow_background_brush_;
+}
+
+void ScrollBar::OnDraw(platform::graphics::IPainter* painter,
+ bool is_expanded) {
+ if (is_expanded) {
+ auto thumb_rect = GetExpandedAreaRect(ScrollBarAreaKind::Thumb);
+ if (thumb_rect)
+ painter->FillRectangle(*thumb_rect, GetExpandedThumbBrush().get().get());
+
+ auto slot_brush = GetExpandedSlotBrush().get().get();
+
+ auto up_slot_rect = GetExpandedAreaRect(ScrollBarAreaKind::UpSlot);
+ if (up_slot_rect) painter->FillRectangle(*up_slot_rect, slot_brush);
+
+ auto down_slot_rect = GetExpandedAreaRect(ScrollBarAreaKind::DownSlot);
+ if (down_slot_rect) painter->FillRectangle(*down_slot_rect, slot_brush);
+
+ auto up_arrow = GetExpandedAreaRect(ScrollBarAreaKind::UpArrow);
+ if (up_arrow) this->DrawUpArrow(painter, *up_arrow);
+
+ auto down_arrow = GetExpandedAreaRect(ScrollBarAreaKind::DownArrow);
+ if (down_arrow) this->DrawDownArrow(painter, *down_arrow);
+ } else {
+ auto optional_rect = GetCollapsedThumbRect();
+ if (optional_rect) {
+ painter->FillRectangle(*optional_rect,
+ GetCollapsedThumbBrush().get().get());
+ }
+ }
+}
+
+void ScrollBar::SetCursor() {
+ if (!old_cursor_) {
+ if (const auto control = render_object_->GetAttachedControl()) {
+ old_cursor_ = control->GetCursor();
+ control->SetCursor(
+ GetUiApplication()->GetCursorManager()->GetSystemCursor(
+ platform::gui::SystemCursorType::Arrow));
+ }
+ }
+}
+
+void ScrollBar::RestoreCursor() {
+ if (old_cursor_) {
+ if (const auto control = render_object_->GetAttachedControl()) {
+ control->SetCursor(*old_cursor_);
+ }
+ old_cursor_ = std::nullopt;
+ }
+}
+
+void ScrollBar::BeginAutoCollapseTimer() {
+ if (!auto_collapse_timer_canceler_ && IsExpanded()) {
+ auto_collapse_timer_canceler_ = GetUiApplication()->SetTimeout(
+ kScrollBarAutoCollapseDelay, [this] { this->SetExpanded(false); });
+ }
+}
+
+void ScrollBar::StopAutoCollapseTimer() {
+ auto_collapse_timer_canceler_.Reset();
+}
+
+void ScrollBar::OnMouseLeave() {
+ RestoreCursor();
+ BeginAutoCollapseTimer();
+}
+
+std::optional<ScrollBarAreaKind> ScrollBar::ExpandedHitTest(
+ const Point& point) {
+ for (auto kind : kScrollBarAreaKindList) {
+ auto rect = this->GetExpandedAreaRect(kind);
+ if (rect) {
+ if (rect->IsPointInside(point)) return kind;
+ }
+ }
+ return std::nullopt;
+}
+
+HorizontalScrollBar::HorizontalScrollBar(
+ gsl::not_null<ScrollRenderObject*> render_object)
+ : ScrollBar(render_object, Direction::Horizontal) {}
+
+void HorizontalScrollBar::DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+void HorizontalScrollBar::DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Rotation(180) * Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+bool HorizontalScrollBar::IsShowBar() {
+ const auto child = render_object_->GetFirstChild();
+ if (child == nullptr) return false;
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ if (view_rect.width >= child_size.width) return false;
+
+ return true;
+}
+
+std::optional<Rect> HorizontalScrollBar::GetExpandedAreaRect(
+ ScrollBarAreaKind area_kind) {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ const auto child = render_object_->GetFirstChild();
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ const float start_percentage = view_rect.left / child_size.width;
+ const float length_percentage = view_rect.width / child_size.width;
+ const float end_percentage = start_percentage + length_percentage;
+
+ const float top = padding_rect.GetBottom() - kScrollBarExpandWidth;
+ const float height = kScrollBarExpandWidth;
+
+ // Without arrow.
+ const float bar_area_length = padding_rect.width - 3 * kScrollBarExpandWidth;
+ const float bar_area_start = padding_rect.left + kScrollBarExpandWidth;
+
+ switch (area_kind) {
+ case ScrollBarAreaKind::UpArrow:
+ return Rect{padding_rect.left, top, kScrollBarExpandWidth, height};
+ case ScrollBarAreaKind::DownArrow:
+ return Rect{padding_rect.GetRight() - 2 * kScrollBarExpandWidth, top,
+ kScrollBarExpandWidth, height};
+ case ScrollBarAreaKind::UpSlot:
+ return Rect{bar_area_start, top, bar_area_length * start_percentage,
+ height};
+ case ScrollBarAreaKind::DownSlot:
+ return Rect{bar_area_start + bar_area_length * end_percentage, top,
+ bar_area_length * (1 - end_percentage), height};
+ case ScrollBarAreaKind::Thumb:
+ return Rect{bar_area_start + bar_area_length * start_percentage, top,
+ bar_area_length * length_percentage, height};
+ default:
+ throw std::invalid_argument("Unsupported scroll area kind.");
+ }
+}
+
+std::optional<Rect> HorizontalScrollBar::GetCollapsedTriggerExpandAreaRect() {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ return Rect{
+ padding_rect.left,
+ padding_rect.GetBottom() - kScrollBarCollapsedTriggerExpandAreaWidth,
+ padding_rect.width, kScrollBarCollapseThumbWidth};
+}
+
+std::optional<Rect> HorizontalScrollBar::GetCollapsedThumbRect() {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto child = render_object_->GetFirstChild();
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ const float start_percentage = view_rect.left / child_size.width;
+ const float length_percentage = view_rect.width / child_size.width;
+ // const float end_percentage = start_percentage + length_percentage;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ return Rect{padding_rect.left + padding_rect.width * start_percentage,
+ padding_rect.GetBottom() - kScrollBarCollapseThumbWidth,
+ padding_rect.width * length_percentage,
+ kScrollBarCollapseThumbWidth};
+}
+
+float HorizontalScrollBar::CalculateNewScrollPosition(
+ const Rect& thumb_original_rect, const Point& mouse_offset) {
+ auto new_thumb_start = thumb_original_rect.left + mouse_offset.x;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ auto scroll_area_start = padding_rect.left + kScrollBarExpandWidth;
+ auto scroll_area_end = padding_rect.GetRight() - 2 * kScrollBarExpandWidth;
+
+ auto thumb_head_end = scroll_area_end - thumb_original_rect.width;
+
+ const auto child = render_object_->GetFirstChild();
+ const auto child_size = child->GetSize();
+
+ new_thumb_start =
+ std::clamp(new_thumb_start, scroll_area_start, thumb_head_end);
+
+ auto offset = (new_thumb_start - scroll_area_start) /
+ (scroll_area_end - scroll_area_start) * child_size.width;
+
+ return offset;
+}
+
+VerticalScrollBar::VerticalScrollBar(
+ gsl::not_null<ScrollRenderObject*> render_object)
+ : ScrollBar(render_object, Direction::Vertical) {}
+
+void VerticalScrollBar::DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Rotation(90) * Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+void VerticalScrollBar::DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Rotation(270) * Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+bool VerticalScrollBar::IsShowBar() {
+ const auto child = render_object_->GetFirstChild();
+ if (child == nullptr) return false;
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ if (view_rect.height >= child_size.height) return false;
+
+ return true;
+}
+
+std::optional<Rect> VerticalScrollBar::GetExpandedAreaRect(
+ ScrollBarAreaKind area_kind) {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ const auto child = render_object_->GetFirstChild();
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ const float start_percentage = view_rect.top / child_size.height;
+ const float length_percentage = view_rect.height / child_size.height;
+ const float end_percentage = start_percentage + length_percentage;
+
+ const float left = padding_rect.GetRight() - kScrollBarExpandWidth;
+ const float width = kScrollBarExpandWidth;
+
+ // Without arrow.
+ const float bar_area_length = padding_rect.height - 3 * kScrollBarExpandWidth;
+ const float bar_area_start = padding_rect.top + kScrollBarExpandWidth;
+
+ switch (area_kind) {
+ case ScrollBarAreaKind::UpArrow:
+ return Rect{left, padding_rect.top, width, kScrollBarExpandWidth};
+ case ScrollBarAreaKind::DownArrow:
+ return Rect{left, padding_rect.GetBottom() - 2 * kScrollBarExpandWidth,
+ width, kScrollBarExpandWidth};
+ case ScrollBarAreaKind::UpSlot:
+ return Rect{left, bar_area_start, width,
+ bar_area_length * start_percentage};
+ case ScrollBarAreaKind::DownSlot:
+ return Rect{left, bar_area_start + bar_area_length * end_percentage,
+ width, bar_area_length * (1 - end_percentage)};
+ case ScrollBarAreaKind::Thumb:
+ return Rect{left, bar_area_start + bar_area_length * start_percentage,
+ width, bar_area_length * length_percentage};
+ default:
+ throw std::invalid_argument("Unsupported scroll area kind.");
+ }
+}
+
+std::optional<Rect> VerticalScrollBar::GetCollapsedTriggerExpandAreaRect() {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ return Rect{
+ padding_rect.GetRight() - kScrollBarCollapsedTriggerExpandAreaWidth,
+ padding_rect.top, kScrollBarCollapseThumbWidth, padding_rect.height};
+}
+
+std::optional<Rect> VerticalScrollBar::GetCollapsedThumbRect() {
+ const auto child = render_object_->GetFirstChild();
+ if (child == nullptr) return std::nullopt;
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto padding_rect = render_object_->GetPaddingRect();
+ const auto child_size = child->GetSize();
+
+ if (view_rect.height >= child_size.height) return std::nullopt;
+
+ const float start_percentage = view_rect.top / child_size.height;
+ const float length_percentage = view_rect.height / child_size.height;
+ // const float end_percentage = start_percentage + length_percentage;
+
+ return Rect{padding_rect.GetRight() - kScrollBarCollapseThumbWidth,
+ padding_rect.top + padding_rect.height * start_percentage,
+ kScrollBarCollapseThumbWidth,
+ padding_rect.height * length_percentage};
+}
+
+float VerticalScrollBar::CalculateNewScrollPosition(
+ const Rect& thumb_original_rect, const Point& mouse_offset) {
+ auto new_thumb_start = thumb_original_rect.top + mouse_offset.y;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ auto scroll_area_start = padding_rect.top + kScrollBarExpandWidth;
+ auto scroll_area_end = padding_rect.GetBottom() - 2 * kScrollBarExpandWidth;
+
+ auto thumb_head_end = scroll_area_end - thumb_original_rect.height;
+
+ const auto child = render_object_->GetFirstChild();
+ const auto child_size = child->GetSize();
+
+ new_thumb_start =
+ std::clamp(new_thumb_start, scroll_area_start, thumb_head_end);
+
+ auto offset = (new_thumb_start - scroll_area_start) /
+ (scroll_area_end - scroll_area_start) * child_size.width;
+
+ return offset;
+}
+
+ScrollBarDelegate::ScrollBarDelegate(
+ gsl::not_null<ScrollRenderObject*> render_object)
+ : render_object_(render_object),
+ horizontal_bar_(render_object),
+ vertical_bar_(render_object) {
+ horizontal_bar_.ScrollAttemptEvent()->AddHandler(
+ [this](auto scroll) { this->scroll_attempt_event_.Raise(scroll); });
+ vertical_bar_.ScrollAttemptEvent()->AddHandler(
+ [this](auto scroll) { this->scroll_attempt_event_.Raise(scroll); });
+}
+
+void ScrollBarDelegate::DrawScrollBar(platform::graphics::IPainter* painter) {
+ horizontal_bar_.Draw(painter);
+ vertical_bar_.Draw(painter);
+}
+
+void ScrollBarDelegate::InstallHandlers(controls::Control* control) {
+ horizontal_bar_.InstallHandlers(control);
+ vertical_bar_.InstallHandlers(control);
+}
+} // namespace cru::ui::render