aboutsummaryrefslogtreecommitdiff
path: root/src/ui/controls
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-11-18 00:46:27 +0800
committerYuqian Yang <crupest@crupest.life>2025-11-18 00:46:27 +0800
commit6b4edc9be8ec556147c195cf2047d92b9439efd7 (patch)
treea1d7b7d1e821b4e1911fd00761f77a24ee483f4a /src/ui/controls
parentf7c4d19df66c602d74795e98ce2ee4390d06fbb4 (diff)
downloadcru-6b4edc9be8ec556147c195cf2047d92b9439efd7.tar.gz
cru-6b4edc9be8ec556147c195cf2047d92b9439efd7.tar.bz2
cru-6b4edc9be8ec556147c195cf2047d92b9439efd7.zip
Bring back ControlHost and refactor tree management of control.
Diffstat (limited to 'src/ui/controls')
-rw-r--r--src/ui/controls/Control.cpp148
-rw-r--r--src/ui/controls/ControlHost.cpp424
-rw-r--r--src/ui/controls/NoChildControl.cpp10
-rw-r--r--src/ui/controls/TextHostControlService.cpp17
-rw-r--r--src/ui/controls/TreeView.cpp20
-rw-r--r--src/ui/controls/Window.cpp404
6 files changed, 539 insertions, 484 deletions
diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp
index 9c0fc537..02148b72 100644
--- a/src/ui/controls/Control.cpp
+++ b/src/ui/controls/Control.cpp
@@ -1,11 +1,12 @@
#include "cru/ui/controls/Control.h"
+#include "cru/base/Base.h"
#include "cru/base/log/Logger.h"
-#include "cru/ui/controls/Window.h"
-
#include "cru/platform/gui/Cursor.h"
#include "cru/platform/gui/UiApplication.h"
+#include "cru/ui/controls/ControlHost.h"
#include "cru/ui/style/StyleRuleSet.h"
+#include <algorithm>
#include <format>
namespace cru::ui::controls {
@@ -17,25 +18,14 @@ Control::Control() {
style_rule_set_ = std::make_shared<style::StyleRuleSet>();
style_rule_set_bind_ =
std::make_unique<style::StyleRuleSetBind>(this, style_rule_set_);
-
- MouseEnterEvent()->Direct()->AddHandler(
- [this](events::MouseEventArgs&) { this->is_mouse_over_ = true; });
-
- MouseLeaveEvent()->Direct()->AddHandler(
- [this](events::MouseEventArgs&) { this->is_mouse_over_ = false; });
}
Control::~Control() {
- if (auto window = GetWindow()) {
- if (window->IsInEventHandling()) {
- CRU_LOG_TAG_WARN(
- "Better use delete later to delete control during event handling.");
- }
+ if (host_ && host_->IsInEventHandling()) {
+ CRU_LOG_TAG_WARN(
+ "Better use delete later to delete control during event handling.");
}
- if (auto window = GetWindow()) {
- window->NotifyControlDestroyed(this);
- }
RemoveFromParent();
}
@@ -44,23 +34,9 @@ std::string Control::GetDebugId() const {
static_cast<const void*>(this));
}
-Window* Control::GetWindow() {
- auto parent = this;
- while (parent) {
- if (auto window = dynamic_cast<Window*>(parent)) {
- return window;
- }
- parent = parent->GetParent();
- }
- return nullptr;
-}
+ControlHost* Control::GetControlHost() { return host_; }
-void Control::SetParent(Control* parent) {
- if (parent_ == parent) return;
- auto old_parent = parent_;
- parent_ = parent;
- OnParentChanged(old_parent, parent);
-}
+Control* Control::GetParent() { return parent_; }
bool Control::HasAncestor(Control* control) {
auto parent = this;
@@ -71,6 +47,21 @@ bool Control::HasAncestor(Control* control) {
return false;
}
+const std::vector<Control*>& Control::GetChildren() { return children_; }
+
+void Control::RemoveChild(Control* child) {
+ auto iter = std::ranges::find(children_, child);
+ if (iter != children_.cend()) {
+ RemoveChildAt(iter - children_.cbegin());
+ }
+}
+
+void Control::RemoveAllChild() {
+ while (!GetChildren().empty()) {
+ RemoveChildAt(GetChildren().size() - 1);
+ }
+}
+
void Control::RemoveFromParent() {
if (parent_) {
parent_->RemoveChild(this);
@@ -87,39 +78,85 @@ controls::Control* Control::HitTest(const Point& point) {
return nullptr;
}
-bool Control::HasFocus() {
- auto window = GetWindow();
- if (window == nullptr) return false;
+void Control::InsertChildAt(Control* control, Index index) {
+ if (index < 0 || index > children_.size()) {
+ throw Exception("Child control index out of range.");
+ }
+
+ if (control->parent_) {
+ throw Exception("Control already has a parent.");
+ }
+
+ children_.insert(children_.cbegin() + index, control);
+ control->parent_ = this;
+
+ TraverseDescendents([this](Control* control) { control->host_ = host_; },
+ false);
+ if (host_) {
+ host_->NotifyControlParentChange(control, nullptr, this);
+ }
+ control->OnParentChanged(nullptr, this);
+ OnChildInserted(control, index);
- return window->GetFocusControl() == this;
+ if (host_) {
+ host_->InvalidateLayout();
+ }
}
-bool Control::CaptureMouse() {
- auto window = GetWindow();
- if (window == nullptr) return false;
+void Control::RemoveChildAt(Index index) {
+ if (index < 0 || index >= children_.size()) {
+ throw Exception("Child control index out of range.");
+ }
+
+ auto control = children_[index];
+ children_.erase(children_.cbegin() + index);
+ control->parent_ = nullptr;
+ TraverseDescendents([this](Control* control) { control->host_ = nullptr; },
+ false);
+ if (host_) {
+ host_->NotifyControlParentChange(control, this, nullptr);
+ }
+ control->OnParentChanged(this, nullptr);
+ OnChildRemoved(control, index);
- return window->SetMouseCaptureControl(this);
+ if (host_) {
+ host_->InvalidateLayout();
+ }
+}
+
+void Control::AddChild(Control* control) {
+ InsertChildAt(control, GetChildren().size());
+}
+
+bool Control::HasFocus() {
+ if (!host_) return false;
+ return host_->GetFocusControl() == this;
}
void Control::SetFocus() {
- auto window = GetWindow();
- if (window == nullptr) return;
+ if (!host_) return;
+ host_->SetFocusControl(this);
+}
- window->SetFocusControl(this);
+bool Control::IsMouseOver() {
+ if (!host_) return false;
+ return host_->GetMouseHoverControl() == this;
+}
+
+bool Control::CaptureMouse() {
+ if (!host_) return false;
+ return host_->SetMouseCaptureControl(this);
}
bool Control::ReleaseMouse() {
- auto window = GetWindow();
- if (window == nullptr) return false;
- if (window->GetMouseCaptureControl() != this) return false;
- return window->SetMouseCaptureControl(nullptr);
+ if (!host_) return false;
+ if (!IsMouseCaptured()) return false;
+ return host_->SetMouseCaptureControl(nullptr);
}
bool Control::IsMouseCaptured() {
- auto window = GetWindow();
- if (window == nullptr) return false;
-
- return window->GetMouseCaptureControl() == this;
+ if (!host_) return false;
+ return host_->GetMouseCaptureControl() == this;
}
std::shared_ptr<ICursor> Control::GetCursor() { return cursor_; }
@@ -137,13 +174,16 @@ std::shared_ptr<ICursor> Control::GetInheritedCursor() {
void Control::SetCursor(std::shared_ptr<ICursor> cursor) {
cursor_ = std::move(cursor);
- const auto window = GetWindow();
- if (window != nullptr) {
- window->UpdateCursor();
+ if (host_) {
+ host_->UpdateCursor();
}
}
std::shared_ptr<style::StyleRuleSet> Control::GetStyleRuleSet() {
return style_rule_set_;
}
+
+void Control::OnParentChanged(Control* old_parent, Control* new_parent) {}
+void Control::OnChildInserted(Control* control, Index index) {}
+void Control::OnChildRemoved(Control* control, Index index) {}
} // namespace cru::ui::controls
diff --git a/src/ui/controls/ControlHost.cpp b/src/ui/controls/ControlHost.cpp
new file mode 100644
index 00000000..09639465
--- /dev/null
+++ b/src/ui/controls/ControlHost.cpp
@@ -0,0 +1,424 @@
+#include "cru/ui/controls/ControlHost.h"
+
+#include "cru/platform/gui/UiApplication.h"
+#include "cru/platform/gui/Window.h"
+#include "cru/ui/Base.h"
+
+#include <cassert>
+
+namespace cru::ui::controls {
+ControlHost::ControlHost(Control* root_control)
+ : event_handling_count_(0),
+ root_control_(root_control),
+ native_window_(CreateNativeWindow()),
+ focus_control_(root_control),
+ mouse_hover_control_(nullptr),
+ mouse_captured_control_(nullptr),
+ layout_prefer_to_fill_window_(true) {
+ root_control->TraverseDescendents(
+ [this](Control* control) { control->host_ = this; }, true);
+}
+
+ControlHost::~ControlHost() {}
+
+platform::gui::INativeWindow* ControlHost::GetNativeWindow() {
+ return native_window_.get();
+}
+
+namespace {
+template <typename T>
+inline void BindNativeEvent(
+ ControlHost* host, platform::gui::INativeWindow* native_window,
+ IEvent<T>* event,
+ void (ControlHost::*handler)(platform::gui::INativeWindow*,
+ typename IEvent<T>::Args)) {
+ event->AddHandler(
+ std::bind(handler, host, native_window, std::placeholders::_1));
+}
+} // namespace
+
+namespace {
+bool IsAncestor(Control* control, Control* ancestor) {
+ while (control != nullptr) {
+ if (control == ancestor) return true;
+ control = control->GetParent();
+ }
+ return false;
+}
+
+// Ancestor at last.
+std::vector<Control*> GetAncestorList(Control* control) {
+ if (control == nullptr) return {};
+
+ std::vector<Control*> l;
+ while (control != nullptr) {
+ l.push_back(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.back() != right_list.back()) return nullptr;
+
+ // find the last same control or the last control (one is ancestor of the
+ // other)
+ auto left_iter = left_list.crbegin();
+ auto right_iter = right_list.crbegin();
+
+ while (true) {
+ if (left_iter == left_list.crend()) {
+ return left_list.front();
+ }
+ if (right_iter == right_list.crend()) {
+ return right_list.front();
+ }
+ if (*left_iter != *right_iter) {
+ return *(--left_iter);
+ }
+ ++left_iter;
+ ++right_iter;
+ }
+}
+} // namespace
+
+std::unique_ptr<platform::gui::INativeWindow>
+ControlHost::CreateNativeWindow() {
+ const auto ui_application = platform::gui::IUiApplication::GetInstance();
+
+ auto native_window = ui_application->CreateWindow();
+ assert(native_window);
+
+ BindNativeEvent(this, native_window, native_window->DestroyEvent(),
+ &ControlHost::OnNativeDestroy);
+ BindNativeEvent(this, native_window, native_window->PaintEvent(),
+ &ControlHost::OnNativePaint);
+ BindNativeEvent(this, native_window, native_window->ResizeEvent(),
+ &ControlHost::OnNativeResize);
+ BindNativeEvent(this, native_window, native_window->FocusEvent(),
+ &ControlHost::OnNativeFocus);
+ BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(),
+ &ControlHost::OnNativeMouseEnterLeave);
+ BindNativeEvent(this, native_window, native_window->MouseMoveEvent(),
+ &ControlHost::OnNativeMouseMove);
+ BindNativeEvent(this, native_window, native_window->MouseDownEvent(),
+ &ControlHost::OnNativeMouseDown);
+ BindNativeEvent(this, native_window, native_window->MouseUpEvent(),
+ &ControlHost::OnNativeMouseUp);
+ BindNativeEvent(this, native_window, native_window->MouseWheelEvent(),
+ &ControlHost::OnNativeMouseWheel);
+ BindNativeEvent(this, native_window, native_window->KeyDownEvent(),
+ &ControlHost::OnNativeKeyDown);
+ BindNativeEvent(this, native_window, native_window->KeyUpEvent(),
+ &ControlHost::OnNativeKeyUp);
+
+ return std::unique_ptr<platform::gui::INativeWindow>(native_window);
+}
+
+void ControlHost::InvalidatePaint() {
+ repaint_schedule_canceler_.Reset(
+ platform::gui::IUiApplication::GetInstance()->SetImmediate(
+ [this] { Repaint(); }));
+}
+
+void ControlHost::InvalidateLayout() {
+ relayout_schedule_canceler_.Reset(
+ platform::gui::IUiApplication::GetInstance()->SetImmediate(
+ [this] { Relayout(); }));
+}
+
+bool ControlHost::IsLayoutPreferToFillWindow() const {
+ return layout_prefer_to_fill_window_;
+}
+
+void ControlHost::SetLayoutPreferToFillWindow(bool value) {
+ if (value == layout_prefer_to_fill_window_) return;
+ layout_prefer_to_fill_window_ = value;
+ InvalidateLayout();
+}
+
+void ControlHost::Repaint() {
+ auto painter = native_window_->BeginPaint();
+ painter->Clear(colors::white);
+ root_control_->GetRenderObject()->Draw(painter.get());
+ painter->EndDraw();
+}
+
+void ControlHost::Relayout() {
+ RelayoutWithSize(native_window_->GetClientSize());
+}
+
+void ControlHost::RelayoutWithSize(const Size& available_size,
+ bool set_window_size_to_fit_content) {
+ auto render_object = root_control_->GetRenderObject();
+ render_object->Measure(
+ render::MeasureRequirement{
+ available_size,
+ !set_window_size_to_fit_content && IsLayoutPreferToFillWindow()
+ ? render::MeasureSize(available_size)
+ : render::MeasureSize::NotSpecified()},
+ render::MeasureSize::NotSpecified());
+
+ if (set_window_size_to_fit_content) {
+ native_window_->SetClientSize(render_object->GetDesiredSize());
+ }
+
+ render_object->Layout(Point{});
+ CRU_LOG_TAG_DEBUG("A relayout is finished.");
+
+ AfterLayoutEvent_.Raise(nullptr);
+
+ InvalidatePaint();
+}
+
+Control* ControlHost::GetFocusControl() { return focus_control_; }
+
+void ControlHost::SetFocusControl(Control* control) {
+ if (control == nullptr) control = root_control_;
+ if (focus_control_ == control) return;
+
+ const auto old_focus_control = focus_control_;
+
+ focus_control_ = control;
+
+ DispatchFocusControlChangeEvent(old_focus_control, focus_control_, false);
+}
+
+Control* ControlHost::GetMouseCaptureControl() {
+ return mouse_captured_control_;
+}
+
+bool ControlHost::SetMouseCaptureControl(Control* control) {
+ if (!native_window_->CaptureMouse()) return false;
+
+ if (control == mouse_captured_control_) return true;
+
+ if (control == nullptr) {
+ native_window_->ReleaseMouse();
+ 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;
+}
+
+std::shared_ptr<platform::gui::ICursor> ControlHost::GetOverrideCursor() {
+ return override_cursor_;
+}
+
+void ControlHost::SetOverrideCursor(
+ std::shared_ptr<platform::gui::ICursor> cursor) {
+ if (cursor == override_cursor_) return;
+ override_cursor_ = cursor;
+ UpdateCursor();
+}
+
+bool ControlHost::IsInEventHandling() { return event_handling_count_; }
+
+void ControlHost::OnNativeDestroy(platform::gui::INativeWindow* window,
+ std::nullptr_t) {
+ CRU_UNUSED(window)
+}
+
+void ControlHost::OnNativePaint(platform::gui::INativeWindow* window,
+ std::nullptr_t) {
+ CRU_UNUSED(window)
+ InvalidatePaint();
+}
+
+void ControlHost::OnNativeResize(platform::gui::INativeWindow* window,
+ const Size& size) {
+ CRU_UNUSED(window)
+ CRU_UNUSED(size)
+
+ InvalidateLayout();
+}
+
+void ControlHost::OnNativeFocus(platform::gui::INativeWindow* window,
+ platform::gui::FocusChangeType focus) {
+ CRU_UNUSED(window)
+
+ focus == platform::gui::FocusChangeType::Gain
+ ? DispatchEvent(focus_control_, &Control::GainFocusEvent, nullptr, true)
+ : DispatchEvent(focus_control_, &Control::LoseFocusEvent, nullptr, true);
+}
+
+void ControlHost::OnNativeMouseEnterLeave(
+ platform::gui::INativeWindow* window,
+ platform::gui::MouseEnterLeaveType type) {
+ CRU_UNUSED(window)
+
+ if (type == platform::gui::MouseEnterLeaveType::Leave) {
+ DispatchEvent(mouse_hover_control_, &Control::MouseLeaveEvent, nullptr);
+ mouse_hover_control_ = nullptr;
+ }
+}
+
+void ControlHost::OnNativeMouseMove(platform::gui::INativeWindow* window,
+ const Point& point) {
+ CRU_UNUSED(window)
+
+ // Find the first control that hit test succeed.
+ const auto new_mouse_hover_control = root_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(o, &Control::MouseLeaveEvent, n);
+ } else {
+ DispatchEvent(n, &Control::MouseEnterEvent, o, point);
+ }
+ DispatchEvent(mouse_captured_control_, &Control::MouseMoveEvent, nullptr,
+ point);
+ UpdateCursor();
+ return;
+ }
+
+ DispatchMouseHoverControlChangeEvent(
+ old_mouse_hover_control, new_mouse_hover_control, point, false, false);
+ DispatchEvent(new_mouse_hover_control, &Control::MouseMoveEvent, nullptr,
+ point);
+ UpdateCursor();
+}
+
+void ControlHost::OnNativeMouseDown(
+ platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args) {
+ CRU_UNUSED(window)
+
+ Control* control = mouse_captured_control_
+ ? mouse_captured_control_
+ : root_control_->HitTest(args.point);
+ DispatchEvent(control, &Control::MouseDownEvent, nullptr, args.point,
+ args.button, args.modifier);
+}
+
+void ControlHost::OnNativeMouseUp(
+ platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args) {
+ CRU_UNUSED(window)
+
+ Control* control = mouse_captured_control_
+ ? mouse_captured_control_
+ : root_control_->HitTest(args.point);
+ DispatchEvent(control, &Control::MouseUpEvent, nullptr, args.point,
+ args.button, args.modifier);
+}
+
+void ControlHost::OnNativeMouseWheel(
+ platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseWheelEventArgs& args) {
+ CRU_UNUSED(window)
+
+ Control* control = mouse_captured_control_
+ ? mouse_captured_control_
+ : root_control_->HitTest(args.point);
+ DispatchEvent(control, &Control::MouseWheelEvent, nullptr, args.point,
+ args.delta, args.modifier);
+}
+
+void ControlHost::OnNativeKeyDown(
+ platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args) {
+ CRU_UNUSED(window)
+
+ DispatchEvent(focus_control_, &Control::KeyDownEvent, nullptr, args.key,
+ args.modifier);
+}
+
+void ControlHost::OnNativeKeyUp(platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args) {
+ CRU_UNUSED(window)
+
+ DispatchEvent(focus_control_, &Control::KeyUpEvent, nullptr, args.key,
+ args.modifier);
+}
+
+void ControlHost::DispatchFocusControlChangeEvent(Control* old_control,
+ Control* new_control,
+ bool is_window) {
+ if (new_control != old_control) {
+ const auto lowest_common_ancestor =
+ FindLowestCommonAncestor(old_control, new_control);
+ DispatchEvent(old_control, &Control::LoseFocusEvent, lowest_common_ancestor,
+ is_window);
+ DispatchEvent(new_control, &Control::GainFocusEvent, lowest_common_ancestor,
+ is_window);
+ }
+}
+
+void ControlHost::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(old_control, &Control::MouseLeaveEvent,
+ lowest_common_ancestor); // dispatch mouse leave event.
+ if (!no_enter && new_control != nullptr) {
+ DispatchEvent(new_control, &Control::MouseEnterEvent,
+ lowest_common_ancestor,
+ point); // dispatch mouse enter event.
+ }
+ }
+}
+
+void ControlHost::UpdateCursor() {
+ if (override_cursor_) {
+ native_window_->SetCursor(override_cursor_);
+ } else {
+ const auto capture = GetMouseCaptureControl();
+ native_window_->SetCursor(
+ (capture ? capture : GetMouseHoverControl())->GetInheritedCursor());
+ }
+}
+
+void ControlHost::NotifyControlParentChange(Control* control,
+ Control* old_parent,
+ Control* new_parent) {
+ if (new_parent == nullptr) {
+ if (focus_control_->HasAncestor(control)) {
+ focus_control_ = old_parent;
+ }
+
+ if (mouse_captured_control_->HasAncestor(control)) {
+ mouse_captured_control_ = old_parent;
+ }
+
+ if (mouse_hover_control_->HasAncestor(control)) {
+ mouse_hover_control_ = old_parent;
+ }
+ }
+}
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/NoChildControl.cpp b/src/ui/controls/NoChildControl.cpp
deleted file mode 100644
index 382a5d18..00000000
--- a/src/ui/controls/NoChildControl.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "cru/ui/controls/NoChildControl.h"
-
-namespace cru::ui::controls {
-void NoChildControl::ForEachChild(
- const std::function<void(Control*)>& callback) {
- CRU_UNUSED(callback);
-}
-
-void NoChildControl::RemoveChild(Control* child) { CRU_UNUSED(child); }
-} // namespace cru::ui::controls
diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp
index f51199c9..5908852a 100644
--- a/src/ui/controls/TextHostControlService.cpp
+++ b/src/ui/controls/TextHostControlService.cpp
@@ -13,7 +13,7 @@
#include "cru/ui/Base.h"
#include "cru/ui/DebugFlags.h"
#include "cru/ui/components/Menu.h"
-#include "cru/ui/controls/Window.h"
+#include "cru/ui/controls/ControlHost.h"
#include "cru/ui/helper/ShortcutHub.h"
#include "cru/ui/render/ScrollRenderObject.h"
#include "cru/ui/render/TextRenderObject.h"
@@ -293,9 +293,9 @@ void TextHostControlService::DeleteText(TextRange range,
platform::gui::IInputMethodContext*
TextHostControlService ::GetInputMethodContext() {
- Window* window = this->control_->GetWindow();
- if (!window) return nullptr;
- platform::gui::INativeWindow* native_window = window->GetNativeWindow();
+ auto host = this->control_->GetControlHost();
+ if (!host) return nullptr;
+ platform::gui::INativeWindow* native_window = host->GetNativeWindow();
if (!native_window) return nullptr;
return native_window->GetInputMethodContext();
}
@@ -588,11 +588,10 @@ void TextHostControlService::GainFocusHandler(
this->ReplaceSelectedText(text);
});
- auto window = control_->GetWindow();
- if (window)
- input_method_context_event_guard_ +=
- window->AfterLayoutEvent()->AddHandler(
- [this](auto) { this->UpdateInputMethodPosition(); });
+ auto host = control_->GetControlHost();
+ if (host)
+ input_method_context_event_guard_ += host->AfterLayoutEvent()->AddHandler(
+ [this](auto) { this->UpdateInputMethodPosition(); });
SetCaretVisible(true);
}
}
diff --git a/src/ui/controls/TreeView.cpp b/src/ui/controls/TreeView.cpp
index 659aef00..89613763 100644
--- a/src/ui/controls/TreeView.cpp
+++ b/src/ui/controls/TreeView.cpp
@@ -9,7 +9,7 @@ TreeViewItem::TreeViewItem(TreeView* tree_view, TreeViewItem* parent,
TreeViewItem::~TreeViewItem() {
if (control_) {
- control_->SetParent(nullptr);
+ tree_view_->RemoveChild(control_);
}
for (auto item : children_) {
@@ -46,12 +46,12 @@ void TreeViewItem::RemoveItem(Index position) {
void TreeViewItem::SetControl(Control* control) {
if (control_) {
- control_->SetParent(nullptr);
+ tree_view_->RemoveChild(control);
render_object_item_->SetRenderObject(nullptr);
}
control_ = control;
if (control) {
- control->SetParent(tree_view_);
+ tree_view_->AddChild(tree_view_);
render_object_item_->SetRenderObject(control->GetRenderObject());
}
}
@@ -69,18 +69,10 @@ TreeView::TreeView()
TreeView::~TreeView() {}
-void TreeView::ForEachChild(const std::function<void(Control*)>& predicate) {
- root_item_.TraverseDescendants([&predicate](TreeViewItem* item) {
- if (auto control = item->GetControl()) {
- predicate(control);
- }
- });
-}
-
-void TreeView::RemoveChild(Control* control) {
- root_item_.TraverseDescendants([&control](TreeViewItem* item) {
+void TreeView::OnChildRemoved(Control* control, Index index) {
+ root_item_.TraverseDescendants([control](TreeViewItem* item) {
if (item->GetControl() == control) {
- item->SetControl(nullptr);
+ item->RemoveFromParent();
}
});
}
diff --git a/src/ui/controls/Window.cpp b/src/ui/controls/Window.cpp
index c82b2485..a5fbf05f 100644
--- a/src/ui/controls/Window.cpp
+++ b/src/ui/controls/Window.cpp
@@ -1,26 +1,18 @@
#include "cru/ui/controls/Window.h"
-
#include "cru/platform/gui/UiApplication.h"
#include "cru/platform/gui/Window.h"
#include "cru/ui/Base.h"
+#include "cru/ui/controls/ControlHost.h"
#include <cassert>
namespace cru::ui::controls {
Window::Window()
- : event_handling_count_(0),
- native_window_(CreateNativeWindow()),
- focus_control_(this),
- mouse_hover_control_(nullptr),
- mouse_captured_control_(nullptr),
- layout_prefer_to_fill_window_(true),
- attached_control_(nullptr) {
+ : control_host_(new ControlHost(this)), attached_control_(nullptr) {
GetContainerRenderObject()->SetDefaultHorizontalAlignment(Alignment::Stretch);
GetContainerRenderObject()->SetDefaultVertialAlignment(Alignment::Stretch);
}
-Window::~Window() {}
-
Window* Window::CreatePopup() {
auto window = new Window();
window->GetNativeWindow()->SetStyleFlag(
@@ -36,409 +28,27 @@ void Window::SetAttachedControl(Control* control) {
}
platform::gui::INativeWindow* Window::GetNativeWindow() {
- return native_window_.get();
+ return control_host_->GetNativeWindow();
}
void Window::SetGainFocusOnCreateAndDestroyWhenLoseFocus(bool value) {
gain_focus_on_create_and_destroy_when_lose_focus_event_guard_.Clear();
if (value) {
gain_focus_on_create_and_destroy_when_lose_focus_event_guard_ +=
- native_window_->VisibilityChangeEvent()->AddHandler(
+ GetNativeWindow()->VisibilityChangeEvent()->AddHandler(
[this](platform::gui::WindowVisibilityType type) {
if (type == platform::gui::WindowVisibilityType::Show) {
- native_window_->RequestFocus();
+ GetNativeWindow()->RequestFocus();
}
});
gain_focus_on_create_and_destroy_when_lose_focus_event_guard_ +=
- native_window_->FocusEvent()->AddHandler(
+ GetNativeWindow()->FocusEvent()->AddHandler(
[this](platform::gui::FocusChangeType type) {
if (type == platform::gui::FocusChangeType::Lose) {
- native_window_->Close();
+ GetNativeWindow()->Close();
}
});
}
}
-
-namespace {
-template <typename T>
-inline void BindNativeEvent(
- Window* window, platform::gui::INativeWindow* native_window,
- IEvent<T>* event,
- void (Window::*handler)(platform::gui::INativeWindow*,
- typename IEvent<T>::Args)) {
- event->AddHandler(
- std::bind(handler, window, native_window, std::placeholders::_1));
-}
-} // namespace
-
-namespace {
-bool IsAncestor(Control* control, Control* ancestor) {
- while (control != nullptr) {
- if (control == ancestor) return true;
- control = control->GetParent();
- }
- return false;
-}
-
-// Ancestor at last.
-std::vector<Control*> GetAncestorList(Control* control) {
- if (control == nullptr) return {};
-
- std::vector<Control*> l;
- while (control != nullptr) {
- l.push_back(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.back() != right_list.back()) return nullptr;
-
- // find the last same control or the last control (one is ancestor of the
- // other)
- auto left_iter = left_list.crbegin();
- auto right_iter = right_list.crbegin();
-
- while (true) {
- if (left_iter == left_list.crend()) {
- return left_list.front();
- }
- if (right_iter == right_list.crend()) {
- return right_list.front();
- }
- if (*left_iter != *right_iter) {
- return *(--left_iter);
- }
- ++left_iter;
- ++right_iter;
- }
-}
-} // namespace
-
-std::unique_ptr<platform::gui::INativeWindow> Window::CreateNativeWindow() {
- const auto ui_application = platform::gui::IUiApplication::GetInstance();
-
- auto native_window = ui_application->CreateWindow();
- assert(native_window);
-
- BindNativeEvent(this, native_window, native_window->DestroyEvent(),
- &Window::OnNativeDestroy);
- BindNativeEvent(this, native_window, native_window->PaintEvent(),
- &Window::OnNativePaint);
- BindNativeEvent(this, native_window, native_window->ResizeEvent(),
- &Window::OnNativeResize);
- BindNativeEvent(this, native_window, native_window->FocusEvent(),
- &Window::OnNativeFocus);
- BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(),
- &Window::OnNativeMouseEnterLeave);
- BindNativeEvent(this, native_window, native_window->MouseMoveEvent(),
- &Window::OnNativeMouseMove);
- BindNativeEvent(this, native_window, native_window->MouseDownEvent(),
- &Window::OnNativeMouseDown);
- BindNativeEvent(this, native_window, native_window->MouseUpEvent(),
- &Window::OnNativeMouseUp);
- BindNativeEvent(this, native_window, native_window->MouseWheelEvent(),
- &Window::OnNativeMouseWheel);
- BindNativeEvent(this, native_window, native_window->KeyDownEvent(),
- &Window::OnNativeKeyDown);
- BindNativeEvent(this, native_window, native_window->KeyUpEvent(),
- &Window::OnNativeKeyUp);
-
- return std::unique_ptr<platform::gui::INativeWindow>(native_window);
-}
-
-void Window::InvalidatePaint() {
- repaint_schedule_canceler_.Reset(
- platform::gui::IUiApplication::GetInstance()->SetImmediate(
- [this] { Repaint(); }));
-}
-
-void Window::InvalidateLayout() {
- relayout_schedule_canceler_.Reset(
- platform::gui::IUiApplication::GetInstance()->SetImmediate(
- [this] { Relayout(); }));
-}
-
-bool Window::IsLayoutPreferToFillWindow() const {
- return layout_prefer_to_fill_window_;
-}
-
-void Window::SetLayoutPreferToFillWindow(bool value) {
- if (value == layout_prefer_to_fill_window_) return;
- layout_prefer_to_fill_window_ = value;
- InvalidateLayout();
-}
-
-void Window::Repaint() {
- auto painter = native_window_->BeginPaint();
- painter->Clear(colors::white);
- GetRenderObject()->Draw(painter.get());
- painter->EndDraw();
-}
-
-void Window::Relayout() { RelayoutWithSize(native_window_->GetClientSize()); }
-
-void Window::RelayoutWithSize(const Size& available_size,
- bool set_window_size_to_fit_content) {
- auto render_object = GetRenderObject();
- render_object->Measure(
- render::MeasureRequirement{
- available_size,
- !set_window_size_to_fit_content && IsLayoutPreferToFillWindow()
- ? render::MeasureSize(available_size)
- : render::MeasureSize::NotSpecified()},
- render::MeasureSize::NotSpecified());
-
- if (set_window_size_to_fit_content) {
- native_window_->SetClientSize(render_object->GetDesiredSize());
- }
-
- render_object->Layout(Point{});
- CRU_LOG_TAG_DEBUG("A relayout is finished.");
-
- AfterLayoutEvent_.Raise(nullptr);
-
- InvalidatePaint();
-}
-
-Control* Window::GetFocusControl() { return focus_control_; }
-
-void Window::SetFocusControl(Control* control) {
- if (control == nullptr) control = this;
- if (focus_control_ == control) return;
-
- const auto old_focus_control = focus_control_;
-
- focus_control_ = control;
-
- DispatchFocusControlChangeEvent(old_focus_control, focus_control_, false);
-}
-
-Control* Window::GetMouseCaptureControl() { return mouse_captured_control_; }
-
-bool Window::SetMouseCaptureControl(Control* control) {
- if (!native_window_->CaptureMouse()) return false;
-
- if (control == mouse_captured_control_) return true;
-
- if (control == nullptr) {
- native_window_->ReleaseMouse();
- 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;
-}
-
-std::shared_ptr<platform::gui::ICursor> Window::GetOverrideCursor() {
- return override_cursor_;
-}
-
-void Window::SetOverrideCursor(std::shared_ptr<platform::gui::ICursor> cursor) {
- if (cursor == override_cursor_) return;
- override_cursor_ = cursor;
- UpdateCursor();
-}
-
-bool Window::IsInEventHandling() { return event_handling_count_; }
-
-void Window::OnNativeDestroy(platform::gui::INativeWindow* window,
- std::nullptr_t) {
- CRU_UNUSED(window)
-}
-
-void Window::OnNativePaint(platform::gui::INativeWindow* window,
- std::nullptr_t) {
- CRU_UNUSED(window)
- InvalidatePaint();
-}
-
-void Window::OnNativeResize(platform::gui::INativeWindow* window,
- const Size& size) {
- CRU_UNUSED(window)
- CRU_UNUSED(size)
-
- InvalidateLayout();
-}
-
-void Window::OnNativeFocus(platform::gui::INativeWindow* window,
- platform::gui::FocusChangeType focus) {
- CRU_UNUSED(window)
-
- focus == platform::gui::FocusChangeType::Gain
- ? DispatchEvent(focus_control_, &Control::GainFocusEvent, nullptr, true)
- : DispatchEvent(focus_control_, &Control::LoseFocusEvent, nullptr, true);
-}
-
-void Window::OnNativeMouseEnterLeave(platform::gui::INativeWindow* window,
- platform::gui::MouseEnterLeaveType type) {
- CRU_UNUSED(window)
-
- if (type == platform::gui::MouseEnterLeaveType::Leave) {
- DispatchEvent(mouse_hover_control_, &Control::MouseLeaveEvent, nullptr);
- mouse_hover_control_ = nullptr;
- }
-}
-
-void Window::OnNativeMouseMove(platform::gui::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(o, &Control::MouseLeaveEvent, n);
- } else {
- DispatchEvent(n, &Control::MouseEnterEvent, o, point);
- }
- DispatchEvent(mouse_captured_control_, &Control::MouseMoveEvent, nullptr,
- point);
- UpdateCursor();
- return;
- }
-
- DispatchMouseHoverControlChangeEvent(
- old_mouse_hover_control, new_mouse_hover_control, point, false, false);
- DispatchEvent(new_mouse_hover_control, &Control::MouseMoveEvent, nullptr,
- point);
- UpdateCursor();
-}
-
-void Window::OnNativeMouseDown(
- platform::gui::INativeWindow* window,
- const platform::gui::NativeMouseButtonEventArgs& args) {
- CRU_UNUSED(window)
-
- Control* control =
- mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point);
- DispatchEvent(control, &Control::MouseDownEvent, nullptr, args.point,
- args.button, args.modifier);
-}
-
-void Window::OnNativeMouseUp(
- platform::gui::INativeWindow* window,
- const platform::gui::NativeMouseButtonEventArgs& args) {
- CRU_UNUSED(window)
-
- Control* control =
- mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point);
- DispatchEvent(control, &Control::MouseUpEvent, nullptr, args.point,
- args.button, args.modifier);
-}
-
-void Window::OnNativeMouseWheel(
- platform::gui::INativeWindow* window,
- const platform::gui::NativeMouseWheelEventArgs& args) {
- CRU_UNUSED(window)
-
- Control* control =
- mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point);
- DispatchEvent(control, &Control::MouseWheelEvent, nullptr, args.point,
- args.delta, args.modifier);
-}
-
-void Window::OnNativeKeyDown(platform::gui::INativeWindow* window,
- const platform::gui::NativeKeyEventArgs& args) {
- CRU_UNUSED(window)
-
- DispatchEvent(focus_control_, &Control::KeyDownEvent, nullptr, args.key,
- args.modifier);
-}
-
-void Window::OnNativeKeyUp(platform::gui::INativeWindow* window,
- const platform::gui::NativeKeyEventArgs& args) {
- CRU_UNUSED(window)
-
- DispatchEvent(focus_control_, &Control::KeyUpEvent, nullptr, args.key,
- args.modifier);
-}
-
-void Window::DispatchFocusControlChangeEvent(Control* old_control,
- Control* new_control,
- bool is_window) {
- if (new_control != old_control) {
- const auto lowest_common_ancestor =
- FindLowestCommonAncestor(old_control, new_control);
- DispatchEvent(old_control, &Control::LoseFocusEvent, lowest_common_ancestor,
- is_window);
- DispatchEvent(new_control, &Control::GainFocusEvent, lowest_common_ancestor,
- is_window);
- }
-}
-
-void Window::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(old_control, &Control::MouseLeaveEvent,
- lowest_common_ancestor); // dispatch mouse leave event.
- if (!no_enter && new_control != nullptr) {
- DispatchEvent(new_control, &Control::MouseEnterEvent,
- lowest_common_ancestor,
- point); // dispatch mouse enter event.
- }
- }
-}
-
-void Window::UpdateCursor() {
- if (override_cursor_) {
- native_window_->SetCursor(override_cursor_);
- } else {
- const auto capture = GetMouseCaptureControl();
- native_window_->SetCursor(
- (capture ? capture : GetMouseHoverControl())->GetInheritedCursor());
- }
-}
-
-void Window::NotifyControlDestroyed(Control* control) {
- if (focus_control_->HasAncestor(control)) {
- focus_control_ = control->GetParent();
- }
-
- if (mouse_captured_control_->HasAncestor(control)) {
- mouse_captured_control_ = control->GetParent();
- }
-
- if (mouse_hover_control_->HasAncestor(control)) {
- mouse_hover_control_ = control->GetParent();
- }
-}
} // namespace cru::ui::controls