diff options
-rw-r--r-- | include/cru/platform/GraphBase.hpp | 6 | ||||
-rw-r--r-- | include/cru/ui/render/RenderObject.hpp | 16 | ||||
-rw-r--r-- | include/cru/ui/render/ScrollBarDelegate.hpp | 142 | ||||
-rw-r--r-- | include/cru/ui/render/ScrollRenderObject.hpp | 8 | ||||
-rw-r--r-- | src/ui/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/ui/render/RenderObject.cpp | 14 | ||||
-rw-r--r-- | src/ui/render/ScrollBarDelegate.cpp | 145 | ||||
-rw-r--r-- | src/ui/render/ScrollRenderObject.cpp | 16 |
8 files changed, 342 insertions, 7 deletions
diff --git a/include/cru/platform/GraphBase.hpp b/include/cru/platform/GraphBase.hpp index b580ad31..6bf2736f 100644 --- a/include/cru/platform/GraphBase.hpp +++ b/include/cru/platform/GraphBase.hpp @@ -316,6 +316,12 @@ struct Color { (hex >> 24) & mask); } + constexpr Color WithAlpha(std::uint8_t new_alpha) const { + auto result = *this; + result.alpha = new_alpha; + return result; + } + std::uint8_t red; std::uint8_t green; std::uint8_t blue; diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp index 2b166efc..8bcd4c62 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -2,6 +2,7 @@ #include "Base.hpp" #include "MeasureRequirement.hpp" +#include "cru/common/Base.hpp" #include "cru/common/Event.hpp" #include "cru/ui/Base.hpp" @@ -63,9 +64,7 @@ class RenderObject : public Object { ~RenderObject() override = default; controls::Control* GetAttachedControl() const { return control_; } - void SetAttachedControl(controls::Control* new_control) { - control_ = new_control; - } + void SetAttachedControl(controls::Control* new_control); host::WindowHost* GetWindowHost() const { return window_host_; } @@ -76,6 +75,7 @@ class RenderObject : public Object { void AddChild(RenderObject* render_object, Index position); void RemoveChild(Index position); + RenderObject* GetFirstChild() const; void TraverseDescendants(const std::function<void(RenderObject*)>& action); // Offset from parent's lefttop to lefttop of this render object. Margin is @@ -131,6 +131,9 @@ class RenderObject : public Object { // This will set offset of this render object and call OnLayoutCore. void Layout(const Point& offset); + virtual Rect GetPaddingRect() const; + virtual Rect GetContentRect() const; + void Draw(platform::graphics::IPainter* painter); // Param point must be relative the lefttop of render object including margin. @@ -201,10 +204,11 @@ class RenderObject : public Object { // Lefttop of content_rect should be added when calculated children's offset. virtual void OnLayoutContent(const Rect& content_rect) = 0; - virtual void OnAfterLayout(); + virtual void OnAttachedControlChanged(controls::Control* control) { + CRU_UNUSED(control) + } - virtual Rect GetPaddingRect() const; - virtual Rect GetContentRect() const; + virtual void OnAfterLayout(); private: void SetParent(RenderObject* new_parent); diff --git a/include/cru/ui/render/ScrollBarDelegate.hpp b/include/cru/ui/render/ScrollBarDelegate.hpp new file mode 100644 index 00000000..e5c63f6d --- /dev/null +++ b/include/cru/ui/render/ScrollBarDelegate.hpp @@ -0,0 +1,142 @@ +#pragma once +#include "Base.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" +#include "cru/platform/graphics/Base.hpp" +#include "cru/platform/graphics/Painter.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/controls/Control.hpp" + +#include <gsl/pointers> +#include <memory> +#include <optional> + +namespace cru::ui::render { +class ScrollRenderObject; + +enum class ScrollBarAreaKind { + UpArrow, // Line up + DownArrow, // Line down + UpThumb, // Page up + DownThumb, // Page down + Thumb +}; + +class ScrollBar : public Object { + public: + explicit ScrollBar(gsl::not_null<ScrollRenderObject*> render_object); + + CRU_DELETE_COPY(ScrollBar) + CRU_DELETE_MOVE(ScrollBar) + + ~ScrollBar() override = default; + + public: + bool IsEnabled() const { return is_enabled_; } + void SetEnabled(bool value); + + void Draw(platform::graphics::IPainter* painter); + + virtual std::optional<ScrollBarAreaKind> HitTest(const Point& point) = 0; + + IEvent<float>* ScrollAttemptEvent() { return &scroll_attempt_event_; } + + void InstallHandlers(controls::Control* control); + void UninstallHandlers() { InstallHandlers(nullptr); } + + gsl::not_null<std::shared_ptr<platform::graphics::IBrush>> + GetCollapseThumbBrush() const; + + protected: + virtual void OnDraw(platform::graphics::IPainter* painter, bool expand) = 0; + + protected: + gsl::not_null<ScrollRenderObject*> render_object_; + + private: + bool is_enabled_ = true; + + bool is_expanded_ = false; + + std::shared_ptr<platform::graphics::IBrush> collapse_thumb_brush_; + + EventRevokerListGuard event_guard_; + + Event<float> scroll_attempt_event_; +}; + +class HorizontalScrollBar : public ScrollBar { + public: + explicit HorizontalScrollBar( + gsl::not_null<ScrollRenderObject*> render_object); + + CRU_DELETE_COPY(HorizontalScrollBar) + CRU_DELETE_MOVE(HorizontalScrollBar) + + ~HorizontalScrollBar() override = default; + + public: + std::optional<ScrollBarAreaKind> HitTest(const Point& point) override; + + protected: + void OnDraw(platform::graphics::IPainter* painter, bool expand) override; +}; + +class VerticalScrollBar : public ScrollBar { + public: + explicit VerticalScrollBar(gsl::not_null<ScrollRenderObject*> render_object); + + CRU_DELETE_COPY(VerticalScrollBar) + CRU_DELETE_MOVE(VerticalScrollBar) + + ~VerticalScrollBar() override = default; + + public: + std::optional<ScrollBarAreaKind> HitTest(const Point& point) override; + + protected: + void OnDraw(platform::graphics::IPainter* painter, bool expand) override; +}; + +struct ScrollBarScrollAttemptArgs { + float x_offset; + float y_offset; +}; + +// A delegate to draw scrollbar and register related events. +class ScrollBarDelegate : public Object { + public: + explicit ScrollBarDelegate(gsl::not_null<ScrollRenderObject*> render_object); + + CRU_DELETE_COPY(ScrollBarDelegate) + CRU_DELETE_MOVE(ScrollBarDelegate) + + ~ScrollBarDelegate() override = default; + + public: + bool IsHorizontalBarEnabled() const { return horizontal_bar_.IsEnabled(); } + void SetHorizontalBarEnabled(bool value) { + horizontal_bar_.SetEnabled(value); + } + + bool IsVerticalBarEnabled() const { return horizontal_bar_.IsEnabled(); } + void SetVerticalBarEnabled(bool value) { horizontal_bar_.SetEnabled(value); } + + IEvent<ScrollBarScrollAttemptArgs>* ScrollAttemptEvent() { + return &scroll_attempt_event_; + } + + void DrawScrollBar(platform::graphics::IPainter* painter); + + void InstallHandlers(controls::Control* control); + void UninstallHandlers() { InstallHandlers(nullptr); } + + private: + gsl::not_null<ScrollRenderObject*> render_object_; + + HorizontalScrollBar horizontal_bar_; + VerticalScrollBar vertical_bar_; + + Event<ScrollBarScrollAttemptArgs> scroll_attempt_event_; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp index 3cc0e4c4..5a431527 100644 --- a/include/cru/ui/render/ScrollRenderObject.hpp +++ b/include/cru/ui/render/ScrollRenderObject.hpp @@ -2,7 +2,9 @@ #include "RenderObject.hpp" #include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/render/ScrollBarDelegate.hpp" +#include <memory> #include <optional> namespace cru::ui::render { @@ -16,7 +18,7 @@ namespace cru::ui::render { // Or layout by scroll state. class ScrollRenderObject : public RenderObject { public: - ScrollRenderObject() : RenderObject(ChildMode::Single) {} + ScrollRenderObject(); CRU_DELETE_COPY(ScrollRenderObject) CRU_DELETE_MOVE(ScrollRenderObject) @@ -54,7 +56,11 @@ class ScrollRenderObject : public RenderObject { const MeasureSize& preferred_size) override; void OnLayoutContent(const Rect& content_rect) override; + void OnAttachedControlChanged(controls::Control* control) override; + private: Point scroll_offset_; + + std::unique_ptr<ScrollBarDelegate> scroll_bar_delegate_; }; } // namespace cru::ui::render diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index d1c1e830..6153bc07 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(cru_ui STATIC render/FlexLayoutRenderObject.cpp render/LayoutHelper.cpp render/RenderObject.cpp + render/ScrollBarDelegate.cpp render/ScrollRenderObject.cpp render/StackLayoutRenderObject.cpp render/TextRenderObject.cpp @@ -77,6 +78,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/LayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/MeasureRequirement.hpp ${CRU_UI_INCLUDE_DIR}/render/RenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/ScrollBarDelegate.hpp ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index a40ce9b8..7cf750cd 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -11,6 +11,11 @@ #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)); @@ -41,6 +46,15 @@ void RenderObject::RemoveChild(const Index position) { 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); diff --git a/src/ui/render/ScrollBarDelegate.cpp b/src/ui/render/ScrollBarDelegate.cpp new file mode 100644 index 00000000..2814c567 --- /dev/null +++ b/src/ui/render/ScrollBarDelegate.cpp @@ -0,0 +1,145 @@ +#include "cru/ui/render/ScrollBarDelegate.hpp" + +#include "../Helper.hpp" +#include "cru/common/Base.hpp" +#include "cru/platform/GraphBase.hpp" +#include "cru/platform/graphics/Factory.hpp" +#include "cru/platform/graphics/Painter.hpp" +#include "cru/ui/render/ScrollRenderObject.hpp" + +#include <gsl/pointers> +#include <optional> + +namespace cru::ui::render { +constexpr float kScrollBarCollapseThumbWidth = 2; + +ScrollBar::ScrollBar(gsl::not_null<ScrollRenderObject*> render_object) + : render_object_(render_object) { + // TODO: Use theme resource and delete this. + auto collapse_thumb_brush = GetUiApplication() + ->GetInstance() + ->GetGraphFactory() + ->CreateSolidColorBrush(); + collapse_thumb_brush->SetColor(colors::gray.WithAlpha(128)); + collapse_thumb_brush_ = std::move(collapse_thumb_brush); +} + +void ScrollBar::SetEnabled(bool value) { + CRU_UNUSED(value) + // TODO: Implement this. +} + +void ScrollBar::Draw(platform::graphics::IPainter* painter) { + if (is_enabled_) { + OnDraw(painter, is_expanded_); + } +} + +void ScrollBar::InstallHandlers(controls::Control* control) { + CRU_UNUSED(control); + // TODO: Implement this. +} + +gsl::not_null<std::shared_ptr<platform::graphics::IBrush>> +ScrollBar::GetCollapseThumbBrush() const { + // TODO: Read theme resource. + return collapse_thumb_brush_; +} + +HorizontalScrollBar::HorizontalScrollBar( + gsl::not_null<ScrollRenderObject*> render_object) + : ScrollBar(render_object) {} + +std::optional<ScrollBarAreaKind> HorizontalScrollBar::HitTest( + const Point& point) { + // TODO: Implement this. + CRU_UNUSED(point); + return std::nullopt; +} + +void HorizontalScrollBar::OnDraw(platform::graphics::IPainter* painter, + bool expand) { + const auto child = render_object_->GetFirstChild(); + if (child == nullptr) return; + + const auto view_rect = render_object_->GetViewRect(); + const auto padding_rect = render_object_->GetPaddingRect(); + const auto child_size = child->GetSize(); + + if (view_rect.width >= child_size.width) return; + + 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; + + if (expand) { + // TODO: Implement this. + } else { + Rect thumb_rect{padding_rect.left + padding_rect.width * start_percentage, + padding_rect.GetBottom() - kScrollBarCollapseThumbWidth, + padding_rect.width * length_percentage, + kScrollBarCollapseThumbWidth}; + painter->FillRectangle(thumb_rect, GetCollapseThumbBrush().get().get()); + } +} + +VerticalScrollBar::VerticalScrollBar( + gsl::not_null<ScrollRenderObject*> render_object) + : ScrollBar(render_object) {} + +std::optional<ScrollBarAreaKind> VerticalScrollBar::HitTest( + const Point& point) { + // TODO: Implement this. + CRU_UNUSED(point); + return std::nullopt; +} + +void VerticalScrollBar::OnDraw(platform::graphics::IPainter* painter, + bool expand) { + const auto child = render_object_->GetFirstChild(); + if (child == nullptr) return; + + 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; + + 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; + + if (expand) { + // TODO: Implement this. + } else { + Rect thumb_rect{padding_rect.GetRight() - kScrollBarCollapseThumbWidth, + padding_rect.top + padding_rect.height * start_percentage, + kScrollBarCollapseThumbWidth, + padding_rect.height * length_percentage}; + painter->FillRectangle(thumb_rect, GetCollapseThumbBrush().get().get()); + } +} + +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](float offset) { + this->scroll_attempt_event_.Raise({offset, 0}); + }); + vertical_bar_.ScrollAttemptEvent()->AddHandler([this](float offset) { + this->scroll_attempt_event_.Raise({0, offset}); + }); +} + +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 5b9cb627..18b0adbf 100644 --- a/src/ui/render/ScrollRenderObject.cpp +++ b/src/ui/render/ScrollRenderObject.cpp @@ -2,8 +2,11 @@ #include "cru/platform/graphics/Painter.hpp" #include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/render/ScrollBarDelegate.hpp" #include <algorithm> +#include <memory> namespace cru::ui::render { namespace { @@ -31,6 +34,10 @@ Point CoerceScroll(const Point& scroll_offset, const Size& content_size, } } // namespace +ScrollRenderObject::ScrollRenderObject() : RenderObject(ChildMode::Single) { + scroll_bar_delegate_ = std::make_unique<ScrollBarDelegate>(this); +} + RenderObject* ScrollRenderObject::HitTest(const Point& point) { if (const auto child = GetSingleChild()) { const auto offset = child->GetOffset(); @@ -52,6 +59,7 @@ void ScrollRenderObject::OnDrawCore(platform::graphics::IPainter* painter) { [child](platform::graphics::IPainter* p) { child->Draw(p); }); painter->PopLayer(); } + scroll_bar_delegate_->DrawScrollBar(painter); } Point ScrollRenderObject::GetScrollOffset() { @@ -141,4 +149,12 @@ void ScrollRenderObject::OnLayoutContent(const Rect& content_rect) { 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 |