From d86a71f79afe0e4dac768f61d6bff690567aca5b Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 24 May 2020 01:40:02 +0800 Subject: ... --- src/ui/CMakeLists.txt | 110 ++++---- src/ui/ClickDetector.cpp | 129 ++++++++++ src/ui/ContentControl.cpp | 33 +++ src/ui/LayoutControl.cpp | 53 ++++ src/ui/NoChildControl.cpp | 5 + src/ui/RoutedEventDispatch.hpp | 134 ++++++++++ src/ui/UiHost.cpp | 368 +++++++++++++++++++++++++++ src/ui/UiManager.cpp | 81 ++++++ src/ui/click_detector.cpp | 129 ---------- src/ui/content_control.cpp | 33 --- src/ui/control.cpp | 12 +- src/ui/controls/FlexLayout.cpp | 71 ++++++ src/ui/controls/StackLayout.cpp | 27 ++ src/ui/controls/TextBlock.cpp | 44 ++++ src/ui/controls/TextBox.cpp | 70 +++++ src/ui/controls/TextControlService.hpp | 227 +++++++++++++++++ src/ui/controls/button.cpp | 16 +- src/ui/controls/container.cpp | 6 +- src/ui/controls/flex_layout.cpp | 71 ------ src/ui/controls/stack_layout.cpp | 27 -- src/ui/controls/text_block.cpp | 44 ---- src/ui/controls/text_box.cpp | 70 ----- src/ui/controls/text_control_service.hpp | 227 ----------------- src/ui/helper.cpp | 6 +- src/ui/helper.hpp | 2 +- src/ui/layout_control.cpp | 53 ---- src/ui/no_child_control.cpp | 5 - src/ui/render/BorderRenderObject.cpp | 236 +++++++++++++++++ src/ui/render/CanvasRenderObject.cpp | 28 ++ src/ui/render/FlexLayoutRenderObject.cpp | 197 ++++++++++++++ src/ui/render/LayoutUtility.cpp | 15 ++ src/ui/render/RenderObject.cpp | 190 ++++++++++++++ src/ui/render/ScrollRenderObject.cpp | 1 + src/ui/render/StackLayoutRenderObject.cpp | 49 ++++ src/ui/render/TextRenderObject.cpp | 177 +++++++++++++ src/ui/render/WindowRenderObject.cpp | 45 ++++ src/ui/render/border_render_object.cpp | 236 ----------------- src/ui/render/canvas_render_object.cpp | 28 -- src/ui/render/flex_layout_render_object.cpp | 197 -------------- src/ui/render/layout_utility.cpp | 15 -- src/ui/render/render_object.cpp | 190 -------------- src/ui/render/scroll_render_object.cpp | 1 - src/ui/render/stack_layout_render_object.cpp | 49 ---- src/ui/render/text_render_object.cpp | 177 ------------- src/ui/render/window_render_object.cpp | 45 ---- src/ui/routed_event_dispatch.hpp | 134 ---------- src/ui/ui_host.cpp | 368 --------------------------- src/ui/ui_manager.cpp | 81 ------ src/ui/window.cpp | 6 +- 49 files changed, 2259 insertions(+), 2259 deletions(-) create mode 100644 src/ui/ClickDetector.cpp create mode 100644 src/ui/ContentControl.cpp create mode 100644 src/ui/LayoutControl.cpp create mode 100644 src/ui/NoChildControl.cpp create mode 100644 src/ui/RoutedEventDispatch.hpp create mode 100644 src/ui/UiHost.cpp create mode 100644 src/ui/UiManager.cpp delete mode 100644 src/ui/click_detector.cpp delete mode 100644 src/ui/content_control.cpp create mode 100644 src/ui/controls/FlexLayout.cpp create mode 100644 src/ui/controls/StackLayout.cpp create mode 100644 src/ui/controls/TextBlock.cpp create mode 100644 src/ui/controls/TextBox.cpp create mode 100644 src/ui/controls/TextControlService.hpp delete mode 100644 src/ui/controls/flex_layout.cpp delete mode 100644 src/ui/controls/stack_layout.cpp delete mode 100644 src/ui/controls/text_block.cpp delete mode 100644 src/ui/controls/text_box.cpp delete mode 100644 src/ui/controls/text_control_service.hpp delete mode 100644 src/ui/layout_control.cpp delete mode 100644 src/ui/no_child_control.cpp create mode 100644 src/ui/render/BorderRenderObject.cpp create mode 100644 src/ui/render/CanvasRenderObject.cpp create mode 100644 src/ui/render/FlexLayoutRenderObject.cpp create mode 100644 src/ui/render/LayoutUtility.cpp create mode 100644 src/ui/render/RenderObject.cpp create mode 100644 src/ui/render/ScrollRenderObject.cpp create mode 100644 src/ui/render/StackLayoutRenderObject.cpp create mode 100644 src/ui/render/TextRenderObject.cpp create mode 100644 src/ui/render/WindowRenderObject.cpp delete mode 100644 src/ui/render/border_render_object.cpp delete mode 100644 src/ui/render/canvas_render_object.cpp delete mode 100644 src/ui/render/flex_layout_render_object.cpp delete mode 100644 src/ui/render/layout_utility.cpp delete mode 100644 src/ui/render/render_object.cpp delete mode 100644 src/ui/render/scroll_render_object.cpp delete mode 100644 src/ui/render/stack_layout_render_object.cpp delete mode 100644 src/ui/render/text_render_object.cpp delete mode 100644 src/ui/render/window_render_object.cpp delete mode 100644 src/ui/routed_event_dispatch.hpp delete mode 100644 src/ui/ui_host.cpp delete mode 100644 src/ui/ui_manager.cpp (limited to 'src/ui') diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 777e4fbc..f37982e9 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -1,63 +1,63 @@ set(CRU_UI_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/ui) add_library(cru_ui STATIC - helper.hpp - routed_event_dispatch.hpp + Helper.hpp + RoutedEventDispatch.hpp - click_detector.cpp - content_control.cpp - control.cpp - helper.cpp - layout_control.cpp - no_child_control.cpp - ui_host.cpp - ui_manager.cpp - window.cpp - controls/button.cpp - controls/container.cpp - controls/flex_layout.cpp - controls/stack_layout.cpp - controls/text_block.cpp - controls/text_box.cpp - controls/text_control_service.hpp - render/border_render_object.cpp - render/canvas_render_object.cpp - render/flex_layout_render_object.cpp - render/layout_utility.cpp - render/render_object.cpp - render/scroll_render_object.cpp - render/stack_layout_render_object.cpp - render/text_render_object.cpp - render/window_render_object.cpp + ClickDetector.cpp + ContentControl.cpp + Control.cpp + Helper.cpp + LayoutControl.cpp + NoChildControl.cpp + UiHost.cpp + UiManager.cpp + Window.cpp + controls/Button.cpp + controls/Container.cpp + controls/FlexLayout.cpp + controls/StackLayout.cpp + controls/TextBlock.cpp + controls/TextBox.cpp + controls/TextControlService.hpp + render/BorderRenderObject.cpp + render/CanvasRenderObject.cpp + render/FlexLayoutRenderObject.cpp + render/LayoutUtility.cpp + render/RenderObject.cpp + render/ScrollRenderObject.cpp + render/StackLayoutRenderObject.cpp + render/TextRenderObject.cpp + render/WindowRenderObject.cpp ) target_sources(cru_ui PUBLIC - ${CRU_UI_INCLUDE_DIR}/base.hpp - ${CRU_UI_INCLUDE_DIR}/click_detector.hpp - ${CRU_UI_INCLUDE_DIR}/content_control.hpp - ${CRU_UI_INCLUDE_DIR}/control.hpp - ${CRU_UI_INCLUDE_DIR}/layout_control.hpp - ${CRU_UI_INCLUDE_DIR}/no_child_control.hpp - ${CRU_UI_INCLUDE_DIR}/ui_event.hpp - ${CRU_UI_INCLUDE_DIR}/ui_host.hpp - ${CRU_UI_INCLUDE_DIR}/ui_manager.hpp - ${CRU_UI_INCLUDE_DIR}/window.hpp - ${CRU_UI_INCLUDE_DIR}/controls/base.hpp - ${CRU_UI_INCLUDE_DIR}/controls/button.hpp - ${CRU_UI_INCLUDE_DIR}/controls/container.hpp - ${CRU_UI_INCLUDE_DIR}/controls/flex_layout.hpp - ${CRU_UI_INCLUDE_DIR}/controls/stack_layout.hpp - ${CRU_UI_INCLUDE_DIR}/controls/text_box.hpp - ${CRU_UI_INCLUDE_DIR}/controls/text_block.hpp - ${CRU_UI_INCLUDE_DIR}/render/base.hpp - ${CRU_UI_INCLUDE_DIR}/render/border_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/canvas_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/flex_layout_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/layout_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/layout_utility.hpp - ${CRU_UI_INCLUDE_DIR}/render/render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/scroll_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/stack_layout_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/text_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/window_render_object.hpp + ${CRU_UI_INCLUDE_DIR}/Base.hpp + ${CRU_UI_INCLUDE_DIR}/ClickDetector.hpp + ${CRU_UI_INCLUDE_DIR}/ContentControl.hpp + ${CRU_UI_INCLUDE_DIR}/Control.hpp + ${CRU_UI_INCLUDE_DIR}/LayoutControl.hpp + ${CRU_UI_INCLUDE_DIR}/NoChildControl.hpp + ${CRU_UI_INCLUDE_DIR}/UiEvent.hpp + ${CRU_UI_INCLUDE_DIR}/UiHost.hpp + ${CRU_UI_INCLUDE_DIR}/UiManager.hpp + ${CRU_UI_INCLUDE_DIR}/Window.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Base.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Button.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Container.hpp + ${CRU_UI_INCLUDE_DIR}/controls/FlexLayout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp + ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp + ${CRU_UI_INCLUDE_DIR}/render/Base.hpp + ${CRU_UI_INCLUDE_DIR}/render/BorderRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/CanvasRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/FlexLayoutRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/LayoutRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/LayoutUtility.hpp + ${CRU_UI_INCLUDE_DIR}/render/RenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/WindowRenderObject.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_native) diff --git a/src/ui/ClickDetector.cpp b/src/ui/ClickDetector.cpp new file mode 100644 index 00000000..e873efd4 --- /dev/null +++ b/src/ui/ClickDetector.cpp @@ -0,0 +1,129 @@ +#include "cru/ui/ClickDetector.hpp" + +#include "cru/common/Logger.hpp" + +#include + +namespace cru::ui { +ClickDetector::ClickDetector(Control* control) { + Expects(control); + control_ = control; + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseEnterEvent()->Direct()->AddHandler( + [this](event::MouseEventArgs&) { + if (this->enable_) { + if (this->state_ == ClickState::PressInactive) { + if ((this->button_ & this->trigger_button_)) { + this->SetState(ClickState::Press); + } + } else { + this->SetState(ClickState::Hover); + } + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseLeaveEvent()->Direct()->AddHandler( + [this](event::MouseEventArgs&) { + if (this->enable_) { + if (this->state_ == ClickState::Press) { + if ((this->button_ & this->trigger_button_)) { + this->SetState(ClickState::PressInactive); + } + } else { + this->SetState(ClickState::None); + } + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseDownEvent()->Direct()->AddHandler( + [this](event::MouseButtonEventArgs& args) { + const auto button = args.GetButton(); + if (this->enable_ && (button & this->trigger_button_) && + this->state_ == ClickState::Hover) { + if (!this->control_->CaptureMouse()) { + log::Debug("Failed to capture mouse when begin click."); + return; + } + this->down_point_ = args.GetPoint(); + this->button_ = button; + this->SetState(ClickState::Press); + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseUpEvent()->Direct()->AddHandler( + [this](event::MouseButtonEventArgs& args) { + const auto button = args.GetButton(); + if (this->enable_ && (button & this->trigger_button_) && + button == button_) { + if (this->state_ == ClickState::Press) { + this->SetState(ClickState::Hover); + this->event_.Raise(ClickEventArgs{this->control_, + this->down_point_, + args.GetPoint(), button}); + this->control_->ReleaseMouse(); + } else if (this->state_ == ClickState::PressInactive) { + this->SetState(ClickState::None); + this->control_->ReleaseMouse(); + } + } + }))); +} // namespace cru::ui + +void ClickDetector::SetEnabled(bool enable) { + if (enable == enable_) { + return; + } + + enable_ = enable; + if (enable) { + SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); + } else { + if (state_ == ClickState::Press || state_ == ClickState::PressInactive) { + SetState(ClickState::None); + control_->ReleaseMouse(); + } else if (state_ == ClickState::Hover) { + SetState(ClickState::None); + } + } +} + +void ClickDetector::SetTriggerButton(MouseButton trigger_button) { + if (trigger_button == trigger_button_) { + return; + } + + trigger_button_ = trigger_button; + if ((state_ == ClickState::Press || state_ == ClickState::PressInactive) && + !(button_ & trigger_button)) { + SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); + control_->ReleaseMouse(); + } +} + +void ClickDetector::SetState(ClickState state) { +#ifdef CRU_DEBUG + auto to_string = [](ClickState state) -> std::string_view { + switch (state) { + case ClickState::None: + return "None"; + case ClickState::Hover: + return "Hover"; + case ClickState::Press: + return "Press"; + case ClickState::PressInactive: + return "PressInvactive"; + default: + UnreachableCode(); + } + }; + log::Debug("Click state changed, new state: {}.", to_string(state)); +#endif + + state_ = state; + state_change_event_.Raise(state); +} +} // namespace cru::ui diff --git a/src/ui/ContentControl.cpp b/src/ui/ContentControl.cpp new file mode 100644 index 00000000..8d1a17d2 --- /dev/null +++ b/src/ui/ContentControl.cpp @@ -0,0 +1,33 @@ +#include "cru/ui/ContentControl.hpp" + +#include "cru/ui/Window.hpp" + +namespace cru::ui { +ContentControl::ContentControl() + : child_vector_{nullptr}, child_(child_vector_[0]) {} + +ContentControl::~ContentControl() { delete child_; } + +void ContentControl::SetChild(Control* child) { + Expects(!dynamic_cast(child)); // Can't add a window as child. + if (child == child_) return; + + const auto host = GetUiHost(); + const auto old_child = child_; + child_ = child; + if (old_child) { + old_child->_SetParent(nullptr); + old_child->_SetDescendantUiHost(nullptr); + } + if (child) { + child->_SetParent(this); + child->_SetDescendantUiHost(host); + } + OnChildChanged(old_child, child); +} + +void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { + CRU_UNUSED(old_child) + CRU_UNUSED(new_child) +} +} // namespace cru::ui diff --git a/src/ui/LayoutControl.cpp b/src/ui/LayoutControl.cpp new file mode 100644 index 00000000..4813566b --- /dev/null +++ b/src/ui/LayoutControl.cpp @@ -0,0 +1,53 @@ +#include "cru/ui/LayoutControl.hpp" + +#include "cru/ui/Window.hpp" + +namespace cru::ui { +LayoutControl::~LayoutControl() { + for (const auto child : children_) delete child; +} + +void LayoutControl::AddChild(Control* control, const Index position) { + Expects(control->GetParent() == + nullptr); // The control already has a parent. + Expects(!dynamic_cast(control)); // Can't add a window as child. + Expects(position >= 0); + Expects(position <= + static_cast( + this->children_.size())); // The position is out of range. + + children_.insert(this->children_.cbegin() + position, control); + + control->_SetParent(this); + control->_SetDescendantUiHost(GetUiHost()); + + OnAddChild(control, position); +} + +void LayoutControl::RemoveChild(const Index position) { + Expects(position >= 0); + Expects(position < + static_cast( + this->children_.size())); // The position is out of range. + + const auto i = children_.cbegin() + position; + const auto child = *i; + + children_.erase(i); + + child->_SetParent(nullptr); + child->_SetDescendantUiHost(nullptr); + + OnRemoveChild(child, position); +} + +void LayoutControl::OnAddChild(Control* child, const Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} + +void LayoutControl::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} +} // namespace cru::ui diff --git a/src/ui/NoChildControl.cpp b/src/ui/NoChildControl.cpp new file mode 100644 index 00000000..86861049 --- /dev/null +++ b/src/ui/NoChildControl.cpp @@ -0,0 +1,5 @@ +#include "cru/ui/NoChildControl.hpp" + +namespace cru::ui { +const std::vector NoChildControl::empty_control_vector{}; +} diff --git a/src/ui/RoutedEventDispatch.hpp b/src/ui/RoutedEventDispatch.hpp new file mode 100644 index 00000000..5ff21a74 --- /dev/null +++ b/src/ui/RoutedEventDispatch.hpp @@ -0,0 +1,134 @@ +#pragma once +#include "cru/ui/Control.hpp" + +#include "cru/common/Logger.hpp" + +#include + +namespace cru::ui { +// Dispatch the event. +// +// This will raise routed event of the control and its parent and parent's +// parent ... (until "last_receiver" if it's not nullptr) with appropriate args. +// +// First tunnel from top to bottom possibly stopped by "handled" flag in +// EventArgs. Second bubble from bottom to top possibly stopped by "handled" +// flag in EventArgs. Last direct to each control. +// +// Args is of type "EventArgs". The first init argument is "sender", which is +// automatically bound to each receiving control. The second init argument is +// "original_sender", which is unchanged. And "args" will be perfectly forwarded +// as the rest arguments. +template +void DispatchEvent(const std::string_view& event_name, + Control* const original_sender, + event::RoutedEvent* (Control::*event_ptr)(), + Control* const last_receiver, Args&&... args) { +#ifndef CRU_DEBUG + CRU_UNUSED(event_name) +#endif + +#ifdef CRU_DEBUG + bool do_log = true; + if (event_name == "MouseMove") do_log = false; +#endif + + if (original_sender == last_receiver) { + /* + #ifdef CRU_DEBUG + if (do_log) + log::Debug( + "Routed event {} no need to dispatch (original_sender == " + "last_receiver). Original sender is {}.", + event_name, original_sender->GetControlType()); + #endif + */ + return; + } + + std::list receive_list; + + auto parent = original_sender; + while (parent != last_receiver) { + receive_list.push_back(parent); + parent = parent->GetParent(); + } + +#ifdef CRU_DEBUG + if (do_log) { + std::string log = "Dispatch routed event "; + log += event_name; + log += ". Path (parent first): "; + auto i = receive_list.crbegin(); + const auto end = --receive_list.crend(); + for (; i != end; ++i) { + log += (*i)->GetControlType(); + log += " -> "; + } + log += (*i)->GetControlType(); + log::Debug(log); + } +#endif + + auto handled = false; + +#ifdef CRU_DEBUG + int count = 0; +#endif + + // tunnel + for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { +#ifdef CRU_DEBUG + count++; +#endif + EventArgs event_args(*i, original_sender, std::forward(args)...); + static_cast*>(((*i)->*event_ptr)()->Tunnel()) + ->Raise(event_args); + if (event_args.IsHandled()) { + handled = true; +#ifdef CRU_DEBUG + if (do_log) + log::Debug( + "Routed event is short-circuit in TUNNEL at {}-st control (count " + "from parent).", + count); +#endif + break; + } + } + + // bubble + if (!handled) { + for (auto i : receive_list) { +#ifdef CRU_DEBUG + count--; +#endif + EventArgs event_args(i, original_sender, std::forward(args)...); + static_cast*>((i->*event_ptr)()->Bubble()) + ->Raise(event_args); + if (event_args.IsHandled()) { +#ifdef CRU_DEBUG + if (do_log) + log::Debug( + "Routed event is short-circuit in BUBBLE at {}-st control " + "(count " + "from parent).", + count); +#endif + break; + } + } + } + + // direct + for (auto i : receive_list) { + EventArgs event_args(i, original_sender, std::forward(args)...); + static_cast*>((i->*event_ptr)()->Direct()) + ->Raise(event_args); + } + +#ifdef CRU_DEBUG + if (do_log) log::Debug("Routed event dispatch finished."); +#endif +} +} // namespace cru::ui diff --git a/src/ui/UiHost.cpp b/src/ui/UiHost.cpp new file mode 100644 index 00000000..069e68de --- /dev/null +++ b/src/ui/UiHost.cpp @@ -0,0 +1,368 @@ +#include "cru/ui/UiHost.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/platform/native/Window.hpp" +#include "cru/ui/render/WindowRenderObject.hpp" +#include "cru/ui/Window.hpp" +#include "RoutedEventDispatch.hpp" + +namespace cru::ui { +using platform::native::INativeWindow; +using platform::native::IUiApplication; + +namespace event_names { +#ifdef CRU_DEBUG +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = #name; +#else +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = ""; +#endif + +CRU_DEFINE_EVENT_NAME(LoseFocus) +CRU_DEFINE_EVENT_NAME(GainFocus) +CRU_DEFINE_EVENT_NAME(MouseEnter) +CRU_DEFINE_EVENT_NAME(MouseLeave) +CRU_DEFINE_EVENT_NAME(MouseMove) +CRU_DEFINE_EVENT_NAME(MouseDown) +CRU_DEFINE_EVENT_NAME(MouseUp) +CRU_DEFINE_EVENT_NAME(KeyDown) +CRU_DEFINE_EVENT_NAME(KeyUp) +CRU_DEFINE_EVENT_NAME(Char) + +#undef CRU_DEFINE_EVENT_NAME +} // namespace event_names + +namespace { +bool IsAncestor(Control* control, Control* ancestor) { + while (control != nullptr) { + if (control == ancestor) return true; + control = control->GetParent(); + } + return false; +} + +std::list GetAncestorList(Control* control) { + std::list l; + while (control != nullptr) { + l.push_front(control); + control = control->GetParent(); + } + return l; +} + +Control* FindLowestCommonAncestor(Control* left, Control* right) { + if (left == nullptr || right == nullptr) return nullptr; + + auto&& left_list = GetAncestorList(left); + auto&& right_list = GetAncestorList(right); + + // the root is different + if (left_list.front() != right_list.front()) return nullptr; + + // find the last same control or the last control (one is ancestor of the + // other) + auto left_i = left_list.cbegin(); + auto right_i = right_list.cbegin(); + + while (true) { + if (left_i == left_list.cend()) { + return *(--left_i); + } + if (right_i == right_list.cend()) { + return *(--right_i); + } + if (*left_i != *right_i) { + return *(--left_i); + } + ++left_i; + ++right_i; + } +} +} // namespace + +namespace { +template +inline void BindNativeEvent( + UiHost* host, INativeWindow* native_window, IEvent* event, + void (UiHost::*handler)(INativeWindow*, typename IEvent::EventArgs), + std::vector& guard_pool) { + guard_pool.push_back(EventRevokerGuard(event->AddHandler( + std::bind(handler, host, native_window, std::placeholders::_1)))); +} +} // namespace + +UiHost::UiHost(Window* window) + : mouse_hover_control_(nullptr), + focus_control_(window), + mouse_captured_control_(nullptr), + window_control_(window) { + native_window_resolver_ = + IUiApplication::GetInstance()->CreateWindow(nullptr); + + const auto native_window = native_window_resolver_->Resolve(); + window->ui_host_ = this; + + root_render_object_ = std::make_unique(this); + root_render_object_->SetAttachedControl(window); + window->render_object_ = root_render_object_.get(); + + BindNativeEvent(this, native_window, native_window->DestroyEvent(), + &UiHost::OnNativeDestroy, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->PaintEvent(), + &UiHost::OnNativePaint, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->ResizeEvent(), + &UiHost::OnNativeResize, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->FocusEvent(), + &UiHost::OnNativeFocus, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), + &UiHost::OnNativeMouseEnterLeave, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), + &UiHost::OnNativeMouseMove, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseDownEvent(), + &UiHost::OnNativeMouseDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseUpEvent(), + &UiHost::OnNativeMouseUp, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyDownEvent(), + &UiHost::OnNativeKeyDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyUpEvent(), + &UiHost::OnNativeKeyUp, event_revoker_guards_); +} + +UiHost::~UiHost() { + deleting_ = true; + window_control_->TraverseDescendants( + [this](Control* control) { control->OnDetachFromHost(this); }); + if (!native_window_destroyed_) { + const auto native_window = native_window_resolver_->Resolve(); + if (native_window) { + native_window->Close(); + } + } +} + +void UiHost::InvalidatePaint() { + if (const auto native_window = native_window_resolver_->Resolve()) + native_window->RequestRepaint(); +} + +void UiHost::InvalidateLayout() { + if (!need_layout_) { + platform::native::IUiApplication::GetInstance()->InvokeLater( + [resolver = this->CreateResolver()] { + if (const auto host = resolver.Resolve()) { + host->Relayout(); + host->need_layout_ = false; + host->after_layout_event_.Raise(AfterLayoutEventArgs{}); + log::Debug("A relayout finished."); + host->InvalidatePaint(); + } + }); + need_layout_ = true; + } +} + +void UiHost::Relayout() { + const auto native_window = native_window_resolver_->Resolve(); + const auto client_size = native_window + ? native_window->GetClientSize() + : Size{100, 100}; // a reasonable assumed size + root_render_object_->Measure(client_size); + root_render_object_->Layout(Rect{Point{}, client_size}); +} + +bool UiHost::RequestFocusFor(Control* control) { + Expects(control != nullptr); // The control to request focus can't be null. + // You can set it as the window. + + if (focus_control_ == control) return true; + + DispatchEvent(event_names::LoseFocus, focus_control_, + &Control::LoseFocusEvent, nullptr, false); + + focus_control_ = control; + + DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, + nullptr, false); + + return true; +} + +Control* UiHost::GetFocusControl() { return focus_control_; } + +bool UiHost::CaptureMouseFor(Control* control) { + const auto native_window = native_window_resolver_->Resolve(); + if (!native_window) return false; + + if (control == mouse_captured_control_) return true; + + if (control == nullptr) { + const auto old_capture_control = mouse_captured_control_; + mouse_captured_control_ = + nullptr; // update this in case this is used in event handlers + if (old_capture_control != mouse_hover_control_) { + DispatchMouseHoverControlChangeEvent( + old_capture_control, mouse_hover_control_, + native_window->GetMousePosition(), true, false); + } + UpdateCursor(); + return true; + } + + if (mouse_captured_control_) return false; + + mouse_captured_control_ = control; + DispatchMouseHoverControlChangeEvent( + mouse_hover_control_, mouse_captured_control_, + native_window->GetMousePosition(), false, true); + UpdateCursor(); + return true; +} + +Control* UiHost::GetMouseCaptureControl() { return mouse_captured_control_; } + +void UiHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { + CRU_UNUSED(window) + native_window_destroyed_ = true; + if (!deleting_ && !retain_after_destroy_) delete window_control_; +} + +void UiHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { + auto painter = window->BeginPaint(); + root_render_object_->Draw(painter.get()); + painter->EndDraw(); +} + +void UiHost::OnNativeResize(INativeWindow* window, const Size& size) { + CRU_UNUSED(window) + CRU_UNUSED(size) + + InvalidateLayout(); +} + +void UiHost::OnNativeFocus(INativeWindow* window, + platform::native::FocusChangeType focus) { + CRU_UNUSED(window) + + focus == platform::native::FocusChangeType::Gain + ? DispatchEvent(event_names::GainFocus, focus_control_, + &Control::GainFocusEvent, nullptr, true) + : DispatchEvent(event_names::LoseFocus, focus_control_, + &Control::LoseFocusEvent, nullptr, true); +} + +void UiHost::OnNativeMouseEnterLeave( + INativeWindow* window, platform::native::MouseEnterLeaveType type) { + CRU_UNUSED(window) + + if (type == platform::native::MouseEnterLeaveType::Leave) { + DispatchEvent(event_names::MouseLeave, mouse_hover_control_, + &Control::MouseLeaveEvent, nullptr); + mouse_hover_control_ = nullptr; + } +} + +void UiHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { + CRU_UNUSED(window) + + // Find the first control that hit test succeed. + const auto new_mouse_hover_control = HitTest(point); + const auto old_mouse_hover_control = mouse_hover_control_; + mouse_hover_control_ = new_mouse_hover_control; + + if (mouse_captured_control_) { + const auto n = FindLowestCommonAncestor(new_mouse_hover_control, + mouse_captured_control_); + const auto o = FindLowestCommonAncestor(old_mouse_hover_control, + mouse_captured_control_); + bool a = IsAncestor(o, n); + if (a) { + DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); + } else { + DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, + point); + } + DispatchEvent(event_names::MouseMove, mouse_captured_control_, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); + return; + } + + DispatchMouseHoverControlChangeEvent( + old_mouse_hover_control, new_mouse_hover_control, point, false, false); + DispatchEvent(event_names::MouseMove, new_mouse_hover_control, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); +} + +void UiHost::OnNativeMouseDown( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, + nullptr, args.point, args.button, args.modifier); +} + +void UiHost::OnNativeMouseUp( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, + args.point, args.button, args.modifier); +} + +void UiHost::OnNativeKeyDown(INativeWindow* window, + const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, + nullptr, args.key, args.modifier); +} + +void UiHost::OnNativeKeyUp(INativeWindow* window, + const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, + nullptr, args.key, args.modifier); +} + +void UiHost::DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point, + bool no_leave, + bool no_enter) { + if (new_control != old_control) // if the mouse-hover-on control changed + { + const auto lowest_common_ancestor = + FindLowestCommonAncestor(old_control, new_control); + if (!no_leave && old_control != nullptr) + DispatchEvent(event_names::MouseLeave, old_control, + &Control::MouseLeaveEvent, + lowest_common_ancestor); // dispatch mouse leave event. + if (!no_enter && new_control != nullptr) { + DispatchEvent(event_names::MouseEnter, new_control, + &Control::MouseEnterEvent, lowest_common_ancestor, + point); // dispatch mouse enter event. + } + } +} + +void UiHost::UpdateCursor() { + if (const auto native_window = native_window_resolver_->Resolve()) { + const auto capture = GetMouseCaptureControl(); + native_window->SetCursor( + (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); + } +} + +Control* UiHost::HitTest(const Point& point) { + return root_render_object_->HitTest(point)->GetAttachedControl(); +} +} // namespace cru::ui diff --git a/src/ui/UiManager.cpp b/src/ui/UiManager.cpp new file mode 100644 index 00000000..b8effdfd --- /dev/null +++ b/src/ui/UiManager.cpp @@ -0,0 +1,81 @@ +#include "cru/ui/UiManager.hpp" + +#include "cru/platform/graph/Brush.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/graph/Font.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "Helper.hpp" + +namespace cru::ui { +using namespace cru::platform::graph; + +namespace { +std::unique_ptr CreateSolidColorBrush(IGraphFactory* factory, + const Color& color) { + auto brush = factory->CreateSolidColorBrush(); + brush->SetColor(color); + return brush; +} +} // namespace + +UiManager* UiManager::GetInstance() { + static UiManager* instance = new UiManager(); + GetUiApplication()->AddOnQuitHandler([] { + delete instance; + instance = nullptr; + }); + return instance; +} + +UiManager::UiManager() { + const auto factory = GetGraphFactory(); + + theme_resource_.default_font = factory->CreateFont("等线", 24.0f); + + const auto black_brush = std::shared_ptr( + CreateSolidColorBrush(factory, colors::black)); + theme_resource_.text_brush = black_brush; + theme_resource_.text_selection_brush = + CreateSolidColorBrush(factory, colors::skyblue); + theme_resource_.caret_brush = black_brush; + + theme_resource_.button_style.normal.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)); + theme_resource_.button_style.hover.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)); + theme_resource_.button_style.press.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); + theme_resource_.button_style.press_cancel.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); + + theme_resource_.button_style.normal.border_thickness = + theme_resource_.button_style.hover.border_thickness = + theme_resource_.button_style.press.border_thickness = + theme_resource_.button_style.press_cancel.border_thickness = + Thickness(3); + + theme_resource_.button_style.normal.border_radius = + theme_resource_.button_style.hover.border_radius = + theme_resource_.button_style.press.border_radius = + theme_resource_.button_style.press_cancel.border_radius = + CornerRadius({5, 5}); + + theme_resource_.text_box_border_style.normal.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0xced4da)); + theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); + theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); + + theme_resource_.text_box_border_style.hover = + theme_resource_.text_box_border_style.normal; + + theme_resource_.text_box_border_style.normal.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x495057)); + theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); + theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); + + theme_resource_.text_box_border_style.focus_hover = + theme_resource_.text_box_border_style.focus; +} + +UiManager::~UiManager() = default; +} // namespace cru::ui diff --git a/src/ui/click_detector.cpp b/src/ui/click_detector.cpp deleted file mode 100644 index 3f342a31..00000000 --- a/src/ui/click_detector.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "cru/ui/click_detector.hpp" - -#include "cru/common/logger.hpp" - -#include - -namespace cru::ui { -ClickDetector::ClickDetector(Control* control) { - Expects(control); - control_ = control; - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseEnterEvent()->Direct()->AddHandler( - [this](event::MouseEventArgs&) { - if (this->enable_) { - if (this->state_ == ClickState::PressInactive) { - if ((this->button_ & this->trigger_button_)) { - this->SetState(ClickState::Press); - } - } else { - this->SetState(ClickState::Hover); - } - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseLeaveEvent()->Direct()->AddHandler( - [this](event::MouseEventArgs&) { - if (this->enable_) { - if (this->state_ == ClickState::Press) { - if ((this->button_ & this->trigger_button_)) { - this->SetState(ClickState::PressInactive); - } - } else { - this->SetState(ClickState::None); - } - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseDownEvent()->Direct()->AddHandler( - [this](event::MouseButtonEventArgs& args) { - const auto button = args.GetButton(); - if (this->enable_ && (button & this->trigger_button_) && - this->state_ == ClickState::Hover) { - if (!this->control_->CaptureMouse()) { - log::Debug("Failed to capture mouse when begin click."); - return; - } - this->down_point_ = args.GetPoint(); - this->button_ = button; - this->SetState(ClickState::Press); - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseUpEvent()->Direct()->AddHandler( - [this](event::MouseButtonEventArgs& args) { - const auto button = args.GetButton(); - if (this->enable_ && (button & this->trigger_button_) && - button == button_) { - if (this->state_ == ClickState::Press) { - this->SetState(ClickState::Hover); - this->event_.Raise(ClickEventArgs{this->control_, - this->down_point_, - args.GetPoint(), button}); - this->control_->ReleaseMouse(); - } else if (this->state_ == ClickState::PressInactive) { - this->SetState(ClickState::None); - this->control_->ReleaseMouse(); - } - } - }))); -} // namespace cru::ui - -void ClickDetector::SetEnabled(bool enable) { - if (enable == enable_) { - return; - } - - enable_ = enable; - if (enable) { - SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); - } else { - if (state_ == ClickState::Press || state_ == ClickState::PressInactive) { - SetState(ClickState::None); - control_->ReleaseMouse(); - } else if (state_ == ClickState::Hover) { - SetState(ClickState::None); - } - } -} - -void ClickDetector::SetTriggerButton(MouseButton trigger_button) { - if (trigger_button == trigger_button_) { - return; - } - - trigger_button_ = trigger_button; - if ((state_ == ClickState::Press || state_ == ClickState::PressInactive) && - !(button_ & trigger_button)) { - SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); - control_->ReleaseMouse(); - } -} - -void ClickDetector::SetState(ClickState state) { -#ifdef CRU_DEBUG - auto to_string = [](ClickState state) -> std::string_view { - switch (state) { - case ClickState::None: - return "None"; - case ClickState::Hover: - return "Hover"; - case ClickState::Press: - return "Press"; - case ClickState::PressInactive: - return "PressInvactive"; - default: - UnreachableCode(); - } - }; - log::Debug("Click state changed, new state: {}.", to_string(state)); -#endif - - state_ = state; - state_change_event_.Raise(state); -} -} // namespace cru::ui diff --git a/src/ui/content_control.cpp b/src/ui/content_control.cpp deleted file mode 100644 index eb13f4cb..00000000 --- a/src/ui/content_control.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "cru/ui/content_control.hpp" - -#include "cru/ui/window.hpp" - -namespace cru::ui { -ContentControl::ContentControl() - : child_vector_{nullptr}, child_(child_vector_[0]) {} - -ContentControl::~ContentControl() { delete child_; } - -void ContentControl::SetChild(Control* child) { - Expects(!dynamic_cast(child)); // Can't add a window as child. - if (child == child_) return; - - const auto host = GetUiHost(); - const auto old_child = child_; - child_ = child; - if (old_child) { - old_child->_SetParent(nullptr); - old_child->_SetDescendantUiHost(nullptr); - } - if (child) { - child->_SetParent(this); - child->_SetDescendantUiHost(host); - } - OnChildChanged(old_child, child); -} - -void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { - CRU_UNUSED(old_child) - CRU_UNUSED(new_child) -} -} // namespace cru::ui diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 5417f4ce..cd1367fe 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -1,10 +1,10 @@ -#include "cru/ui/control.hpp" +#include "cru/ui/Control.hpp" -#include "cru/platform/native/cursor.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/base.hpp" -#include "cru/ui/ui_host.hpp" -#include "routed_event_dispatch.hpp" +#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/UiHost.hpp" +#include "RoutedEventDispatch.hpp" #include diff --git a/src/ui/controls/FlexLayout.cpp b/src/ui/controls/FlexLayout.cpp new file mode 100644 index 00000000..25f30558 --- /dev/null +++ b/src/ui/controls/FlexLayout.cpp @@ -0,0 +1,71 @@ +#include "cru/ui/controls/FlexLayout.hpp" + +#include "cru/ui/render/FlexLayoutRenderObject.hpp" + +namespace cru::ui::controls { +using render::FlexLayoutRenderObject; + +FlexLayout::FlexLayout() { + render_object_.reset(new FlexLayoutRenderObject()); + render_object_->SetAttachedControl(this); +} + +FlexLayout::~FlexLayout() = default; + +render::RenderObject* FlexLayout::GetRenderObject() const { + return render_object_.get(); +} + +namespace { +int FindPosition(render::RenderObject* parent, render::RenderObject* child) { + const auto& render_objects = parent->GetChildren(); + const auto find_result = + std::find(render_objects.cbegin(), render_objects.cend(), child); + if (find_result == render_objects.cend()) { + throw std::logic_error("Control is not a child of FlexLayout."); + } + return static_cast(find_result - render_objects.cbegin()); +} +} // namespace + +FlexChildLayoutData FlexLayout::GetChildLayoutData(Control* control) { + Expects(control); + return *render_object_->GetChildLayoutData( + FindPosition(render_object_.get(), control->GetRenderObject())); +} + +void FlexLayout::SetChildLayoutData(Control* control, + const FlexChildLayoutData& data) { + Expects(control); + *render_object_->GetChildLayoutData( + FindPosition(render_object_.get(), control->GetRenderObject())) = data; +} + +FlexMainAlignment FlexLayout::GetContentMainAlign() const { + return render_object_->GetContentMainAlign(); +} + +void FlexLayout::SetContentMainAlign(FlexMainAlignment value) { + if (value == GetContentMainAlign()) return; + render_object_->SetContentMainAlign(value); +} + +FlexDirection FlexLayout::GetFlexDirection() const { + return render_object_->GetFlexDirection(); +} + +void FlexLayout::SetFlexDirection(FlexDirection direction) { + if (direction == GetFlexDirection()) return; + render_object_->SetFlexDirection(direction); +} + +void FlexLayout::OnAddChild(Control* child, const Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void FlexLayout::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/StackLayout.cpp b/src/ui/controls/StackLayout.cpp new file mode 100644 index 00000000..ce500b79 --- /dev/null +++ b/src/ui/controls/StackLayout.cpp @@ -0,0 +1,27 @@ +#include "cru/ui/controls/StackLayout.hpp" + +#include "cru/ui/render/StackLayoutRenderObject.hpp" + +namespace cru::ui::controls { +using render::StackLayoutRenderObject; + +StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { + render_object_->SetAttachedControl(this); +} + +StackLayout::~StackLayout() = default; + +render::RenderObject* StackLayout::GetRenderObject() const { + return render_object_.get(); +} + +void StackLayout::OnAddChild(Control* child, const Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void StackLayout::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextBlock.cpp b/src/ui/controls/TextBlock.cpp new file mode 100644 index 00000000..f77e279b --- /dev/null +++ b/src/ui/controls/TextBlock.cpp @@ -0,0 +1,44 @@ +#include "cru/ui/controls/TextBlock.hpp" + +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "TextControlService.hpp" + +namespace cru::ui::controls { +using render::CanvasRenderObject; +using render::StackLayoutRenderObject; +using render::TextRenderObject; + +TextBlock::TextBlock() { + const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); + + text_render_object_ = std::make_unique( + theme_resources->text_brush, theme_resources->default_font, + theme_resources->text_selection_brush, theme_resources->caret_brush); + + text_render_object_->SetAttachedControl(this); + + service_ = std::make_unique>(this); + service_->SetEnabled(true); +} + +TextBlock::~TextBlock() = default; + +render::RenderObject* TextBlock::GetRenderObject() const { + return text_render_object_.get(); +} + +std::string TextBlock::GetText() const { + return text_render_object_->GetText(); +} + +void TextBlock::SetText(std::string text) { + text_render_object_->SetText(std::move(text)); +} + +render::TextRenderObject* TextBlock::GetTextRenderObject() { + return text_render_object_.get(); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp new file mode 100644 index 00000000..64fd4c60 --- /dev/null +++ b/src/ui/controls/TextBox.cpp @@ -0,0 +1,70 @@ +#include "cru/ui/controls/TextBox.hpp" + +#include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "TextControlService.hpp" + +namespace cru::ui::controls { +using render::BorderRenderObject; +using render::CanvasRenderObject; +using render::StackLayoutRenderObject; +using render::TextRenderObject; + +TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { + const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); + + border_style_ = theme_resources->text_box_border_style; + + text_render_object_ = std::make_unique( + theme_resources->text_brush, theme_resources->default_font, + theme_resources->text_selection_brush, theme_resources->caret_brush); + + border_render_object_->AddChild(text_render_object_.get(), 0); + + border_render_object_->SetAttachedControl(this); + text_render_object_->SetAttachedControl(this); + + service_ = std::make_unique>(this); + service_->SetEnabled(true); + service_->SetCaretVisible(true); + + GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { + this->service_->SetEnabled(true); + this->UpdateBorderStyle(); + }); + + LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { + this->service_->SetEnabled(false); + this->UpdateBorderStyle(); + }); +} + +TextBox::~TextBox() {} + +render::RenderObject* TextBox::GetRenderObject() const { + return border_render_object_.get(); +} + +render::TextRenderObject* TextBox::GetTextRenderObject() { + return text_render_object_.get(); +} + +const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } + +void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { + border_style_ = std::move(border_style); +} + +void TextBox::OnMouseHoverChange(bool) { UpdateBorderStyle(); } + +void TextBox::UpdateBorderStyle() { + const auto focus = HasFocus(); + const auto hover = IsMouseOver(); + border_render_object_->SetBorderStyle( + focus ? (hover ? border_style_.focus_hover : border_style_.focus) + : (hover ? border_style_.hover : border_style_.normal)); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp new file mode 100644 index 00000000..ad0db343 --- /dev/null +++ b/src/ui/controls/TextControlService.hpp @@ -0,0 +1,227 @@ +#pragma once +#include "../Helper.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/graph/Font.hpp" +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/Control.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiEvent.hpp" + +namespace cru::ui::controls { +constexpr int k_default_caret_blink_duration = 500; + +// TControl should inherits `Control` and has following methods: +// ``` +// render::TextRenderObject* GetTextRenderObject(); +// ``` +template +class TextControlService : public Object { + public: + TextControlService(TControl* control); + + CRU_DELETE_COPY(TextControlService) + CRU_DELETE_MOVE(TextControlService) + + ~TextControlService(); + + public: + bool IsEnabled() { return enable_; } + void SetEnabled(bool enable); + + bool IsCaretVisible() { return caret_visible_; } + void SetCaretVisible(bool visible); + + int GetCaretBlinkDuration() { return caret_blink_duration_; } + void SetCaretBlinkDuration(int milliseconds); + + private: + void AbortSelection(); + + void SetupCaret(); + void TearDownCaret(); + + void SetupHandlers(); + + void MouseMoveHandler(event::MouseEventArgs& args); + void MouseDownHandler(event::MouseButtonEventArgs& args); + void MouseUpHandler(event::MouseButtonEventArgs& args); + void LoseFocusHandler(event::FocusChangeEventArgs& args); + + private: + TControl* control_; + std::vector event_revoker_guards_; + + bool enable_ = false; + + bool caret_visible_ = false; + long long caret_timer_id_ = -1; + int caret_blink_duration_ = k_default_caret_blink_duration; + + // nullopt means not selecting + std::optional select_down_button_; + + // before the char + int select_start_position_; +}; + +template +TextControlService::TextControlService(TControl* control) + : control_(control) {} + +template +TextControlService::~TextControlService() { + const auto application = GetUiApplication(); + // Don't call TearDownCaret, because it use text render object of control, + // which may be destroyed already. + application->CancelTimer(this->caret_timer_id_); +} + +template +void TextControlService::SetEnabled(bool enable) { + if (enable == this->enable_) return; + if (enable) { + this->SetupHandlers(); + if (this->caret_visible_) { + this->SetupCaret(); + } + } else { + this->AbortSelection(); + this->event_revoker_guards_.clear(); + this->TearDownCaret(); + } +} + +template +void TextControlService::SetCaretVisible(bool visible) { + if (visible == this->caret_visible_) return; + + this->caret_visible_ = visible; + + if (this->enable_) { + if (visible) { + this->SetupCaret(); + } else { + this->TearDownCaret(); + } + } +} + +template +void TextControlService::SetCaretBlinkDuration(int milliseconds) { + if (this->caret_blink_duration_ == milliseconds) return; + + if (this->enable_ && this->caret_visible_) { + this->TearDownCaret(); + this->SetupCaret(); + } +} + +template +void TextControlService::AbortSelection() { + if (this->select_down_button_.has_value()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; + } + this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); +} + +template +void TextControlService::SetupCaret() { + const auto application = GetUiApplication(); + + // Cancel first anyhow for safety. + application->CancelTimer(this->caret_timer_id_); + + this->control_->GetTextRenderObject()->SetDrawCaret(true); + this->caret_timer_id_ = application->SetInterval( + std::chrono::milliseconds(this->caret_blink_duration_), + [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); +} + +template +void TextControlService::TearDownCaret() { + const auto application = GetUiApplication(); + application->CancelTimer(this->caret_timer_id_); + this->control_->GetTextRenderObject()->SetDrawCaret(false); +} + +template +void TextControlService::SetupHandlers() { + Expects(event_revoker_guards_.empty()); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseMoveHandler, this, + std::placeholders::_1))}); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseDownHandler, this, + std::placeholders::_1))}); + this->event_revoker_guards_.push_back(EventRevokerGuard{ + control_->MouseUpEvent()->Direct()->AddHandler(std::bind( + &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->LoseFocusEvent()->Direct()->AddHandler( + std::bind(&TextControlService::LoseFocusHandler, this, + std::placeholders::_1))}); +} + +template +void TextControlService::MouseMoveHandler( + event::MouseEventArgs& args) { + if (this->select_down_button_.has_value()) { + const auto text_render_object = this->control_->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + text_render_object->FromRootToContent(args.GetPoint())); + const auto position = result.position + (result.trailing ? 1 : 0); + log::Debug( + "TextControlService: Text selection changed on mouse move, range: {}, " + "{}.", + position, this->select_start_position_); + this->control_->GetTextRenderObject()->SetSelectionRange( + TextRange::FromTwoSides( + static_cast(position), + static_cast(this->select_start_position_))); + text_render_object->SetCaretPosition(position); + } +} + +template +void TextControlService::MouseDownHandler( + event::MouseButtonEventArgs& args) { + if (this->select_down_button_.has_value()) { + return; + } else { + if (!this->control_->CaptureMouse()) return; + if (!this->control_->RequestFocus()) return; + const auto text_render_object = this->control_->GetTextRenderObject(); + this->select_down_button_ = args.GetButton(); + const auto result = text_render_object->TextHitTest( + text_render_object->FromRootToContent(args.GetPoint())); + const auto position = result.position + (result.trailing ? 1 : 0); + text_render_object->SetSelectionRange(std::nullopt); + text_render_object->SetCaretPosition(position); + this->select_start_position_ = position; + log::Debug("TextControlService: Begin to select text, start position: {}.", + position); + } +} + +template +void TextControlService::MouseUpHandler( + event::MouseButtonEventArgs& args) { + if (this->select_down_button_.has_value() && + this->select_down_button_.value() == args.GetButton()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; + log::Debug("TextControlService: End selecting text."); + } +} + +template +void TextControlService::LoseFocusHandler( + event::FocusChangeEventArgs& args) { + if (!args.IsWindow()) this->AbortSelection(); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/button.cpp b/src/ui/controls/button.cpp index 7dd087ba..6f6af878 100644 --- a/src/ui/controls/button.cpp +++ b/src/ui/controls/button.cpp @@ -1,13 +1,13 @@ -#include "cru/ui/controls/button.hpp" +#include "cru/ui/controls/Button.hpp" #include -#include "../helper.hpp" -#include "cru/platform/graph/brush.hpp" -#include "cru/platform/native/cursor.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/render/border_render_object.hpp" -#include "cru/ui/ui_manager.hpp" -#include "cru/ui/window.hpp" +#include "../Helper.hpp" +#include "cru/platform/graph/Brush.hpp" +#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "cru/ui/Window.hpp" namespace cru::ui::controls { using cru::platform::native::SystemCursorType; diff --git a/src/ui/controls/container.cpp b/src/ui/controls/container.cpp index 84582d80..de58ee64 100644 --- a/src/ui/controls/container.cpp +++ b/src/ui/controls/container.cpp @@ -1,7 +1,7 @@ -#include "cru/ui/controls/container.hpp" +#include "cru/ui/controls/Container.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/ui/render/border_render_object.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" namespace cru::ui::controls { Container::Container() { diff --git a/src/ui/controls/flex_layout.cpp b/src/ui/controls/flex_layout.cpp deleted file mode 100644 index 5412164a..00000000 --- a/src/ui/controls/flex_layout.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "cru/ui/controls/flex_layout.hpp" - -#include "cru/ui/render/flex_layout_render_object.hpp" - -namespace cru::ui::controls { -using render::FlexLayoutRenderObject; - -FlexLayout::FlexLayout() { - render_object_.reset(new FlexLayoutRenderObject()); - render_object_->SetAttachedControl(this); -} - -FlexLayout::~FlexLayout() = default; - -render::RenderObject* FlexLayout::GetRenderObject() const { - return render_object_.get(); -} - -namespace { -int FindPosition(render::RenderObject* parent, render::RenderObject* child) { - const auto& render_objects = parent->GetChildren(); - const auto find_result = - std::find(render_objects.cbegin(), render_objects.cend(), child); - if (find_result == render_objects.cend()) { - throw std::logic_error("Control is not a child of FlexLayout."); - } - return static_cast(find_result - render_objects.cbegin()); -} -} // namespace - -FlexChildLayoutData FlexLayout::GetChildLayoutData(Control* control) { - Expects(control); - return *render_object_->GetChildLayoutData( - FindPosition(render_object_.get(), control->GetRenderObject())); -} - -void FlexLayout::SetChildLayoutData(Control* control, - const FlexChildLayoutData& data) { - Expects(control); - *render_object_->GetChildLayoutData( - FindPosition(render_object_.get(), control->GetRenderObject())) = data; -} - -FlexMainAlignment FlexLayout::GetContentMainAlign() const { - return render_object_->GetContentMainAlign(); -} - -void FlexLayout::SetContentMainAlign(FlexMainAlignment value) { - if (value == GetContentMainAlign()) return; - render_object_->SetContentMainAlign(value); -} - -FlexDirection FlexLayout::GetFlexDirection() const { - return render_object_->GetFlexDirection(); -} - -void FlexLayout::SetFlexDirection(FlexDirection direction) { - if (direction == GetFlexDirection()) return; - render_object_->SetFlexDirection(direction); -} - -void FlexLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void FlexLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/stack_layout.cpp b/src/ui/controls/stack_layout.cpp deleted file mode 100644 index 47511f33..00000000 --- a/src/ui/controls/stack_layout.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "cru/ui/controls/stack_layout.hpp" - -#include "cru/ui/render/stack_layout_render_object.hpp" - -namespace cru::ui::controls { -using render::StackLayoutRenderObject; - -StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { - render_object_->SetAttachedControl(this); -} - -StackLayout::~StackLayout() = default; - -render::RenderObject* StackLayout::GetRenderObject() const { - return render_object_.get(); -} - -void StackLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void StackLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_block.cpp b/src/ui/controls/text_block.cpp deleted file mode 100644 index a3ec9f54..00000000 --- a/src/ui/controls/text_block.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "cru/ui/controls/text_block.hpp" - -#include "cru/ui/render/canvas_render_object.hpp" -#include "cru/ui/render/stack_layout_render_object.hpp" -#include "cru/ui/render/text_render_object.hpp" -#include "cru/ui/ui_manager.hpp" -#include "text_control_service.hpp" - -namespace cru::ui::controls { -using render::CanvasRenderObject; -using render::StackLayoutRenderObject; -using render::TextRenderObject; - -TextBlock::TextBlock() { - const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - - text_render_object_ = std::make_unique( - theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush, theme_resources->caret_brush); - - text_render_object_->SetAttachedControl(this); - - service_ = std::make_unique>(this); - service_->SetEnabled(true); -} - -TextBlock::~TextBlock() = default; - -render::RenderObject* TextBlock::GetRenderObject() const { - return text_render_object_.get(); -} - -std::string TextBlock::GetText() const { - return text_render_object_->GetText(); -} - -void TextBlock::SetText(std::string text) { - text_render_object_->SetText(std::move(text)); -} - -render::TextRenderObject* TextBlock::GetTextRenderObject() { - return text_render_object_.get(); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp deleted file mode 100644 index 8b7dc692..00000000 --- a/src/ui/controls/text_box.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "cru/ui/controls/text_box.hpp" - -#include "cru/ui/render/border_render_object.hpp" -#include "cru/ui/render/canvas_render_object.hpp" -#include "cru/ui/render/stack_layout_render_object.hpp" -#include "cru/ui/render/text_render_object.hpp" -#include "cru/ui/ui_manager.hpp" -#include "text_control_service.hpp" - -namespace cru::ui::controls { -using render::BorderRenderObject; -using render::CanvasRenderObject; -using render::StackLayoutRenderObject; -using render::TextRenderObject; - -TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { - const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - - border_style_ = theme_resources->text_box_border_style; - - text_render_object_ = std::make_unique( - theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush, theme_resources->caret_brush); - - border_render_object_->AddChild(text_render_object_.get(), 0); - - border_render_object_->SetAttachedControl(this); - text_render_object_->SetAttachedControl(this); - - service_ = std::make_unique>(this); - service_->SetEnabled(true); - service_->SetCaretVisible(true); - - GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetEnabled(true); - this->UpdateBorderStyle(); - }); - - LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetEnabled(false); - this->UpdateBorderStyle(); - }); -} - -TextBox::~TextBox() {} - -render::RenderObject* TextBox::GetRenderObject() const { - return border_render_object_.get(); -} - -render::TextRenderObject* TextBox::GetTextRenderObject() { - return text_render_object_.get(); -} - -const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } - -void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { - border_style_ = std::move(border_style); -} - -void TextBox::OnMouseHoverChange(bool) { UpdateBorderStyle(); } - -void TextBox::UpdateBorderStyle() { - const auto focus = HasFocus(); - const auto hover = IsMouseOver(); - border_render_object_->SetBorderStyle( - focus ? (hover ? border_style_.focus_hover : border_style_.focus) - : (hover ? border_style_.hover : border_style_.normal)); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_control_service.hpp b/src/ui/controls/text_control_service.hpp deleted file mode 100644 index 626423bf..00000000 --- a/src/ui/controls/text_control_service.hpp +++ /dev/null @@ -1,227 +0,0 @@ -#pragma once -#include "../helper.hpp" -#include "cru/common/logger.hpp" -#include "cru/platform/graph/font.hpp" -#include "cru/platform/graph/painter.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/control.hpp" -#include "cru/ui/render/canvas_render_object.hpp" -#include "cru/ui/render/text_render_object.hpp" -#include "cru/ui/ui_event.hpp" - -namespace cru::ui::controls { -constexpr int k_default_caret_blink_duration = 500; - -// TControl should inherits `Control` and has following methods: -// ``` -// render::TextRenderObject* GetTextRenderObject(); -// ``` -template -class TextControlService : public Object { - public: - TextControlService(TControl* control); - - CRU_DELETE_COPY(TextControlService) - CRU_DELETE_MOVE(TextControlService) - - ~TextControlService(); - - public: - bool IsEnabled() { return enable_; } - void SetEnabled(bool enable); - - bool IsCaretVisible() { return caret_visible_; } - void SetCaretVisible(bool visible); - - int GetCaretBlinkDuration() { return caret_blink_duration_; } - void SetCaretBlinkDuration(int milliseconds); - - private: - void AbortSelection(); - - void SetupCaret(); - void TearDownCaret(); - - void SetupHandlers(); - - void MouseMoveHandler(event::MouseEventArgs& args); - void MouseDownHandler(event::MouseButtonEventArgs& args); - void MouseUpHandler(event::MouseButtonEventArgs& args); - void LoseFocusHandler(event::FocusChangeEventArgs& args); - - private: - TControl* control_; - std::vector event_revoker_guards_; - - bool enable_ = false; - - bool caret_visible_ = false; - long long caret_timer_id_ = -1; - int caret_blink_duration_ = k_default_caret_blink_duration; - - // nullopt means not selecting - std::optional select_down_button_; - - // before the char - int select_start_position_; -}; - -template -TextControlService::TextControlService(TControl* control) - : control_(control) {} - -template -TextControlService::~TextControlService() { - const auto application = GetUiApplication(); - // Don't call TearDownCaret, because it use text render object of control, - // which may be destroyed already. - application->CancelTimer(this->caret_timer_id_); -} - -template -void TextControlService::SetEnabled(bool enable) { - if (enable == this->enable_) return; - if (enable) { - this->SetupHandlers(); - if (this->caret_visible_) { - this->SetupCaret(); - } - } else { - this->AbortSelection(); - this->event_revoker_guards_.clear(); - this->TearDownCaret(); - } -} - -template -void TextControlService::SetCaretVisible(bool visible) { - if (visible == this->caret_visible_) return; - - this->caret_visible_ = visible; - - if (this->enable_) { - if (visible) { - this->SetupCaret(); - } else { - this->TearDownCaret(); - } - } -} - -template -void TextControlService::SetCaretBlinkDuration(int milliseconds) { - if (this->caret_blink_duration_ == milliseconds) return; - - if (this->enable_ && this->caret_visible_) { - this->TearDownCaret(); - this->SetupCaret(); - } -} - -template -void TextControlService::AbortSelection() { - if (this->select_down_button_.has_value()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - } - this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); -} - -template -void TextControlService::SetupCaret() { - const auto application = GetUiApplication(); - - // Cancel first anyhow for safety. - application->CancelTimer(this->caret_timer_id_); - - this->control_->GetTextRenderObject()->SetDrawCaret(true); - this->caret_timer_id_ = application->SetInterval( - std::chrono::milliseconds(this->caret_blink_duration_), - [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); -} - -template -void TextControlService::TearDownCaret() { - const auto application = GetUiApplication(); - application->CancelTimer(this->caret_timer_id_); - this->control_->GetTextRenderObject()->SetDrawCaret(false); -} - -template -void TextControlService::SetupHandlers() { - Expects(event_revoker_guards_.empty()); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseMoveHandler, this, - std::placeholders::_1))}); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseDownHandler, this, - std::placeholders::_1))}); - this->event_revoker_guards_.push_back(EventRevokerGuard{ - control_->MouseUpEvent()->Direct()->AddHandler(std::bind( - &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->LoseFocusEvent()->Direct()->AddHandler( - std::bind(&TextControlService::LoseFocusHandler, this, - std::placeholders::_1))}); -} - -template -void TextControlService::MouseMoveHandler( - event::MouseEventArgs& args) { - if (this->select_down_button_.has_value()) { - const auto text_render_object = this->control_->GetTextRenderObject(); - const auto result = text_render_object->TextHitTest( - text_render_object->FromRootToContent(args.GetPoint())); - const auto position = result.position + (result.trailing ? 1 : 0); - log::Debug( - "TextControlService: Text selection changed on mouse move, range: {}, " - "{}.", - position, this->select_start_position_); - this->control_->GetTextRenderObject()->SetSelectionRange( - TextRange::FromTwoSides( - static_cast(position), - static_cast(this->select_start_position_))); - text_render_object->SetCaretPosition(position); - } -} - -template -void TextControlService::MouseDownHandler( - event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value()) { - return; - } else { - if (!this->control_->CaptureMouse()) return; - if (!this->control_->RequestFocus()) return; - const auto text_render_object = this->control_->GetTextRenderObject(); - this->select_down_button_ = args.GetButton(); - const auto result = text_render_object->TextHitTest( - text_render_object->FromRootToContent(args.GetPoint())); - const auto position = result.position + (result.trailing ? 1 : 0); - text_render_object->SetSelectionRange(std::nullopt); - text_render_object->SetCaretPosition(position); - this->select_start_position_ = position; - log::Debug("TextControlService: Begin to select text, start position: {}.", - position); - } -} - -template -void TextControlService::MouseUpHandler( - event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value() && - this->select_down_button_.value() == args.GetButton()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - log::Debug("TextControlService: End selecting text."); - } -} - -template -void TextControlService::LoseFocusHandler( - event::FocusChangeEventArgs& args) { - if (!args.IsWindow()) this->AbortSelection(); -} -} // namespace cru::ui::controls diff --git a/src/ui/helper.cpp b/src/ui/helper.cpp index 4d5d8665..6f67e701 100644 --- a/src/ui/helper.cpp +++ b/src/ui/helper.cpp @@ -1,7 +1,7 @@ -#include "helper.hpp" +#include "Helper.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/native/ui_application.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/native/UiApplication.hpp" namespace cru::ui { using cru::platform::graph::IGraphFactory; diff --git a/src/ui/helper.hpp b/src/ui/helper.hpp index 4fd14aa6..6923852f 100644 --- a/src/ui/helper.hpp +++ b/src/ui/helper.hpp @@ -1,5 +1,5 @@ #pragma once -#include "cru/ui/base.hpp" +#include "cru/ui/Base.hpp" namespace cru::platform { namespace graph { diff --git a/src/ui/layout_control.cpp b/src/ui/layout_control.cpp deleted file mode 100644 index 1d5d1ede..00000000 --- a/src/ui/layout_control.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "cru/ui/layout_control.hpp" - -#include "cru/ui/window.hpp" - -namespace cru::ui { -LayoutControl::~LayoutControl() { - for (const auto child : children_) delete child; -} - -void LayoutControl::AddChild(Control* control, const Index position) { - Expects(control->GetParent() == - nullptr); // The control already has a parent. - Expects(!dynamic_cast(control)); // Can't add a window as child. - Expects(position >= 0); - Expects(position <= - static_cast( - this->children_.size())); // The position is out of range. - - children_.insert(this->children_.cbegin() + position, control); - - control->_SetParent(this); - control->_SetDescendantUiHost(GetUiHost()); - - OnAddChild(control, position); -} - -void LayoutControl::RemoveChild(const Index position) { - Expects(position >= 0); - Expects(position < - static_cast( - this->children_.size())); // The position is out of range. - - const auto i = children_.cbegin() + position; - const auto child = *i; - - children_.erase(i); - - child->_SetParent(nullptr); - child->_SetDescendantUiHost(nullptr); - - OnRemoveChild(child, position); -} - -void LayoutControl::OnAddChild(Control* child, const Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} - -void LayoutControl::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} -} // namespace cru::ui diff --git a/src/ui/no_child_control.cpp b/src/ui/no_child_control.cpp deleted file mode 100644 index 81299411..00000000 --- a/src/ui/no_child_control.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "cru/ui/no_child_control.hpp" - -namespace cru::ui { -const std::vector NoChildControl::empty_control_vector{}; -} diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp new file mode 100644 index 00000000..a656cb99 --- /dev/null +++ b/src/ui/render/BorderRenderObject.cpp @@ -0,0 +1,236 @@ +#include "cru/ui/render/BorderRenderObject.hpp" + +#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 + +namespace cru::ui::render { +BorderRenderObject::BorderRenderObject() { + SetChildMode(ChildMode::Single); + RecreateGeometry(); +} + +BorderRenderObject::~BorderRenderObject() {} + +void BorderRenderObject::Draw(platform::graph::IPainter* painter) { + if (background_brush_ != nullptr) + painter->FillGeometry(border_inner_geometry_.get(), + background_brush_.get()); + if (is_border_enabled_) { + if (border_brush_ == nullptr) { + log::Warn("Border is enabled but brush is null"); + } else { + painter->FillGeometry(geometry_.get(), border_brush_.get()); + } + } + if (const auto child = GetChild()) { + auto offset = child->GetOffset(); + platform::graph::util::WithTransform( + painter, platform::Matrix::Translation(offset.x, offset.y), + [child](auto p) { child->Draw(p); }); + } + if (foreground_brush_ != nullptr) + painter->FillGeometry(border_inner_geometry_.get(), + foreground_brush_.get()); +} + +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; + InvalidateLayout(); +} + +RenderObject* BorderRenderObject::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; + } + } + + if (is_border_enabled_) { + const auto contains = + border_outer_geometry_->FillContains(Point{point.x, point.y}); + return contains ? this : nullptr; + } else { + const auto margin = GetMargin(); + const auto size = GetSize(); + return Rect{margin.left, margin.top, + std::max(size.width - margin.GetHorizontalTotal(), 0.0f), + std::max(size.height - margin.GetVerticalTotal(), 0.0f)} + .IsPointInside(point) + ? this + : nullptr; + } +} + +void BorderRenderObject::OnMeasureCore(const Size& available_size) { + const auto margin = GetMargin(); + const auto padding = GetPadding(); + Size margin_border_padding_size{ + margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), + margin.GetVerticalTotal() + padding.GetVerticalTotal()}; + + if (is_border_enabled_) { + margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); + margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); + } + + auto coerced_margin_border_padding_size = margin_border_padding_size; + if (coerced_margin_border_padding_size.width > available_size.width) { + log::Warn( + "Measure: horizontal length of padding, border and margin is bigger " + "than available length."); + coerced_margin_border_padding_size.width = available_size.width; + } + if (coerced_margin_border_padding_size.height > available_size.height) { + log::Warn( + "Measure: vertical length of padding, border and margin is bigger " + "than available length."); + coerced_margin_border_padding_size.height = available_size.height; + } + + const auto coerced_content_available_size = + available_size - coerced_margin_border_padding_size; + + const auto actual_content_size = + OnMeasureContent(coerced_content_available_size); + + SetPreferredSize(coerced_margin_border_padding_size + actual_content_size); +} + +void BorderRenderObject::OnLayoutCore(const Rect& rect) { + const auto margin = GetMargin(); + const auto padding = GetPadding(); + Size margin_border_padding_size{ + margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), + margin.GetVerticalTotal() + padding.GetVerticalTotal()}; + + if (is_border_enabled_) { + margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); + margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); + } + + const auto content_available_size = + rect.GetSize() - margin_border_padding_size; + auto coerced_content_available_size = content_available_size; + + if (coerced_content_available_size.width < 0) { + log::Warn( + "Layout: horizontal length of padding, border and margin is bigger " + "than available length."); + coerced_content_available_size.width = 0; + } + if (coerced_content_available_size.height < 0) { + log::Warn( + "Layout: vertical length of padding, border and margin is bigger " + "than available length."); + coerced_content_available_size.height = 0; + } + + OnLayoutContent( + Rect{margin.left + (is_border_enabled_ ? border_thickness_.left : 0) + + padding.left, + margin.top + (is_border_enabled_ ? border_thickness_.top : 0) + + padding.top, + coerced_content_available_size.width, + coerced_content_available_size.height}); +} + +Size BorderRenderObject::OnMeasureContent(const Size& available_size) { + const auto child = GetChild(); + if (child) { + child->Measure(available_size); + return child->GetPreferredSize(); + } else { + return Size{}; + } +} + +void BorderRenderObject::OnLayoutContent(const Rect& content_rect) { + const auto child = GetChild(); + if (child) { + child->Layout(content_rect); + } +} + +void BorderRenderObject::OnAfterLayout() { RecreateGeometry(); } + +void BorderRenderObject::RecreateGeometry() { + geometry_.reset(); + border_outer_geometry_.reset(); + + Thickness t = border_thickness_; + t.left /= 2.0; + t.top /= 2.0; + t.right /= 2.0; + t.bottom /= 2.0; + const CornerRadius& r = border_radius_; + + CornerRadius outer_radius(r.left_top + Point{t.left, t.top}, + r.right_top + Point{t.right, t.top}, + r.left_bottom + Point{t.left, t.bottom}, + r.right_bottom + Point{t.right, t.bottom}); + + CornerRadius inner_radius(r.left_top - Point{t.left, t.top}, + r.right_top - Point{t.right, t.top}, + 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, + 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)); + builder->QuadraticBezierTo( + Point(rect.GetRight(), rect.top), + Point(rect.GetRight(), rect.top + corner.right_top.y)); + builder->LineTo( + Point(rect.GetRight(), rect.GetBottom() - corner.right_bottom.y)); + builder->QuadraticBezierTo( + Point(rect.GetRight(), rect.GetBottom()), + Point(rect.GetRight() - corner.right_bottom.x, rect.GetBottom())); + builder->LineTo(Point(rect.left + corner.left_bottom.x, rect.GetBottom())); + builder->QuadraticBezierTo( + Point(rect.left, rect.GetBottom()), + Point(rect.left, rect.GetBottom() - corner.left_bottom.y)); + builder->LineTo(Point(rect.left, rect.top + corner.left_top.y)); + builder->QuadraticBezierTo(Point(rect.left, rect.top), + Point(rect.left + corner.left_top.x, rect.top)); + builder->CloseFigure(true); + }; + + const auto size = GetSize(); + const auto margin = GetMargin(); + const Rect outer_rect{margin.left, margin.top, + size.width - margin.GetHorizontalTotal(), + size.height - margin.GetVerticalTotal()}; + const auto graph_factory = GetGraphFactory(); + std::unique_ptr builder{ + graph_factory->CreateGeometryBuilder()}; + f(builder.get(), outer_rect, outer_radius); + border_outer_geometry_ = builder->Build(); + builder.reset(); + + const Rect inner_rect = outer_rect.Shrink(border_thickness_); + + builder = graph_factory->CreateGeometryBuilder(); + f(builder.get(), inner_rect, inner_radius); + border_inner_geometry_ = builder->Build(); + builder.reset(); + + builder = graph_factory->CreateGeometryBuilder(); + f(builder.get(), outer_rect, outer_radius); + f(builder.get(), inner_rect, inner_radius); + geometry_ = builder->Build(); + builder.reset(); +} +} // namespace cru::ui::render diff --git a/src/ui/render/CanvasRenderObject.cpp b/src/ui/render/CanvasRenderObject.cpp new file mode 100644 index 00000000..16ac9239 --- /dev/null +++ b/src/ui/render/CanvasRenderObject.cpp @@ -0,0 +1,28 @@ +#include "cru/ui/render/CanvasRenderObject.hpp" + +#include "cru/ui/render/LayoutUtility.hpp" + +namespace cru::ui::render { +CanvasRenderObject::CanvasRenderObject() : RenderObject(ChildMode::None) {} + +CanvasRenderObject::~CanvasRenderObject() = default; + +void CanvasRenderObject::Draw(platform::graph::IPainter* painter) { + const auto rect = GetContentRect(); + CanvasPaintEventArgs args{painter, rect}; + paint_event_.Raise(args); +} + +RenderObject* CanvasRenderObject::HitTest(const Point& point) { + const auto padding_rect = GetPaddingRect(); + return padding_rect.IsPointInside(point) ? this : nullptr; +} + +Size CanvasRenderObject::OnMeasureContent(const Size& available_size) { + return Min(available_size, GetDesiredSize()); +} + +void CanvasRenderObject::OnLayoutContent(const Rect& content_rect) { + CRU_UNUSED(content_rect) +} +} // namespace cru::ui::render diff --git a/src/ui/render/FlexLayoutRenderObject.cpp b/src/ui/render/FlexLayoutRenderObject.cpp new file mode 100644 index 00000000..b609fd97 --- /dev/null +++ b/src/ui/render/FlexLayoutRenderObject.cpp @@ -0,0 +1,197 @@ +#include "cru/ui/render/FlexLayoutRenderObject.hpp" + +#include "cru/platform/graph/util/Painter.hpp" + +#include +#include + +namespace cru::ui::render { +Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) { + std::vector has_basis_children; + std::vector no_basis_children; + std::vector grow_children; + std::vector shrink_chilren; + const auto child_count = GetChildCount(); + for (int i = 0; i < child_count; i++) { + const auto& layout_data = *GetChildLayoutData(i); + if (layout_data.flex_basis.has_value()) + has_basis_children.push_back(i); + else + no_basis_children.push_back(i); + if (layout_data.flex_grow > 0) grow_children.push_back(i); + if (layout_data.flex_shrink > 0) shrink_chilren.push_back(i); + } + + std::function get_main_length; + std::function get_cross_length; + std::function create_size; + + if (direction_ == FlexDirection::Horizontal || + direction_ == FlexDirection::HorizontalReverse) { + get_main_length = [](const Size& size) { return size.width; }; + get_cross_length = [](const Size& size) { return size.height; }; + create_size = [](float main, float cross) { return Size(main, cross); }; + } else { + get_main_length = [](const Size& size) { return size.height; }; + get_cross_length = [](const Size& size) { return size.width; }; + create_size = [](float main, float cross) { return Size(cross, main); }; + } + + const auto& children = GetChildren(); + + float remain_main_length = get_main_length(available_size); + float max_cross_length = 0; + + for (const int i : has_basis_children) { + const auto child = children[i]; + const float basis = GetChildLayoutData(i)->flex_basis.value(); + child->Measure(create_size(basis, get_cross_length(available_size))); + remain_main_length -= basis; + const float child_preferred_cross_length = + get_cross_length(child->GetPreferredSize()); + child->SetPreferredSize(create_size(basis, child_preferred_cross_length)); + max_cross_length = std::max(max_cross_length, child_preferred_cross_length); + } + + for (const int i : no_basis_children) { + const auto child = children[i]; + child->Measure(create_size(remain_main_length > 0 ? remain_main_length : 0, + get_cross_length(available_size))); + remain_main_length -= get_main_length(child->GetPreferredSize()); + max_cross_length = + std::max(max_cross_length, get_cross_length(child->GetPreferredSize())); + } + + if (remain_main_length > 0) { + float total_grow = 0; + for (const int i : grow_children) + total_grow += GetChildLayoutData(i)->flex_grow; + + for (const int i : grow_children) { + const float distributed_grow_length = + remain_main_length * (GetChildLayoutData(i)->flex_grow / total_grow); + const auto child = children[i]; + const float new_main_length = + get_main_length(child->GetPreferredSize()) + distributed_grow_length; + child->Measure( + create_size(new_main_length, get_cross_length(available_size))); + const float new_child_preferred_cross_length = + get_cross_length(child->GetPreferredSize()); + child->SetPreferredSize( + create_size(new_main_length, new_child_preferred_cross_length)); + max_cross_length = + std::max(max_cross_length, new_child_preferred_cross_length); + } + } + + if (remain_main_length < 0) { + float total_shrink = 0; + for (const int i : shrink_chilren) + total_shrink += GetChildLayoutData(i)->flex_shrink; + + for (const int i : shrink_chilren) { + const float distributed_shrink_length = // negative + remain_main_length * + (GetChildLayoutData(i)->flex_shrink / total_shrink); + const auto child = children[i]; + float new_main_length = get_main_length(child->GetPreferredSize()) + + distributed_shrink_length; + new_main_length = new_main_length > 0 ? new_main_length : 0; + child->Measure( + create_size(new_main_length, get_cross_length(available_size))); + const float new_child_preferred_cross_length = + get_cross_length(child->GetPreferredSize()); + child->SetPreferredSize( + create_size(new_main_length, new_child_preferred_cross_length)); + max_cross_length = + std::max(max_cross_length, new_child_preferred_cross_length); + } + } + + return create_size(get_main_length(available_size) - + (remain_main_length > 0 ? remain_main_length : 0), + max_cross_length); +} + +void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { + auto calculate_anchor = [](int alignment, float start_point, + float total_length, + float content_length) -> float { + switch (alignment) { + case internal::align_start: + return start_point; + case internal::align_center: + return start_point + (total_length - content_length) / 2.0f; + case internal::align_end: + return start_point + total_length - content_length; + default: + return 0; + } + }; + + const auto& children = GetChildren(); + if (direction_ == FlexDirection::Horizontal || + direction_ == FlexDirection::HorizontalReverse) { + float actual_content_width = 0; + for (const auto child : children) { + actual_content_width += child->GetPreferredSize().width; + } + + const float content_anchor_x = + calculate_anchor(static_cast(content_main_align_), 0, + content_rect.width, actual_content_width); + + float anchor_x = 0; + for (int i = 0; i < static_cast(children.size()); i++) { + const auto child = children[i]; + const auto size = child->GetPreferredSize(); + + float real_anchor_x = anchor_x + content_anchor_x; + if (direction_ == FlexDirection::Horizontal) + real_anchor_x = content_rect.left + real_anchor_x; + else + real_anchor_x = content_rect.GetRight() - real_anchor_x; + child->Layout(Rect{ + real_anchor_x, + calculate_anchor( + static_cast(GetChildLayoutData(i)->cross_alignment.value_or( + this->item_cross_align_)), + content_rect.top, content_rect.height, size.height), + size.width, size.height}); + + anchor_x += size.width; + } + } else { + float actual_content_height = 0; + for (const auto child : children) { + actual_content_height = child->GetPreferredSize().height; + } + + const float content_anchor_y = + calculate_anchor(static_cast(content_main_align_), 0, + content_rect.height, actual_content_height); + + float anchor_y = 0; + for (int i = 0; i < static_cast(children.size()); i++) { + const auto child = children[i]; + const auto size = child->GetPreferredSize(); + + float real_anchor_y = anchor_y + content_anchor_y; + if (direction_ == FlexDirection::Vertical) { + real_anchor_y = content_rect.top + real_anchor_y; + } else { + real_anchor_y = content_rect.GetBottom() - real_anchor_y; + } + child->Layout(Rect{ + real_anchor_y, + calculate_anchor( + static_cast(GetChildLayoutData(i)->cross_alignment.value_or( + this->item_cross_align_)), + content_rect.left, content_rect.width, size.width), + size.width, size.height}); + + anchor_y += size.height; + } + } +} +} // namespace cru::ui::render diff --git a/src/ui/render/LayoutUtility.cpp b/src/ui/render/LayoutUtility.cpp new file mode 100644 index 00000000..03252f1c --- /dev/null +++ b/src/ui/render/LayoutUtility.cpp @@ -0,0 +1,15 @@ +#include "cru/ui/render/LayoutUtility.hpp" + +#include + +namespace cru::ui::render { +Size Min(const Size& left, const Size& right) { + return Size{std::min(left.width, right.width), + std::min(left.height, right.height)}; +} + +Size Max(const Size& left, const Size& right) { + return Size{std::max(left.width, right.width), + std::max(left.height, right.height)}; +} +} // namespace cru::ui::render diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp new file mode 100644 index 00000000..83e306a8 --- /dev/null +++ b/src/ui/render/RenderObject.cpp @@ -0,0 +1,190 @@ +#include "cru/ui/render/RenderObject.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/ui/UiHost.hpp" + +#include + +namespace cru::ui::render { +void RenderObject::AddChild(RenderObject* render_object, const Index position) { + Expects(child_mode_ != ChildMode::None); + Expects(!(child_mode_ == ChildMode::Single && children_.size() > 0)); + + Expects(render_object->GetParent() == + nullptr); // Render object already has a parent. + Expects(position >= 0); // Position index is less than 0. + Expects( + position <= + static_cast(children_.size())); // Position index is out of bound. + + children_.insert(children_.cbegin() + position, render_object); + render_object->SetParent(this); + render_object->SetRenderHostRecursive(GetUiHost()); + OnAddChild(render_object, position); +} + +void RenderObject::RemoveChild(const Index position) { + Expects(position >= 0); // Position index is less than 0. + Expects(position < static_cast( + children_.size())); // Position index is out of bound. + + const auto i = children_.cbegin() + position; + const auto removed_child = *i; + children_.erase(i); + removed_child->SetParent(nullptr); + removed_child->SetRenderHostRecursive(nullptr); + OnRemoveChild(removed_child, position); +} + +Point RenderObject::GetTotalOffset() const { + Point result{}; + const RenderObject* render_object = this; + + while (render_object != nullptr) { + const auto o = render_object->GetOffset(); + result.x += o.x; + result.y += o.y; + render_object = render_object->GetParent(); + } + + return result; +} + +Point RenderObject::FromRootToContent(const Point& point) const { + const auto offset = GetTotalOffset(); + const auto rect = GetContentRect(); + return Point{point.x - (offset.x + rect.left), + point.y - (offset.y + rect.top)}; +} + +void RenderObject::Measure(const Size& available_size) { + OnMeasureCore(available_size); +} + +void RenderObject::Layout(const Rect& rect) { + SetOffset(rect.GetLeftTop()); + SetSize(rect.GetSize()); + OnLayoutCore(Rect{Point{}, rect.GetSize()}); +} + +void RenderObject::OnParentChanged(RenderObject* old_parent, + RenderObject* new_parent) { + CRU_UNUSED(old_parent) + CRU_UNUSED(new_parent) +} + +void RenderObject::OnAddChild(RenderObject* new_child, Index position) { + CRU_UNUSED(new_child) + CRU_UNUSED(position) + + InvalidateLayout(); + InvalidatePaint(); +} + +void RenderObject::OnRemoveChild(RenderObject* removed_child, Index position) { + CRU_UNUSED(removed_child) + CRU_UNUSED(position) + + InvalidateLayout(); + InvalidatePaint(); +} + +void RenderObject::OnMeasureCore(const Size& available_size) { + Size margin_padding_size{ + margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), + margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; + + auto coerced_margin_padding_size = margin_padding_size; + if (coerced_margin_padding_size.width > available_size.width) { + log::Warn( + "Measure: horizontal length of padding and margin is bigger than " + "available length."); + coerced_margin_padding_size.width = available_size.width; + } + if (coerced_margin_padding_size.height > available_size.height) { + log::Warn( + "Measure: vertical length of padding and margin is bigger than " + "available length."); + coerced_margin_padding_size.height = available_size.height; + } + + const auto coerced_content_available_size = + available_size - coerced_margin_padding_size; + const auto actual_content_size = + OnMeasureContent(coerced_content_available_size); + + SetPreferredSize(coerced_margin_padding_size + actual_content_size); +} + +void RenderObject::OnLayoutCore(const Rect& rect) { + Size margin_padding_size{ + margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), + margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; + const auto content_available_size = rect.GetSize() - margin_padding_size; + auto coerced_content_available_size = content_available_size; + + if (coerced_content_available_size.width < 0) { + log::Warn( + "Layout: horizontal length of padding and margin is bigger than " + "available length."); + coerced_content_available_size.width = 0; + } + if (coerced_content_available_size.height < 0) { + log::Warn( + "Layout: vertical length of padding and margin is bigger than " + "available length."); + coerced_content_available_size.height = 0; + } + + OnLayoutContent(Rect{margin_.left + padding_.left, margin_.top + padding_.top, + coerced_content_available_size.width, + coerced_content_available_size.height}); +} + +void RenderObject::OnAfterLayout() {} + +Rect RenderObject::GetPaddingRect() const { + Rect rect{Point{}, GetSize()}; + rect = rect.Shrink(GetMargin()); + rect.width = std::max(rect.width, 0.0f); + rect.height = std::max(rect.height, 0.0f); + return rect; +} + +Rect RenderObject::GetContentRect() const { + Rect rect{Point{}, GetSize()}; + rect = rect.Shrink(GetMargin()); + rect = rect.Shrink(GetPadding()); + rect.width = std::max(rect.width, 0.0f); + rect.height = std::max(rect.height, 0.0f); + return rect; +} + +void RenderObject::SetParent(RenderObject* new_parent) { + const auto old_parent = parent_; + parent_ = new_parent; + OnParentChanged(old_parent, new_parent); +} + +void RenderObject::InvalidateLayout() { + if (ui_host_ != nullptr) ui_host_->InvalidateLayout(); +} + +void RenderObject::InvalidatePaint() { + if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); +} + +void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { + render_object->OnAfterLayout(); + for (const auto o : render_object->GetChildren()) { + NotifyAfterLayoutRecursive(o); + } +} + +void RenderObject::SetRenderHostRecursive(UiHost* host) { + ui_host_ = host; + for (const auto child : GetChildren()) { + child->SetRenderHostRecursive(host); + } +} +} // namespace cru::ui::render diff --git a/src/ui/render/ScrollRenderObject.cpp b/src/ui/render/ScrollRenderObject.cpp new file mode 100644 index 00000000..33e9e407 --- /dev/null +++ b/src/ui/render/ScrollRenderObject.cpp @@ -0,0 +1 @@ +#include "cru/ui/render/ScrollRenderObject.hpp" diff --git a/src/ui/render/StackLayoutRenderObject.cpp b/src/ui/render/StackLayoutRenderObject.cpp new file mode 100644 index 00000000..d08e136d --- /dev/null +++ b/src/ui/render/StackLayoutRenderObject.cpp @@ -0,0 +1,49 @@ +#include "cru/ui/render/StackLayoutRenderObject.hpp" + +#include + +namespace cru::ui::render { +Size StackLayoutRenderObject::OnMeasureContent(const Size& available_size) { + auto size = Size{}; + for (const auto child : GetChildren()) { + child->Measure(available_size); + const auto& preferred_size = child->GetPreferredSize(); + size.width = std::max(size.width, preferred_size.width); + size.height = std::max(size.height, preferred_size.height); + } + return size; +} + +void StackLayoutRenderObject::OnLayoutContent(const Rect& rect) { + auto calculate_anchor = [](int alignment, float start_point, + float total_length, + float content_length) -> float { + switch (alignment) { + case internal::align_start: + return start_point; + case internal::align_center: + return start_point + (total_length - content_length) / 2.0f; + case internal::align_end: + return start_point + total_length - content_length; + default: + return 0; + } + }; + + const auto count = GetChildCount(); + const auto& children = GetChildren(); + + for (int i = 0; i < count; i++) { + const auto layout_data = GetChildLayoutData(i); + const auto child = children[i]; + const auto& size = child->GetPreferredSize(); + child->Layout( + Rect{calculate_anchor(static_cast(layout_data->horizontal), + rect.left, rect.width, size.width), + calculate_anchor(static_cast(layout_data->vertical), rect.top, + rect.height, size.height), + size.width, size.height}); + } +} + +} // namespace cru::ui::render diff --git a/src/ui/render/TextRenderObject.cpp b/src/ui/render/TextRenderObject.cpp new file mode 100644 index 00000000..05acd086 --- /dev/null +++ b/src/ui/render/TextRenderObject.cpp @@ -0,0 +1,177 @@ +#include "cru/ui/render/TextRenderObject.hpp" + +#include "../Helper.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/graph/TextLayout.hpp" +#include "cru/platform/graph/util/Painter.hpp" + +#include + +namespace cru::ui::render { +TextRenderObject::TextRenderObject( + std::shared_ptr brush, + std::shared_ptr font, + std::shared_ptr selection_brush, + std::shared_ptr caret_brush) { + Expects(brush); + Expects(font); + Expects(selection_brush); + Expects(caret_brush); + + SetChildMode(ChildMode::None); + + brush.swap(brush_); + font.swap(font_); + selection_brush.swap(selection_brush_); + caret_brush.swap(caret_brush_); + + const auto graph_factory = GetGraphFactory(); + text_layout_ = graph_factory->CreateTextLayout(font_, ""); +} + +TextRenderObject::~TextRenderObject() = default; + +std::string TextRenderObject::GetText() const { + return text_layout_->GetText(); +} + +void TextRenderObject::SetText(std::string new_text) { + text_layout_->SetText(std::move(new_text)); +} + +void TextRenderObject::SetBrush( + std::shared_ptr new_brush) { + Expects(new_brush); + new_brush.swap(brush_); + InvalidatePaint(); +} + +std::shared_ptr TextRenderObject::GetFont() const { + return text_layout_->GetFont(); +} + +void TextRenderObject::SetFont(std::shared_ptr font) { + Expects(font); + text_layout_->SetFont(std::move(font)); +} + +std::vector TextRenderObject::TextRangeRect(const TextRange& text_range) { + return text_layout_->TextRangeRect(text_range); +} + +Point TextRenderObject::TextSinglePoint(gsl::index position, bool trailing) { + return text_layout_->TextSinglePoint(position, trailing); +} + +platform::graph::TextHitTestResult TextRenderObject::TextHitTest( + const Point& point) { + return text_layout_->HitTest(point); +} + +void TextRenderObject::SetSelectionRange(std::optional new_range) { + selection_range_ = std::move(new_range); + InvalidatePaint(); +} + +void TextRenderObject::SetSelectionBrush( + std::shared_ptr new_brush) { + Expects(new_brush); + new_brush.swap(selection_brush_); + if (selection_range_ && selection_range_->count) { + InvalidatePaint(); + } +} + +void TextRenderObject::SetDrawCaret(bool draw_caret) { + if (draw_caret_ != draw_caret) { + draw_caret_ = draw_caret; + InvalidatePaint(); + } +} + +void TextRenderObject::SetCaretPosition(gsl::index position) { + if (position != caret_position_) { + caret_position_ = position; + if (draw_caret_) { + InvalidatePaint(); + } + } +} + +void TextRenderObject::GetCaretBrush( + std::shared_ptr brush) { + Expects(brush); + brush.swap(caret_brush_); + if (draw_caret_) { + InvalidatePaint(); + } +} + +void TextRenderObject::SetCaretWidth(const float width) { + Expects(width >= 0.0f); + + caret_width_ = width; + if (draw_caret_) { + InvalidatePaint(); + } +} + +void TextRenderObject::Draw(platform::graph::IPainter* painter) { + platform::graph::util::WithTransform( + painter, + platform::Matrix::Translation(GetMargin().left + GetPadding().left, + GetMargin().top + GetPadding().top), + [this](platform::graph::IPainter* p) { + if (this->selection_range_.has_value()) { + const auto&& rects = + text_layout_->TextRangeRect(this->selection_range_.value()); + for (const auto& rect : rects) + p->FillRectangle(rect, this->GetSelectionBrush().get()); + } + + p->DrawText(Point{}, text_layout_.get(), brush_.get()); + + if (this->draw_caret_ && this->caret_width_ != 0.0f) { + auto caret_pos = this->caret_position_; + gsl::index text_size = this->GetText().size(); + if (caret_pos < 0) { + caret_pos = 0; + } else if (caret_pos > text_size) { + caret_pos = text_size; + } + + const auto caret_top_center = + this->text_layout_->TextSinglePoint(caret_pos, false); + + const auto font_height = this->font_->GetFontSize(); + const auto caret_width = this->caret_width_; + + p->FillRectangle(Rect{caret_top_center.x - caret_width / 2.0f, + caret_top_center.y, caret_width, font_height}, + this->caret_brush_.get()); + } + }); +} + +RenderObject* TextRenderObject::HitTest(const Point& point) { + const auto padding_rect = GetPaddingRect(); + return padding_rect.IsPointInside(point) ? this : nullptr; +} + +Size TextRenderObject::OnMeasureContent(const Size& available_size) { + text_layout_->SetMaxWidth(available_size.width); + text_layout_->SetMaxHeight(available_size.height); + return text_layout_->GetTextBounds().GetSize(); +} + +void TextRenderObject::OnLayoutContent(const Rect& content_rect) { + CRU_UNUSED(content_rect) +} + +void TextRenderObject::OnAfterLayout() { + const auto&& size = GetContentRect().GetSize(); + text_layout_->SetMaxWidth(size.width); + text_layout_->SetMaxHeight(size.height); +} + +} // namespace cru::ui::render diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp new file mode 100644 index 00000000..cd1f806f --- /dev/null +++ b/src/ui/render/WindowRenderObject.cpp @@ -0,0 +1,45 @@ +#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); })); +} + +void WindowRenderObject::Draw(platform::graph::IPainter* painter) { + painter->Clear(colors::white); + if (const auto child = GetChild()) { + auto offset = child->GetOffset(); + platform::graph::util::WithTransform( + painter, platform::Matrix::Translation(offset.x, offset.y), + [child](platform::graph::IPainter* p) { child->Draw(p); }); + } +} + +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 Size& available_size) { + if (const auto child = GetChild()) child->Measure(available_size); + return available_size; +} + +void WindowRenderObject::OnLayoutContent(const Rect& content_rect) { + if (const auto child = GetChild()) child->Layout(content_rect); +} +} // namespace cru::ui::render diff --git a/src/ui/render/border_render_object.cpp b/src/ui/render/border_render_object.cpp deleted file mode 100644 index 7c96a3b6..00000000 --- a/src/ui/render/border_render_object.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "cru/ui/render/border_render_object.hpp" - -#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 - -namespace cru::ui::render { -BorderRenderObject::BorderRenderObject() { - SetChildMode(ChildMode::Single); - RecreateGeometry(); -} - -BorderRenderObject::~BorderRenderObject() {} - -void BorderRenderObject::Draw(platform::graph::IPainter* painter) { - if (background_brush_ != nullptr) - painter->FillGeometry(border_inner_geometry_.get(), - background_brush_.get()); - if (is_border_enabled_) { - if (border_brush_ == nullptr) { - log::Warn("Border is enabled but brush is null"); - } else { - painter->FillGeometry(geometry_.get(), border_brush_.get()); - } - } - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - platform::graph::util::WithTransform( - painter, platform::Matrix::Translation(offset.x, offset.y), - [child](auto p) { child->Draw(p); }); - } - if (foreground_brush_ != nullptr) - painter->FillGeometry(border_inner_geometry_.get(), - foreground_brush_.get()); -} - -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; - InvalidateLayout(); -} - -RenderObject* BorderRenderObject::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; - } - } - - if (is_border_enabled_) { - const auto contains = - border_outer_geometry_->FillContains(Point{point.x, point.y}); - return contains ? this : nullptr; - } else { - const auto margin = GetMargin(); - const auto size = GetSize(); - return Rect{margin.left, margin.top, - std::max(size.width - margin.GetHorizontalTotal(), 0.0f), - std::max(size.height - margin.GetVerticalTotal(), 0.0f)} - .IsPointInside(point) - ? this - : nullptr; - } -} - -void BorderRenderObject::OnMeasureCore(const Size& available_size) { - const auto margin = GetMargin(); - const auto padding = GetPadding(); - Size margin_border_padding_size{ - margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), - margin.GetVerticalTotal() + padding.GetVerticalTotal()}; - - if (is_border_enabled_) { - margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); - margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); - } - - auto coerced_margin_border_padding_size = margin_border_padding_size; - if (coerced_margin_border_padding_size.width > available_size.width) { - log::Warn( - "Measure: horizontal length of padding, border and margin is bigger " - "than available length."); - coerced_margin_border_padding_size.width = available_size.width; - } - if (coerced_margin_border_padding_size.height > available_size.height) { - log::Warn( - "Measure: vertical length of padding, border and margin is bigger " - "than available length."); - coerced_margin_border_padding_size.height = available_size.height; - } - - const auto coerced_content_available_size = - available_size - coerced_margin_border_padding_size; - - const auto actual_content_size = - OnMeasureContent(coerced_content_available_size); - - SetPreferredSize(coerced_margin_border_padding_size + actual_content_size); -} - -void BorderRenderObject::OnLayoutCore(const Rect& rect) { - const auto margin = GetMargin(); - const auto padding = GetPadding(); - Size margin_border_padding_size{ - margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), - margin.GetVerticalTotal() + padding.GetVerticalTotal()}; - - if (is_border_enabled_) { - margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); - margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); - } - - const auto content_available_size = - rect.GetSize() - margin_border_padding_size; - auto coerced_content_available_size = content_available_size; - - if (coerced_content_available_size.width < 0) { - log::Warn( - "Layout: horizontal length of padding, border and margin is bigger " - "than available length."); - coerced_content_available_size.width = 0; - } - if (coerced_content_available_size.height < 0) { - log::Warn( - "Layout: vertical length of padding, border and margin is bigger " - "than available length."); - coerced_content_available_size.height = 0; - } - - OnLayoutContent( - Rect{margin.left + (is_border_enabled_ ? border_thickness_.left : 0) + - padding.left, - margin.top + (is_border_enabled_ ? border_thickness_.top : 0) + - padding.top, - coerced_content_available_size.width, - coerced_content_available_size.height}); -} - -Size BorderRenderObject::OnMeasureContent(const Size& available_size) { - const auto child = GetChild(); - if (child) { - child->Measure(available_size); - return child->GetPreferredSize(); - } else { - return Size{}; - } -} - -void BorderRenderObject::OnLayoutContent(const Rect& content_rect) { - const auto child = GetChild(); - if (child) { - child->Layout(content_rect); - } -} - -void BorderRenderObject::OnAfterLayout() { RecreateGeometry(); } - -void BorderRenderObject::RecreateGeometry() { - geometry_.reset(); - border_outer_geometry_.reset(); - - Thickness t = border_thickness_; - t.left /= 2.0; - t.top /= 2.0; - t.right /= 2.0; - t.bottom /= 2.0; - const CornerRadius& r = border_radius_; - - CornerRadius outer_radius(r.left_top + Point{t.left, t.top}, - r.right_top + Point{t.right, t.top}, - r.left_bottom + Point{t.left, t.bottom}, - r.right_bottom + Point{t.right, t.bottom}); - - CornerRadius inner_radius(r.left_top - Point{t.left, t.top}, - r.right_top - Point{t.right, t.top}, - 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, - 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)); - builder->QuadraticBezierTo( - Point(rect.GetRight(), rect.top), - Point(rect.GetRight(), rect.top + corner.right_top.y)); - builder->LineTo( - Point(rect.GetRight(), rect.GetBottom() - corner.right_bottom.y)); - builder->QuadraticBezierTo( - Point(rect.GetRight(), rect.GetBottom()), - Point(rect.GetRight() - corner.right_bottom.x, rect.GetBottom())); - builder->LineTo(Point(rect.left + corner.left_bottom.x, rect.GetBottom())); - builder->QuadraticBezierTo( - Point(rect.left, rect.GetBottom()), - Point(rect.left, rect.GetBottom() - corner.left_bottom.y)); - builder->LineTo(Point(rect.left, rect.top + corner.left_top.y)); - builder->QuadraticBezierTo(Point(rect.left, rect.top), - Point(rect.left + corner.left_top.x, rect.top)); - builder->CloseFigure(true); - }; - - const auto size = GetSize(); - const auto margin = GetMargin(); - const Rect outer_rect{margin.left, margin.top, - size.width - margin.GetHorizontalTotal(), - size.height - margin.GetVerticalTotal()}; - const auto graph_factory = GetGraphFactory(); - std::unique_ptr builder{ - graph_factory->CreateGeometryBuilder()}; - f(builder.get(), outer_rect, outer_radius); - border_outer_geometry_ = builder->Build(); - builder.reset(); - - const Rect inner_rect = outer_rect.Shrink(border_thickness_); - - builder = graph_factory->CreateGeometryBuilder(); - f(builder.get(), inner_rect, inner_radius); - border_inner_geometry_ = builder->Build(); - builder.reset(); - - builder = graph_factory->CreateGeometryBuilder(); - f(builder.get(), outer_rect, outer_radius); - f(builder.get(), inner_rect, inner_radius); - geometry_ = builder->Build(); - builder.reset(); -} -} // namespace cru::ui::render diff --git a/src/ui/render/canvas_render_object.cpp b/src/ui/render/canvas_render_object.cpp deleted file mode 100644 index 0508e276..00000000 --- a/src/ui/render/canvas_render_object.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "cru/ui/render/canvas_render_object.hpp" - -#include "cru/ui/render/layout_utility.hpp" - -namespace cru::ui::render { -CanvasRenderObject::CanvasRenderObject() : RenderObject(ChildMode::None) {} - -CanvasRenderObject::~CanvasRenderObject() = default; - -void CanvasRenderObject::Draw(platform::graph::IPainter* painter) { - const auto rect = GetContentRect(); - CanvasPaintEventArgs args{painter, rect}; - paint_event_.Raise(args); -} - -RenderObject* CanvasRenderObject::HitTest(const Point& point) { - const auto padding_rect = GetPaddingRect(); - return padding_rect.IsPointInside(point) ? this : nullptr; -} - -Size CanvasRenderObject::OnMeasureContent(const Size& available_size) { - return Min(available_size, GetDesiredSize()); -} - -void CanvasRenderObject::OnLayoutContent(const Rect& content_rect) { - CRU_UNUSED(content_rect) -} -} // namespace cru::ui::render diff --git a/src/ui/render/flex_layout_render_object.cpp b/src/ui/render/flex_layout_render_object.cpp deleted file mode 100644 index f40a9b3e..00000000 --- a/src/ui/render/flex_layout_render_object.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include "cru/ui/render/flex_layout_render_object.hpp" - -#include "cru/platform/graph/util/painter.hpp" - -#include -#include - -namespace cru::ui::render { -Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) { - std::vector has_basis_children; - std::vector no_basis_children; - std::vector grow_children; - std::vector shrink_chilren; - const auto child_count = GetChildCount(); - for (int i = 0; i < child_count; i++) { - const auto& layout_data = *GetChildLayoutData(i); - if (layout_data.flex_basis.has_value()) - has_basis_children.push_back(i); - else - no_basis_children.push_back(i); - if (layout_data.flex_grow > 0) grow_children.push_back(i); - if (layout_data.flex_shrink > 0) shrink_chilren.push_back(i); - } - - std::function get_main_length; - std::function get_cross_length; - std::function create_size; - - if (direction_ == FlexDirection::Horizontal || - direction_ == FlexDirection::HorizontalReverse) { - get_main_length = [](const Size& size) { return size.width; }; - get_cross_length = [](const Size& size) { return size.height; }; - create_size = [](float main, float cross) { return Size(main, cross); }; - } else { - get_main_length = [](const Size& size) { return size.height; }; - get_cross_length = [](const Size& size) { return size.width; }; - create_size = [](float main, float cross) { return Size(cross, main); }; - } - - const auto& children = GetChildren(); - - float remain_main_length = get_main_length(available_size); - float max_cross_length = 0; - - for (const int i : has_basis_children) { - const auto child = children[i]; - const float basis = GetChildLayoutData(i)->flex_basis.value(); - child->Measure(create_size(basis, get_cross_length(available_size))); - remain_main_length -= basis; - const float child_preferred_cross_length = - get_cross_length(child->GetPreferredSize()); - child->SetPreferredSize(create_size(basis, child_preferred_cross_length)); - max_cross_length = std::max(max_cross_length, child_preferred_cross_length); - } - - for (const int i : no_basis_children) { - const auto child = children[i]; - child->Measure(create_size(remain_main_length > 0 ? remain_main_length : 0, - get_cross_length(available_size))); - remain_main_length -= get_main_length(child->GetPreferredSize()); - max_cross_length = - std::max(max_cross_length, get_cross_length(child->GetPreferredSize())); - } - - if (remain_main_length > 0) { - float total_grow = 0; - for (const int i : grow_children) - total_grow += GetChildLayoutData(i)->flex_grow; - - for (const int i : grow_children) { - const float distributed_grow_length = - remain_main_length * (GetChildLayoutData(i)->flex_grow / total_grow); - const auto child = children[i]; - const float new_main_length = - get_main_length(child->GetPreferredSize()) + distributed_grow_length; - child->Measure( - create_size(new_main_length, get_cross_length(available_size))); - const float new_child_preferred_cross_length = - get_cross_length(child->GetPreferredSize()); - child->SetPreferredSize( - create_size(new_main_length, new_child_preferred_cross_length)); - max_cross_length = - std::max(max_cross_length, new_child_preferred_cross_length); - } - } - - if (remain_main_length < 0) { - float total_shrink = 0; - for (const int i : shrink_chilren) - total_shrink += GetChildLayoutData(i)->flex_shrink; - - for (const int i : shrink_chilren) { - const float distributed_shrink_length = // negative - remain_main_length * - (GetChildLayoutData(i)->flex_shrink / total_shrink); - const auto child = children[i]; - float new_main_length = get_main_length(child->GetPreferredSize()) + - distributed_shrink_length; - new_main_length = new_main_length > 0 ? new_main_length : 0; - child->Measure( - create_size(new_main_length, get_cross_length(available_size))); - const float new_child_preferred_cross_length = - get_cross_length(child->GetPreferredSize()); - child->SetPreferredSize( - create_size(new_main_length, new_child_preferred_cross_length)); - max_cross_length = - std::max(max_cross_length, new_child_preferred_cross_length); - } - } - - return create_size(get_main_length(available_size) - - (remain_main_length > 0 ? remain_main_length : 0), - max_cross_length); -} - -void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { - auto calculate_anchor = [](int alignment, float start_point, - float total_length, - float content_length) -> float { - switch (alignment) { - case internal::align_start: - return start_point; - case internal::align_center: - return start_point + (total_length - content_length) / 2.0f; - case internal::align_end: - return start_point + total_length - content_length; - default: - return 0; - } - }; - - const auto& children = GetChildren(); - if (direction_ == FlexDirection::Horizontal || - direction_ == FlexDirection::HorizontalReverse) { - float actual_content_width = 0; - for (const auto child : children) { - actual_content_width += child->GetPreferredSize().width; - } - - const float content_anchor_x = - calculate_anchor(static_cast(content_main_align_), 0, - content_rect.width, actual_content_width); - - float anchor_x = 0; - for (int i = 0; i < static_cast(children.size()); i++) { - const auto child = children[i]; - const auto size = child->GetPreferredSize(); - - float real_anchor_x = anchor_x + content_anchor_x; - if (direction_ == FlexDirection::Horizontal) - real_anchor_x = content_rect.left + real_anchor_x; - else - real_anchor_x = content_rect.GetRight() - real_anchor_x; - child->Layout(Rect{ - real_anchor_x, - calculate_anchor( - static_cast(GetChildLayoutData(i)->cross_alignment.value_or( - this->item_cross_align_)), - content_rect.top, content_rect.height, size.height), - size.width, size.height}); - - anchor_x += size.width; - } - } else { - float actual_content_height = 0; - for (const auto child : children) { - actual_content_height = child->GetPreferredSize().height; - } - - const float content_anchor_y = - calculate_anchor(static_cast(content_main_align_), 0, - content_rect.height, actual_content_height); - - float anchor_y = 0; - for (int i = 0; i < static_cast(children.size()); i++) { - const auto child = children[i]; - const auto size = child->GetPreferredSize(); - - float real_anchor_y = anchor_y + content_anchor_y; - if (direction_ == FlexDirection::Vertical) { - real_anchor_y = content_rect.top + real_anchor_y; - } else { - real_anchor_y = content_rect.GetBottom() - real_anchor_y; - } - child->Layout(Rect{ - real_anchor_y, - calculate_anchor( - static_cast(GetChildLayoutData(i)->cross_alignment.value_or( - this->item_cross_align_)), - content_rect.left, content_rect.width, size.width), - size.width, size.height}); - - anchor_y += size.height; - } - } -} -} // namespace cru::ui::render diff --git a/src/ui/render/layout_utility.cpp b/src/ui/render/layout_utility.cpp deleted file mode 100644 index aae62d35..00000000 --- a/src/ui/render/layout_utility.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "cru/ui/render/layout_utility.hpp" - -#include - -namespace cru::ui::render { -Size Min(const Size& left, const Size& right) { - return Size{std::min(left.width, right.width), - std::min(left.height, right.height)}; -} - -Size Max(const Size& left, const Size& right) { - return Size{std::max(left.width, right.width), - std::max(left.height, right.height)}; -} -} // namespace cru::ui::render diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp deleted file mode 100644 index 4a1bedf3..00000000 --- a/src/ui/render/render_object.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "cru/ui/render/render_object.hpp" - -#include "cru/common/logger.hpp" -#include "cru/ui/ui_host.hpp" - -#include - -namespace cru::ui::render { -void RenderObject::AddChild(RenderObject* render_object, const Index position) { - Expects(child_mode_ != ChildMode::None); - Expects(!(child_mode_ == ChildMode::Single && children_.size() > 0)); - - Expects(render_object->GetParent() == - nullptr); // Render object already has a parent. - Expects(position >= 0); // Position index is less than 0. - Expects( - position <= - static_cast(children_.size())); // Position index is out of bound. - - children_.insert(children_.cbegin() + position, render_object); - render_object->SetParent(this); - render_object->SetRenderHostRecursive(GetUiHost()); - OnAddChild(render_object, position); -} - -void RenderObject::RemoveChild(const Index position) { - Expects(position >= 0); // Position index is less than 0. - Expects(position < static_cast( - children_.size())); // Position index is out of bound. - - const auto i = children_.cbegin() + position; - const auto removed_child = *i; - children_.erase(i); - removed_child->SetParent(nullptr); - removed_child->SetRenderHostRecursive(nullptr); - OnRemoveChild(removed_child, position); -} - -Point RenderObject::GetTotalOffset() const { - Point result{}; - const RenderObject* render_object = this; - - while (render_object != nullptr) { - const auto o = render_object->GetOffset(); - result.x += o.x; - result.y += o.y; - render_object = render_object->GetParent(); - } - - return result; -} - -Point RenderObject::FromRootToContent(const Point& point) const { - const auto offset = GetTotalOffset(); - const auto rect = GetContentRect(); - return Point{point.x - (offset.x + rect.left), - point.y - (offset.y + rect.top)}; -} - -void RenderObject::Measure(const Size& available_size) { - OnMeasureCore(available_size); -} - -void RenderObject::Layout(const Rect& rect) { - SetOffset(rect.GetLeftTop()); - SetSize(rect.GetSize()); - OnLayoutCore(Rect{Point{}, rect.GetSize()}); -} - -void RenderObject::OnParentChanged(RenderObject* old_parent, - RenderObject* new_parent) { - CRU_UNUSED(old_parent) - CRU_UNUSED(new_parent) -} - -void RenderObject::OnAddChild(RenderObject* new_child, Index position) { - CRU_UNUSED(new_child) - CRU_UNUSED(position) - - InvalidateLayout(); - InvalidatePaint(); -} - -void RenderObject::OnRemoveChild(RenderObject* removed_child, Index position) { - CRU_UNUSED(removed_child) - CRU_UNUSED(position) - - InvalidateLayout(); - InvalidatePaint(); -} - -void RenderObject::OnMeasureCore(const Size& available_size) { - Size margin_padding_size{ - margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), - margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; - - auto coerced_margin_padding_size = margin_padding_size; - if (coerced_margin_padding_size.width > available_size.width) { - log::Warn( - "Measure: horizontal length of padding and margin is bigger than " - "available length."); - coerced_margin_padding_size.width = available_size.width; - } - if (coerced_margin_padding_size.height > available_size.height) { - log::Warn( - "Measure: vertical length of padding and margin is bigger than " - "available length."); - coerced_margin_padding_size.height = available_size.height; - } - - const auto coerced_content_available_size = - available_size - coerced_margin_padding_size; - const auto actual_content_size = - OnMeasureContent(coerced_content_available_size); - - SetPreferredSize(coerced_margin_padding_size + actual_content_size); -} - -void RenderObject::OnLayoutCore(const Rect& rect) { - Size margin_padding_size{ - margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), - margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; - const auto content_available_size = rect.GetSize() - margin_padding_size; - auto coerced_content_available_size = content_available_size; - - if (coerced_content_available_size.width < 0) { - log::Warn( - "Layout: horizontal length of padding and margin is bigger than " - "available length."); - coerced_content_available_size.width = 0; - } - if (coerced_content_available_size.height < 0) { - log::Warn( - "Layout: vertical length of padding and margin is bigger than " - "available length."); - coerced_content_available_size.height = 0; - } - - OnLayoutContent(Rect{margin_.left + padding_.left, margin_.top + padding_.top, - coerced_content_available_size.width, - coerced_content_available_size.height}); -} - -void RenderObject::OnAfterLayout() {} - -Rect RenderObject::GetPaddingRect() const { - Rect rect{Point{}, GetSize()}; - rect = rect.Shrink(GetMargin()); - rect.width = std::max(rect.width, 0.0f); - rect.height = std::max(rect.height, 0.0f); - return rect; -} - -Rect RenderObject::GetContentRect() const { - Rect rect{Point{}, GetSize()}; - rect = rect.Shrink(GetMargin()); - rect = rect.Shrink(GetPadding()); - rect.width = std::max(rect.width, 0.0f); - rect.height = std::max(rect.height, 0.0f); - return rect; -} - -void RenderObject::SetParent(RenderObject* new_parent) { - const auto old_parent = parent_; - parent_ = new_parent; - OnParentChanged(old_parent, new_parent); -} - -void RenderObject::InvalidateLayout() { - if (ui_host_ != nullptr) ui_host_->InvalidateLayout(); -} - -void RenderObject::InvalidatePaint() { - if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); -} - -void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { - render_object->OnAfterLayout(); - for (const auto o : render_object->GetChildren()) { - NotifyAfterLayoutRecursive(o); - } -} - -void RenderObject::SetRenderHostRecursive(UiHost* host) { - ui_host_ = host; - for (const auto child : GetChildren()) { - child->SetRenderHostRecursive(host); - } -} -} // namespace cru::ui::render diff --git a/src/ui/render/scroll_render_object.cpp b/src/ui/render/scroll_render_object.cpp deleted file mode 100644 index f77ee636..00000000 --- a/src/ui/render/scroll_render_object.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "cru/ui/render/scroll_render_object.hpp" diff --git a/src/ui/render/stack_layout_render_object.cpp b/src/ui/render/stack_layout_render_object.cpp deleted file mode 100644 index 1cb31252..00000000 --- a/src/ui/render/stack_layout_render_object.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "cru/ui/render/stack_layout_render_object.hpp" - -#include - -namespace cru::ui::render { -Size StackLayoutRenderObject::OnMeasureContent(const Size& available_size) { - auto size = Size{}; - for (const auto child : GetChildren()) { - child->Measure(available_size); - const auto& preferred_size = child->GetPreferredSize(); - size.width = std::max(size.width, preferred_size.width); - size.height = std::max(size.height, preferred_size.height); - } - return size; -} - -void StackLayoutRenderObject::OnLayoutContent(const Rect& rect) { - auto calculate_anchor = [](int alignment, float start_point, - float total_length, - float content_length) -> float { - switch (alignment) { - case internal::align_start: - return start_point; - case internal::align_center: - return start_point + (total_length - content_length) / 2.0f; - case internal::align_end: - return start_point + total_length - content_length; - default: - return 0; - } - }; - - const auto count = GetChildCount(); - const auto& children = GetChildren(); - - for (int i = 0; i < count; i++) { - const auto layout_data = GetChildLayoutData(i); - const auto child = children[i]; - const auto& size = child->GetPreferredSize(); - child->Layout( - Rect{calculate_anchor(static_cast(layout_data->horizontal), - rect.left, rect.width, size.width), - calculate_anchor(static_cast(layout_data->vertical), rect.top, - rect.height, size.height), - size.width, size.height}); - } -} - -} // namespace cru::ui::render diff --git a/src/ui/render/text_render_object.cpp b/src/ui/render/text_render_object.cpp deleted file mode 100644 index cd670db1..00000000 --- a/src/ui/render/text_render_object.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include "cru/ui/render/text_render_object.hpp" - -#include "../helper.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/graph/text_layout.hpp" -#include "cru/platform/graph/util/painter.hpp" - -#include - -namespace cru::ui::render { -TextRenderObject::TextRenderObject( - std::shared_ptr brush, - std::shared_ptr font, - std::shared_ptr selection_brush, - std::shared_ptr caret_brush) { - Expects(brush); - Expects(font); - Expects(selection_brush); - Expects(caret_brush); - - SetChildMode(ChildMode::None); - - brush.swap(brush_); - font.swap(font_); - selection_brush.swap(selection_brush_); - caret_brush.swap(caret_brush_); - - const auto graph_factory = GetGraphFactory(); - text_layout_ = graph_factory->CreateTextLayout(font_, ""); -} - -TextRenderObject::~TextRenderObject() = default; - -std::string TextRenderObject::GetText() const { - return text_layout_->GetText(); -} - -void TextRenderObject::SetText(std::string new_text) { - text_layout_->SetText(std::move(new_text)); -} - -void TextRenderObject::SetBrush( - std::shared_ptr new_brush) { - Expects(new_brush); - new_brush.swap(brush_); - InvalidatePaint(); -} - -std::shared_ptr TextRenderObject::GetFont() const { - return text_layout_->GetFont(); -} - -void TextRenderObject::SetFont(std::shared_ptr font) { - Expects(font); - text_layout_->SetFont(std::move(font)); -} - -std::vector TextRenderObject::TextRangeRect(const TextRange& text_range) { - return text_layout_->TextRangeRect(text_range); -} - -Point TextRenderObject::TextSinglePoint(gsl::index position, bool trailing) { - return text_layout_->TextSinglePoint(position, trailing); -} - -platform::graph::TextHitTestResult TextRenderObject::TextHitTest( - const Point& point) { - return text_layout_->HitTest(point); -} - -void TextRenderObject::SetSelectionRange(std::optional new_range) { - selection_range_ = std::move(new_range); - InvalidatePaint(); -} - -void TextRenderObject::SetSelectionBrush( - std::shared_ptr new_brush) { - Expects(new_brush); - new_brush.swap(selection_brush_); - if (selection_range_ && selection_range_->count) { - InvalidatePaint(); - } -} - -void TextRenderObject::SetDrawCaret(bool draw_caret) { - if (draw_caret_ != draw_caret) { - draw_caret_ = draw_caret; - InvalidatePaint(); - } -} - -void TextRenderObject::SetCaretPosition(gsl::index position) { - if (position != caret_position_) { - caret_position_ = position; - if (draw_caret_) { - InvalidatePaint(); - } - } -} - -void TextRenderObject::GetCaretBrush( - std::shared_ptr brush) { - Expects(brush); - brush.swap(caret_brush_); - if (draw_caret_) { - InvalidatePaint(); - } -} - -void TextRenderObject::SetCaretWidth(const float width) { - Expects(width >= 0.0f); - - caret_width_ = width; - if (draw_caret_) { - InvalidatePaint(); - } -} - -void TextRenderObject::Draw(platform::graph::IPainter* painter) { - platform::graph::util::WithTransform( - painter, - platform::Matrix::Translation(GetMargin().left + GetPadding().left, - GetMargin().top + GetPadding().top), - [this](platform::graph::IPainter* p) { - if (this->selection_range_.has_value()) { - const auto&& rects = - text_layout_->TextRangeRect(this->selection_range_.value()); - for (const auto& rect : rects) - p->FillRectangle(rect, this->GetSelectionBrush().get()); - } - - p->DrawText(Point{}, text_layout_.get(), brush_.get()); - - if (this->draw_caret_ && this->caret_width_ != 0.0f) { - auto caret_pos = this->caret_position_; - gsl::index text_size = this->GetText().size(); - if (caret_pos < 0) { - caret_pos = 0; - } else if (caret_pos > text_size) { - caret_pos = text_size; - } - - const auto caret_top_center = - this->text_layout_->TextSinglePoint(caret_pos, false); - - const auto font_height = this->font_->GetFontSize(); - const auto caret_width = this->caret_width_; - - p->FillRectangle(Rect{caret_top_center.x - caret_width / 2.0f, - caret_top_center.y, caret_width, font_height}, - this->caret_brush_.get()); - } - }); -} - -RenderObject* TextRenderObject::HitTest(const Point& point) { - const auto padding_rect = GetPaddingRect(); - return padding_rect.IsPointInside(point) ? this : nullptr; -} - -Size TextRenderObject::OnMeasureContent(const Size& available_size) { - text_layout_->SetMaxWidth(available_size.width); - text_layout_->SetMaxHeight(available_size.height); - return text_layout_->GetTextBounds().GetSize(); -} - -void TextRenderObject::OnLayoutContent(const Rect& content_rect) { - CRU_UNUSED(content_rect) -} - -void TextRenderObject::OnAfterLayout() { - const auto&& size = GetContentRect().GetSize(); - text_layout_->SetMaxWidth(size.width); - text_layout_->SetMaxHeight(size.height); -} - -} // namespace cru::ui::render diff --git a/src/ui/render/window_render_object.cpp b/src/ui/render/window_render_object.cpp deleted file mode 100644 index abed6fa4..00000000 --- a/src/ui/render/window_render_object.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "cru/ui/render/window_render_object.hpp" - -#include "../helper.hpp" -#include "cru/platform/graph/util/painter.hpp" -#include "cru/ui/ui_host.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); })); -} - -void WindowRenderObject::Draw(platform::graph::IPainter* painter) { - painter->Clear(colors::white); - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - platform::graph::util::WithTransform( - painter, platform::Matrix::Translation(offset.x, offset.y), - [child](platform::graph::IPainter* p) { child->Draw(p); }); - } -} - -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 Size& available_size) { - if (const auto child = GetChild()) child->Measure(available_size); - return available_size; -} - -void WindowRenderObject::OnLayoutContent(const Rect& content_rect) { - if (const auto child = GetChild()) child->Layout(content_rect); -} -} // namespace cru::ui::render diff --git a/src/ui/routed_event_dispatch.hpp b/src/ui/routed_event_dispatch.hpp deleted file mode 100644 index dd55ce17..00000000 --- a/src/ui/routed_event_dispatch.hpp +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once -#include "cru/ui/control.hpp" - -#include "cru/common/logger.hpp" - -#include - -namespace cru::ui { -// Dispatch the event. -// -// This will raise routed event of the control and its parent and parent's -// parent ... (until "last_receiver" if it's not nullptr) with appropriate args. -// -// First tunnel from top to bottom possibly stopped by "handled" flag in -// EventArgs. Second bubble from bottom to top possibly stopped by "handled" -// flag in EventArgs. Last direct to each control. -// -// Args is of type "EventArgs". The first init argument is "sender", which is -// automatically bound to each receiving control. The second init argument is -// "original_sender", which is unchanged. And "args" will be perfectly forwarded -// as the rest arguments. -template -void DispatchEvent(const std::string_view& event_name, - Control* const original_sender, - event::RoutedEvent* (Control::*event_ptr)(), - Control* const last_receiver, Args&&... args) { -#ifndef CRU_DEBUG - CRU_UNUSED(event_name) -#endif - -#ifdef CRU_DEBUG - bool do_log = true; - if (event_name == "MouseMove") do_log = false; -#endif - - if (original_sender == last_receiver) { - /* - #ifdef CRU_DEBUG - if (do_log) - log::Debug( - "Routed event {} no need to dispatch (original_sender == " - "last_receiver). Original sender is {}.", - event_name, original_sender->GetControlType()); - #endif - */ - return; - } - - std::list receive_list; - - auto parent = original_sender; - while (parent != last_receiver) { - receive_list.push_back(parent); - parent = parent->GetParent(); - } - -#ifdef CRU_DEBUG - if (do_log) { - std::string log = "Dispatch routed event "; - log += event_name; - log += ". Path (parent first): "; - auto i = receive_list.crbegin(); - const auto end = --receive_list.crend(); - for (; i != end; ++i) { - log += (*i)->GetControlType(); - log += " -> "; - } - log += (*i)->GetControlType(); - log::Debug(log); - } -#endif - - auto handled = false; - -#ifdef CRU_DEBUG - int count = 0; -#endif - - // tunnel - for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { -#ifdef CRU_DEBUG - count++; -#endif - EventArgs event_args(*i, original_sender, std::forward(args)...); - static_cast*>(((*i)->*event_ptr)()->Tunnel()) - ->Raise(event_args); - if (event_args.IsHandled()) { - handled = true; -#ifdef CRU_DEBUG - if (do_log) - log::Debug( - "Routed event is short-circuit in TUNNEL at {}-st control (count " - "from parent).", - count); -#endif - break; - } - } - - // bubble - if (!handled) { - for (auto i : receive_list) { -#ifdef CRU_DEBUG - count--; -#endif - EventArgs event_args(i, original_sender, std::forward(args)...); - static_cast*>((i->*event_ptr)()->Bubble()) - ->Raise(event_args); - if (event_args.IsHandled()) { -#ifdef CRU_DEBUG - if (do_log) - log::Debug( - "Routed event is short-circuit in BUBBLE at {}-st control " - "(count " - "from parent).", - count); -#endif - break; - } - } - } - - // direct - for (auto i : receive_list) { - EventArgs event_args(i, original_sender, std::forward(args)...); - static_cast*>((i->*event_ptr)()->Direct()) - ->Raise(event_args); - } - -#ifdef CRU_DEBUG - if (do_log) log::Debug("Routed event dispatch finished."); -#endif -} -} // namespace cru::ui diff --git a/src/ui/ui_host.cpp b/src/ui/ui_host.cpp deleted file mode 100644 index 8b85ad30..00000000 --- a/src/ui/ui_host.cpp +++ /dev/null @@ -1,368 +0,0 @@ -#include "cru/ui/ui_host.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/graph/painter.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/platform/native/window.hpp" -#include "cru/ui/render/window_render_object.hpp" -#include "cru/ui/window.hpp" -#include "routed_event_dispatch.hpp" - -namespace cru::ui { -using platform::native::INativeWindow; -using platform::native::IUiApplication; - -namespace event_names { -#ifdef CRU_DEBUG -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = #name; -#else -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = ""; -#endif - -CRU_DEFINE_EVENT_NAME(LoseFocus) -CRU_DEFINE_EVENT_NAME(GainFocus) -CRU_DEFINE_EVENT_NAME(MouseEnter) -CRU_DEFINE_EVENT_NAME(MouseLeave) -CRU_DEFINE_EVENT_NAME(MouseMove) -CRU_DEFINE_EVENT_NAME(MouseDown) -CRU_DEFINE_EVENT_NAME(MouseUp) -CRU_DEFINE_EVENT_NAME(KeyDown) -CRU_DEFINE_EVENT_NAME(KeyUp) -CRU_DEFINE_EVENT_NAME(Char) - -#undef CRU_DEFINE_EVENT_NAME -} // namespace event_names - -namespace { -bool IsAncestor(Control* control, Control* ancestor) { - while (control != nullptr) { - if (control == ancestor) return true; - control = control->GetParent(); - } - return false; -} - -std::list GetAncestorList(Control* control) { - std::list l; - while (control != nullptr) { - l.push_front(control); - control = control->GetParent(); - } - return l; -} - -Control* FindLowestCommonAncestor(Control* left, Control* right) { - if (left == nullptr || right == nullptr) return nullptr; - - auto&& left_list = GetAncestorList(left); - auto&& right_list = GetAncestorList(right); - - // the root is different - if (left_list.front() != right_list.front()) return nullptr; - - // find the last same control or the last control (one is ancestor of the - // other) - auto left_i = left_list.cbegin(); - auto right_i = right_list.cbegin(); - - while (true) { - if (left_i == left_list.cend()) { - return *(--left_i); - } - if (right_i == right_list.cend()) { - return *(--right_i); - } - if (*left_i != *right_i) { - return *(--left_i); - } - ++left_i; - ++right_i; - } -} -} // namespace - -namespace { -template -inline void BindNativeEvent( - UiHost* host, INativeWindow* native_window, IEvent* event, - void (UiHost::*handler)(INativeWindow*, typename IEvent::EventArgs), - std::vector& guard_pool) { - guard_pool.push_back(EventRevokerGuard(event->AddHandler( - std::bind(handler, host, native_window, std::placeholders::_1)))); -} -} // namespace - -UiHost::UiHost(Window* window) - : mouse_hover_control_(nullptr), - focus_control_(window), - mouse_captured_control_(nullptr), - window_control_(window) { - native_window_resolver_ = - IUiApplication::GetInstance()->CreateWindow(nullptr); - - const auto native_window = native_window_resolver_->Resolve(); - window->ui_host_ = this; - - root_render_object_ = std::make_unique(this); - root_render_object_->SetAttachedControl(window); - window->render_object_ = root_render_object_.get(); - - BindNativeEvent(this, native_window, native_window->DestroyEvent(), - &UiHost::OnNativeDestroy, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->PaintEvent(), - &UiHost::OnNativePaint, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->ResizeEvent(), - &UiHost::OnNativeResize, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->FocusEvent(), - &UiHost::OnNativeFocus, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), - &UiHost::OnNativeMouseEnterLeave, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), - &UiHost::OnNativeMouseMove, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseDownEvent(), - &UiHost::OnNativeMouseDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseUpEvent(), - &UiHost::OnNativeMouseUp, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyDownEvent(), - &UiHost::OnNativeKeyDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyUpEvent(), - &UiHost::OnNativeKeyUp, event_revoker_guards_); -} - -UiHost::~UiHost() { - deleting_ = true; - window_control_->TraverseDescendants( - [this](Control* control) { control->OnDetachFromHost(this); }); - if (!native_window_destroyed_) { - const auto native_window = native_window_resolver_->Resolve(); - if (native_window) { - native_window->Close(); - } - } -} - -void UiHost::InvalidatePaint() { - if (const auto native_window = native_window_resolver_->Resolve()) - native_window->RequestRepaint(); -} - -void UiHost::InvalidateLayout() { - if (!need_layout_) { - platform::native::IUiApplication::GetInstance()->InvokeLater( - [resolver = this->CreateResolver()] { - if (const auto host = resolver.Resolve()) { - host->Relayout(); - host->need_layout_ = false; - host->after_layout_event_.Raise(AfterLayoutEventArgs{}); - log::Debug("A relayout finished."); - host->InvalidatePaint(); - } - }); - need_layout_ = true; - } -} - -void UiHost::Relayout() { - const auto native_window = native_window_resolver_->Resolve(); - const auto client_size = native_window - ? native_window->GetClientSize() - : Size{100, 100}; // a reasonable assumed size - root_render_object_->Measure(client_size); - root_render_object_->Layout(Rect{Point{}, client_size}); -} - -bool UiHost::RequestFocusFor(Control* control) { - Expects(control != nullptr); // The control to request focus can't be null. - // You can set it as the window. - - if (focus_control_ == control) return true; - - DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, false); - - focus_control_ = control; - - DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, - nullptr, false); - - return true; -} - -Control* UiHost::GetFocusControl() { return focus_control_; } - -bool UiHost::CaptureMouseFor(Control* control) { - const auto native_window = native_window_resolver_->Resolve(); - if (!native_window) return false; - - if (control == mouse_captured_control_) return true; - - if (control == nullptr) { - const auto old_capture_control = mouse_captured_control_; - mouse_captured_control_ = - nullptr; // update this in case this is used in event handlers - if (old_capture_control != mouse_hover_control_) { - DispatchMouseHoverControlChangeEvent( - old_capture_control, mouse_hover_control_, - native_window->GetMousePosition(), true, false); - } - UpdateCursor(); - return true; - } - - if (mouse_captured_control_) return false; - - mouse_captured_control_ = control; - DispatchMouseHoverControlChangeEvent( - mouse_hover_control_, mouse_captured_control_, - native_window->GetMousePosition(), false, true); - UpdateCursor(); - return true; -} - -Control* UiHost::GetMouseCaptureControl() { return mouse_captured_control_; } - -void UiHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { - CRU_UNUSED(window) - native_window_destroyed_ = true; - if (!deleting_ && !retain_after_destroy_) delete window_control_; -} - -void UiHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { - auto painter = window->BeginPaint(); - root_render_object_->Draw(painter.get()); - painter->EndDraw(); -} - -void UiHost::OnNativeResize(INativeWindow* window, const Size& size) { - CRU_UNUSED(window) - CRU_UNUSED(size) - - InvalidateLayout(); -} - -void UiHost::OnNativeFocus(INativeWindow* window, - platform::native::FocusChangeType focus) { - CRU_UNUSED(window) - - focus == platform::native::FocusChangeType::Gain - ? DispatchEvent(event_names::GainFocus, focus_control_, - &Control::GainFocusEvent, nullptr, true) - : DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, true); -} - -void UiHost::OnNativeMouseEnterLeave( - INativeWindow* window, platform::native::MouseEnterLeaveType type) { - CRU_UNUSED(window) - - if (type == platform::native::MouseEnterLeaveType::Leave) { - DispatchEvent(event_names::MouseLeave, mouse_hover_control_, - &Control::MouseLeaveEvent, nullptr); - mouse_hover_control_ = nullptr; - } -} - -void UiHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { - CRU_UNUSED(window) - - // Find the first control that hit test succeed. - const auto new_mouse_hover_control = HitTest(point); - const auto old_mouse_hover_control = mouse_hover_control_; - mouse_hover_control_ = new_mouse_hover_control; - - if (mouse_captured_control_) { - const auto n = FindLowestCommonAncestor(new_mouse_hover_control, - mouse_captured_control_); - const auto o = FindLowestCommonAncestor(old_mouse_hover_control, - mouse_captured_control_); - bool a = IsAncestor(o, n); - if (a) { - DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); - } else { - DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, - point); - } - DispatchEvent(event_names::MouseMove, mouse_captured_control_, - &Control::MouseMoveEvent, nullptr, point); - UpdateCursor(); - return; - } - - DispatchMouseHoverControlChangeEvent( - old_mouse_hover_control, new_mouse_hover_control, point, false, false); - DispatchEvent(event_names::MouseMove, new_mouse_hover_control, - &Control::MouseMoveEvent, nullptr, point); - UpdateCursor(); -} - -void UiHost::OnNativeMouseDown( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, - nullptr, args.point, args.button, args.modifier); -} - -void UiHost::OnNativeMouseUp( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, - args.point, args.button, args.modifier); -} - -void UiHost::OnNativeKeyDown(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, - nullptr, args.key, args.modifier); -} - -void UiHost::OnNativeKeyUp(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, - nullptr, args.key, args.modifier); -} - -void UiHost::DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, - const Point& point, - bool no_leave, - bool no_enter) { - if (new_control != old_control) // if the mouse-hover-on control changed - { - const auto lowest_common_ancestor = - FindLowestCommonAncestor(old_control, new_control); - if (!no_leave && old_control != nullptr) - DispatchEvent(event_names::MouseLeave, old_control, - &Control::MouseLeaveEvent, - lowest_common_ancestor); // dispatch mouse leave event. - if (!no_enter && new_control != nullptr) { - DispatchEvent(event_names::MouseEnter, new_control, - &Control::MouseEnterEvent, lowest_common_ancestor, - point); // dispatch mouse enter event. - } - } -} - -void UiHost::UpdateCursor() { - if (const auto native_window = native_window_resolver_->Resolve()) { - const auto capture = GetMouseCaptureControl(); - native_window->SetCursor( - (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); - } -} - -Control* UiHost::HitTest(const Point& point) { - return root_render_object_->HitTest(point)->GetAttachedControl(); -} -} // namespace cru::ui diff --git a/src/ui/ui_manager.cpp b/src/ui/ui_manager.cpp deleted file mode 100644 index 905d29ad..00000000 --- a/src/ui/ui_manager.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "cru/ui/ui_manager.hpp" - -#include "cru/platform/graph/brush.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/graph/font.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "helper.hpp" - -namespace cru::ui { -using namespace cru::platform::graph; - -namespace { -std::unique_ptr CreateSolidColorBrush(IGraphFactory* factory, - const Color& color) { - auto brush = factory->CreateSolidColorBrush(); - brush->SetColor(color); - return brush; -} -} // namespace - -UiManager* UiManager::GetInstance() { - static UiManager* instance = new UiManager(); - GetUiApplication()->AddOnQuitHandler([] { - delete instance; - instance = nullptr; - }); - return instance; -} - -UiManager::UiManager() { - const auto factory = GetGraphFactory(); - - theme_resource_.default_font = factory->CreateFont("等线", 24.0f); - - const auto black_brush = std::shared_ptr( - CreateSolidColorBrush(factory, colors::black)); - theme_resource_.text_brush = black_brush; - theme_resource_.text_selection_brush = - CreateSolidColorBrush(factory, colors::skyblue); - theme_resource_.caret_brush = black_brush; - - theme_resource_.button_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)); - theme_resource_.button_style.hover.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)); - theme_resource_.button_style.press.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - theme_resource_.button_style.press_cancel.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - - theme_resource_.button_style.normal.border_thickness = - theme_resource_.button_style.hover.border_thickness = - theme_resource_.button_style.press.border_thickness = - theme_resource_.button_style.press_cancel.border_thickness = - Thickness(3); - - theme_resource_.button_style.normal.border_radius = - theme_resource_.button_style.hover.border_radius = - theme_resource_.button_style.press.border_radius = - theme_resource_.button_style.press_cancel.border_radius = - CornerRadius({5, 5}); - - theme_resource_.text_box_border_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0xced4da)); - theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.hover = - theme_resource_.text_box_border_style.normal; - - theme_resource_.text_box_border_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x495057)); - theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.focus_hover = - theme_resource_.text_box_border_style.focus; -} - -UiManager::~UiManager() = default; -} // namespace cru::ui diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 7c0683af..de7044dd 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -1,7 +1,7 @@ -#include "cru/ui/window.hpp" +#include "cru/ui/Window.hpp" -#include "cru/ui/render/window_render_object.hpp" -#include "cru/ui/ui_host.hpp" +#include "cru/ui/render/WindowRenderObject.hpp" +#include "cru/ui/UiHost.hpp" namespace cru::ui { Window* Window::CreateOverlapped() { -- cgit v1.2.3