diff options
Diffstat (limited to 'src/ui/render')
-rw-r--r-- | src/ui/render/BorderRenderObject.cpp | 34 | ||||
-rw-r--r-- | src/ui/render/CanvasRenderObject.cpp | 2 | ||||
-rw-r--r-- | src/ui/render/FlexLayoutRenderObject.cpp | 23 | ||||
-rw-r--r-- | src/ui/render/RenderObject.cpp | 100 | ||||
-rw-r--r-- | src/ui/render/ScrollBar.cpp | 622 | ||||
-rw-r--r-- | src/ui/render/ScrollRenderObject.cpp | 66 | ||||
-rw-r--r-- | src/ui/render/TextRenderObject.cpp | 40 | ||||
-rw-r--r-- | src/ui/render/WindowRenderObject.cpp | 40 |
8 files changed, 818 insertions, 109 deletions
diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp index b7e1e709..e2c40f0c 100644 --- a/src/ui/render/BorderRenderObject.cpp +++ b/src/ui/render/BorderRenderObject.cpp @@ -2,9 +2,11 @@ #include "../Helper.hpp" #include "cru/common/Logger.hpp" -#include "cru/platform/graph/Factory.hpp" -#include "cru/platform/graph/Geometry.hpp" -#include "cru/platform/graph/util/Painter.hpp" +#include "cru/platform/graphics/Factory.hpp" +#include "cru/platform/graphics/Geometry.hpp" +#include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/style/ApplyBorderStyleInfo.hpp" +#include "gsl/gsl_assert" #include <algorithm> @@ -16,12 +18,13 @@ BorderRenderObject::BorderRenderObject() { BorderRenderObject::~BorderRenderObject() {} -void BorderRenderObject::SetBorderStyle(const BorderStyle& style) { - border_brush_ = style.border_brush; - border_thickness_ = style.border_thickness; - border_radius_ = style.border_radius; - foreground_brush_ = style.foreground_brush; - background_brush_ = style.background_brush; +void BorderRenderObject::ApplyBorderStyle( + const style::ApplyBorderStyleInfo& style) { + if (style.border_brush) border_brush_ = *style.border_brush; + if (style.border_thickness) border_thickness_ = *style.border_thickness; + if (style.border_radius) border_radius_ = *style.border_radius; + if (style.foreground_brush) foreground_brush_ = *style.foreground_brush; + if (style.background_brush) background_brush_ = *style.background_brush; InvalidateLayout(); } @@ -51,7 +54,7 @@ RenderObject* BorderRenderObject::HitTest(const Point& point) { } } -void BorderRenderObject::OnDrawCore(platform::graph::IPainter* painter) { +void BorderRenderObject::OnDrawCore(platform::graphics::IPainter* painter) { if (background_brush_ != nullptr) painter->FillGeometry(border_inner_geometry_.get(), background_brush_.get()); @@ -109,9 +112,10 @@ Size BorderRenderObject::OnMeasureCore(const MeasureRequirement& requirement, if (!requirement.max.height.IsNotSpecified()) { const auto max_height = requirement.max.height.GetLengthOrMax(); if (coerced_space_size.height > max_height) { - log::TagWarn(log_tag, - u"(Measure) Vertical length of padding, border and margin is " - u"bigger than required max length."); + log::TagWarn( + log_tag, + u"(Measure) Vertical length of padding, border and margin is " + u"bigger than required max length."); coerced_space_size.height = max_height; } content_requirement.max.height = max_height - coerced_space_size.height; @@ -235,7 +239,7 @@ void BorderRenderObject::RecreateGeometry() { r.left_bottom - Point{t.left, t.bottom}, r.right_bottom - Point{t.right, t.bottom}); - auto f = [](platform::graph::IGeometryBuilder* builder, const Rect& rect, + auto f = [](platform::graphics::IGeometryBuilder* builder, const Rect& rect, const CornerRadius& corner) { builder->BeginFigure(Point(rect.left + corner.left_top.x, rect.top)); builder->LineTo(Point(rect.GetRight() - corner.right_top.x, rect.top)); @@ -263,7 +267,7 @@ void BorderRenderObject::RecreateGeometry() { size.width - margin.GetHorizontalTotal(), size.height - margin.GetVerticalTotal()}; const auto graph_factory = GetGraphFactory(); - std::unique_ptr<platform::graph::IGeometryBuilder> builder{ + std::unique_ptr<platform::graphics::IGeometryBuilder> builder{ graph_factory->CreateGeometryBuilder()}; f(builder.get(), outer_rect, outer_radius); border_outer_geometry_ = builder->Build(); diff --git a/src/ui/render/CanvasRenderObject.cpp b/src/ui/render/CanvasRenderObject.cpp index 967fdcec..bf1155e1 100644 --- a/src/ui/render/CanvasRenderObject.cpp +++ b/src/ui/render/CanvasRenderObject.cpp @@ -10,7 +10,7 @@ RenderObject* CanvasRenderObject::HitTest(const Point& point) { return padding_rect.IsPointInside(point) ? this : nullptr; } -void CanvasRenderObject::OnDrawContent(platform::graph::IPainter* painter) { +void CanvasRenderObject::OnDrawContent(platform::graphics::IPainter* painter) { const auto rect = GetContentRect(); CanvasPaintEventArgs args{painter, rect.GetSize()}; paint_event_.Raise(args); diff --git a/src/ui/render/FlexLayoutRenderObject.cpp b/src/ui/render/FlexLayoutRenderObject.cpp index ade230b5..b1ef69ee 100644 --- a/src/ui/render/FlexLayoutRenderObject.cpp +++ b/src/ui/render/FlexLayoutRenderObject.cpp @@ -1,7 +1,7 @@ #include "cru/ui/render/FlexLayoutRenderObject.hpp" #include "cru/common/Logger.hpp" -#include "cru/platform/graph/util/Painter.hpp" +#include "cru/platform/graphics/util/Painter.hpp" #include "cru/ui/render/LayoutHelper.hpp" #include <algorithm> @@ -10,6 +10,10 @@ namespace cru::ui::render { +std::u16string_view FlexLayoutRenderObject::GetName() const { + return u"FlexLayoutRenderObject"; +} + struct tag_horizontal_t {}; struct tag_vertical_t {}; @@ -64,7 +68,7 @@ template <typename TSize> constexpr TSize CreateTSize(decltype(std::declval<TSize>().width) main, decltype(std::declval<TSize>().height) cross, tag_vertical_t) { - return TSize{main, cross}; + return TSize{cross, main}; } enum class FlexLayoutAdjustType { None, Expand, Shrink }; @@ -387,10 +391,11 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { const auto cross_align = GetChildLayoutDataList()[i].cross_alignment.value_or( GetItemCrossAlign()); - child->Layout( - Point{content_rect.top + current_main_offset, - CalculateAnchorByAlignment(cross_align, content_rect.left, - content_rect.width, size.width)}); + child->Layout(Point{ + CalculateAnchorByAlignment(cross_align, content_rect.left, + content_rect.width, size.width), + content_rect.top + current_main_offset, + }); current_main_offset += size.height; } } else { @@ -402,9 +407,9 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { GetChildLayoutDataList()[i].cross_alignment.value_or( GetItemCrossAlign()); child->Layout( - Point{content_rect.GetBottom() - current_main_offset, - CalculateAnchorByAlignment(cross_align, content_rect.left, - content_rect.width, size.width)}); + Point{CalculateAnchorByAlignment(cross_align, content_rect.left, + content_rect.width, size.width), + content_rect.GetBottom() - current_main_offset}); current_main_offset += size.height; } } diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index 30433868..7cf750cd 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -1,12 +1,21 @@ #include "cru/ui/render/RenderObject.hpp" #include "cru/common/Logger.hpp" -#include "cru/platform/graph/util/Painter.hpp" -#include "cru/ui/UiHost.hpp" +#include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/DebugFlags.hpp" +#include "cru/ui/host/WindowHost.hpp" #include <algorithm> +#include <string> +#include <string_view> +#include <vector> namespace cru::ui::render { +void RenderObject::SetAttachedControl(controls::Control* new_control) { + control_ = new_control; + OnAttachedControlChanged(new_control); +} + void RenderObject::AddChild(RenderObject* render_object, const Index position) { Expects(child_mode_ != ChildMode::None); Expects(!(child_mode_ == ChildMode::Single && children_.size() > 0)); @@ -20,7 +29,7 @@ void RenderObject::AddChild(RenderObject* render_object, const Index position) { children_.insert(children_.cbegin() + position, render_object); render_object->SetParent(this); - render_object->SetRenderHostRecursive(GetUiHost()); + render_object->SetWindowHostRecursive(GetWindowHost()); OnAddChild(render_object, position); } @@ -33,10 +42,25 @@ void RenderObject::RemoveChild(const Index position) { const auto removed_child = *i; children_.erase(i); removed_child->SetParent(nullptr); - removed_child->SetRenderHostRecursive(nullptr); + removed_child->SetWindowHostRecursive(nullptr); OnRemoveChild(removed_child, position); } +RenderObject* RenderObject::GetFirstChild() const { + const auto& children = GetChildren(); + if (children.empty()) { + return nullptr; + } else { + return children.front(); + } +} + +void RenderObject::TraverseDescendants( + const std::function<void(RenderObject*)>& action) { + action(this); + for (auto child : children_) child->TraverseDescendants(action); +} + Point RenderObject::GetTotalOffset() const { Point result{}; const RenderObject* render_object = this; @@ -66,18 +90,34 @@ void RenderObject::Measure(const MeasureRequirement& requirement, MeasureSize merged_preferred_size = preferred_size.OverrideBy(preferred_size_); + if constexpr (cru::ui::debug_flags::layout) { + log::Debug(u"{} Measure begins :\nrequirement: {}\npreferred size: {}", + this->GetDebugPathInTree(), requirement.ToDebugString(), + preferred_size.ToDebugString()); + } + size_ = OnMeasureCore(merged_requirement, merged_preferred_size); + + if constexpr (cru::ui::debug_flags::layout) { + log::Debug(u"{} Measure ends :\nresult size: {}", + this->GetDebugPathInTree(), size_.ToDebugString()); + } + Ensures(size_.width >= 0); Ensures(size_.height >= 0); Ensures(requirement.Satisfy(size_)); } void RenderObject::Layout(const Point& offset) { + if constexpr (cru::ui::debug_flags::layout) { + log::Debug(u"{} Layout :\noffset: {}", this->GetDebugPathInTree(), + offset.ToDebugString()); + } offset_ = offset; OnLayoutCore(); } -void RenderObject::Draw(platform::graph::IPainter* painter) { +void RenderObject::Draw(platform::graphics::IPainter* painter) { OnDrawCore(painter); } @@ -112,29 +152,29 @@ void RenderObject::OnRemoveChild(RenderObject* removed_child, Index position) { InvalidatePaint(); } -void RenderObject::DefaultDrawChildren(platform::graph::IPainter* painter) { +void RenderObject::DefaultDrawChildren(platform::graphics::IPainter* painter) { for (const auto child : GetChildren()) { auto offset = child->GetOffset(); - platform::graph::util::WithTransform( + platform::graphics::util::WithTransform( painter, platform::Matrix::Translation(offset.x, offset.y), [child](auto p) { child->Draw(p); }); } } -void RenderObject::DefaultDrawContent(platform::graph::IPainter* painter) { +void RenderObject::DefaultDrawContent(platform::graphics::IPainter* painter) { const auto content_rect = GetContentRect(); - platform::graph::util::WithTransform( + platform::graphics::util::WithTransform( painter, Matrix::Translation(content_rect.left, content_rect.top), [this](auto p) { this->OnDrawContent(p); }); } -void RenderObject::OnDrawCore(platform::graph::IPainter* painter) { +void RenderObject::OnDrawCore(platform::graphics::IPainter* painter) { DefaultDrawContent(painter); DefaultDrawChildren(painter); } -void RenderObject::OnDrawContent(platform::graph::IPainter* painter) { +void RenderObject::OnDrawContent(platform::graphics::IPainter* painter) { CRU_UNUSED(painter); } @@ -249,24 +289,44 @@ void RenderObject::SetParent(RenderObject* new_parent) { } void RenderObject::InvalidateLayout() { - if (ui_host_ != nullptr) ui_host_->InvalidateLayout(); + if (window_host_ != nullptr) window_host_->InvalidateLayout(); } void RenderObject::InvalidatePaint() { - if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); + if (window_host_ != nullptr) window_host_->InvalidatePaint(); } -void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { - render_object->OnAfterLayout(); - for (const auto o : render_object->GetChildren()) { - NotifyAfterLayoutRecursive(o); +constexpr std::u16string_view kUnamedName(u"UNNAMED"); + +std::u16string_view RenderObject::GetName() const { return kUnamedName; } + +std::u16string RenderObject::GetDebugPathInTree() const { + std::vector<std::u16string_view> chain; + const RenderObject* parent = this; + while (parent != nullptr) { + chain.push_back(parent->GetName()); + parent = parent->GetParent(); } + + std::u16string result(chain.back()); + for (auto iter = chain.crbegin() + 1; iter != chain.crend(); ++iter) { + result += u" -> "; + result += *iter; + } + + return result; } -void RenderObject::SetRenderHostRecursive(UiHost* host) { - ui_host_ = host; +void RenderObject::SetWindowHostRecursive(host::WindowHost* host) { + if (window_host_ != nullptr) { + detach_from_host_event_.Raise(nullptr); + } + window_host_ = host; + if (host != nullptr) { + attach_to_host_event_.Raise(nullptr); + } for (const auto child : GetChildren()) { - child->SetRenderHostRecursive(host); + child->SetWindowHostRecursive(host); } } } // namespace cru::ui::render 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 diff --git a/src/ui/render/ScrollRenderObject.cpp b/src/ui/render/ScrollRenderObject.cpp index 08ce744b..fd5143ff 100644 --- a/src/ui/render/ScrollRenderObject.cpp +++ b/src/ui/render/ScrollRenderObject.cpp @@ -1,11 +1,18 @@ #include "cru/ui/render/ScrollRenderObject.hpp" -#include "cru/platform/graph/Painter.hpp" -#include "cru/platform/graph/util/Painter.hpp" +#include "cru/platform/graphics/Painter.hpp" +#include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/render/ScrollBar.hpp" #include <algorithm> +#include <memory> +#include <optional> namespace cru::ui::render { +constexpr float kLineHeight = 16; + namespace { // This method assumes margin offset is already considered. // It promises that it won't return negetive value. @@ -24,13 +31,46 @@ Point CoerceScroll(const Point& scroll_offset, const Size& content_size, n = max; }; - coerce(result.x, scroll_offset.x); - coerce(result.y, scroll_offset.y); + coerce(result.x, max_scroll.x); + coerce(result.y, max_scroll.y); return result; } } // namespace +ScrollRenderObject::ScrollRenderObject() : RenderObject(ChildMode::Single) { + scroll_bar_delegate_ = std::make_unique<ScrollBarDelegate>(this); + scroll_bar_delegate_->ScrollAttemptEvent()->AddHandler( + [this](const struct Scroll& scroll) { this->Scroll(scroll); }); +} + +void ScrollRenderObject::Scroll(const struct Scroll& scroll) { + auto direction = scroll.direction; + + switch (scroll.kind) { + case ScrollKind::Absolute: + SetScrollOffset(direction, scroll.value); + break; + case ScrollKind::Relative: + SetScrollOffset(direction, + GetScrollOffset(scroll.direction) + scroll.value); + break; + case ScrollKind::Page: + SetScrollOffset(direction, GetScrollOffset(direction) + + (direction == Direction::Horizontal + ? GetViewRect().width + : GetViewRect().height) * + scroll.value); + break; + case ScrollKind::Line: + SetScrollOffset(direction, + GetScrollOffset(direction) + kLineHeight * scroll.value); + break; + default: + break; + } +} + RenderObject* ScrollRenderObject::HitTest(const Point& point) { if (const auto child = GetSingleChild()) { const auto offset = child->GetOffset(); @@ -42,16 +82,17 @@ RenderObject* ScrollRenderObject::HitTest(const Point& point) { return rect.IsPointInside(point) ? this : nullptr; } // namespace cru::ui::render -void ScrollRenderObject::OnDrawCore(platform::graph::IPainter* painter) { +void ScrollRenderObject::OnDrawCore(platform::graphics::IPainter* painter) { DefaultDrawContent(painter); if (const auto child = GetSingleChild()) { - painter->PushLayer(this->GetPaddingRect()); + painter->PushLayer(this->GetContentRect()); const auto offset = child->GetOffset(); - platform::graph::util::WithTransform( + platform::graphics::util::WithTransform( painter, Matrix::Translation(offset.x, offset.y), - [child](platform::graph::IPainter* p) { child->Draw(p); }); + [child](platform::graphics::IPainter* p) { child->Draw(p); }); painter->PopLayer(); } + scroll_bar_delegate_->DrawScrollBar(painter); } Point ScrollRenderObject::GetScrollOffset() { @@ -138,8 +179,15 @@ Size ScrollRenderObject::OnMeasureContent(const MeasureRequirement& requirement, void ScrollRenderObject::OnLayoutContent(const Rect& content_rect) { if (const auto child = GetSingleChild()) { - const auto child_size = child->GetSize(); child->Layout(content_rect.GetLeftTop() - GetScrollOffset()); } } + +void ScrollRenderObject::OnAttachedControlChanged(controls::Control* control) { + if (control) { + scroll_bar_delegate_->InstallHandlers(control); + } else { + scroll_bar_delegate_->UninstallHandlers(); + } +} } // namespace cru::ui::render diff --git a/src/ui/render/TextRenderObject.cpp b/src/ui/render/TextRenderObject.cpp index cecbe1f3..06092d52 100644 --- a/src/ui/render/TextRenderObject.cpp +++ b/src/ui/render/TextRenderObject.cpp @@ -2,19 +2,19 @@ #include "../Helper.hpp" #include "cru/common/Logger.hpp" -#include "cru/platform/graph/Factory.hpp" -#include "cru/platform/graph/TextLayout.hpp" -#include "cru/platform/graph/util/Painter.hpp" +#include "cru/platform/graphics/Factory.hpp" +#include "cru/platform/graphics/TextLayout.hpp" +#include "cru/platform/graphics/util/Painter.hpp" #include <algorithm> #include <limits> namespace cru::ui::render { TextRenderObject::TextRenderObject( - std::shared_ptr<platform::graph::IBrush> brush, - std::shared_ptr<platform::graph::IFont> font, - std::shared_ptr<platform::graph::IBrush> selection_brush, - std::shared_ptr<platform::graph::IBrush> caret_brush) { + std::shared_ptr<platform::graphics::IBrush> brush, + std::shared_ptr<platform::graphics::IFont> font, + std::shared_ptr<platform::graphics::IBrush> selection_brush, + std::shared_ptr<platform::graphics::IBrush> caret_brush) { Expects(brush); Expects(font); Expects(selection_brush); @@ -43,20 +43,22 @@ std::u16string_view TextRenderObject::GetTextView() const { void TextRenderObject::SetText(std::u16string new_text) { text_layout_->SetText(std::move(new_text)); + InvalidateLayout(); } void TextRenderObject::SetBrush( - std::shared_ptr<platform::graph::IBrush> new_brush) { + std::shared_ptr<platform::graphics::IBrush> new_brush) { Expects(new_brush); new_brush.swap(brush_); InvalidatePaint(); } -std::shared_ptr<platform::graph::IFont> TextRenderObject::GetFont() const { +std::shared_ptr<platform::graphics::IFont> TextRenderObject::GetFont() const { return text_layout_->GetFont(); } -void TextRenderObject::SetFont(std::shared_ptr<platform::graph::IFont> font) { +void TextRenderObject::SetFont( + std::shared_ptr<platform::graphics::IFont> font) { Expects(font); text_layout_->SetFont(std::move(font)); } @@ -69,7 +71,7 @@ Point TextRenderObject::TextSinglePoint(gsl::index position, bool trailing) { return text_layout_->TextSinglePoint(position, trailing); } -platform::graph::TextHitTestResult TextRenderObject::TextHitTest( +platform::graphics::TextHitTestResult TextRenderObject::TextHitTest( const Point& point) { return text_layout_->HitTest(point); } @@ -80,7 +82,7 @@ void TextRenderObject::SetSelectionRange(std::optional<TextRange> new_range) { } void TextRenderObject::SetSelectionBrush( - std::shared_ptr<platform::graph::IBrush> new_brush) { + std::shared_ptr<platform::graphics::IBrush> new_brush) { Expects(new_brush); new_brush.swap(selection_brush_); if (selection_range_ && selection_range_->count) { @@ -105,7 +107,7 @@ void TextRenderObject::SetCaretPosition(gsl::index position) { } void TextRenderObject::GetCaretBrush( - std::shared_ptr<platform::graph::IBrush> brush) { + std::shared_ptr<platform::graphics::IBrush> brush) { Expects(brush); brush.swap(caret_brush_); if (draw_caret_) { @@ -153,12 +155,18 @@ Rect TextRenderObject::GetCaretRect() { return rect; } +void TextRenderObject::SetMeasureIncludingTrailingSpace(bool including) { + if (is_measure_including_trailing_space_ == including) return; + is_measure_including_trailing_space_ = including; + InvalidateLayout(); +} + RenderObject* TextRenderObject::HitTest(const Point& point) { const auto padding_rect = GetPaddingRect(); return padding_rect.IsPointInside(point) ? this : nullptr; } -void TextRenderObject::OnDrawContent(platform::graph::IPainter* painter) { +void TextRenderObject::OnDrawContent(platform::graphics::IPainter* painter) { if (this->selection_range_.has_value()) { const auto&& rects = text_layout_->TextRangeRect(this->selection_range_.value()); @@ -184,7 +192,9 @@ Size TextRenderObject::OnMeasureContent(const MeasureRequirement& requirement, text_layout_->SetMaxWidth(measure_width); text_layout_->SetMaxHeight(std::numeric_limits<float>::max()); - const auto text_size = text_layout_->GetTextBounds().GetSize(); + const auto text_size = + text_layout_->GetTextBounds(is_measure_including_trailing_space_) + .GetSize(); auto result = text_size; if (requirement.max.width.IsSpecified() && diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp deleted file mode 100644 index 4adf559e..00000000 --- a/src/ui/render/WindowRenderObject.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "cru/ui/render/WindowRenderObject.hpp" - -#include "../Helper.hpp" -#include "cru/platform/graph/util/Painter.hpp" -#include "cru/ui/UiHost.hpp" - -namespace cru::ui::render { -WindowRenderObject::WindowRenderObject(UiHost* host) { - SetChildMode(ChildMode::Single); - ui_host_ = host; - after_layout_event_guard_.Reset(host->AfterLayoutEvent()->AddHandler( - [this](auto) { NotifyAfterLayoutRecursive(this); })); -} - -RenderObject* WindowRenderObject::HitTest(const Point& point) { - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - Point p{point.x - offset.x, point.y - offset.y}; - const auto result = child->HitTest(p); - if (result != nullptr) { - return result; - } - } - return Rect{Point{}, GetSize()}.IsPointInside(point) ? this : nullptr; -} - -Size WindowRenderObject::OnMeasureContent(const MeasureRequirement& requirement, - const MeasureSize& preferred_size) { - if (const auto child = GetChild()) { - child->Measure(requirement, preferred_size); - return child->GetSize(); - } else { - return Size{}; - } -} - -void WindowRenderObject::OnLayoutContent(const Rect& content_rect) { - if (const auto child = GetChild()) child->Layout(content_rect.GetLeftTop()); -} -} // namespace cru::ui::render |