aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/cru/ui/Base.h2
-rw-r--r--include/cru/ui/controls/Button.h4
-rw-r--r--include/cru/ui/controls/CheckBox.h8
-rw-r--r--include/cru/ui/controls/Control.h45
-rw-r--r--include/cru/ui/controls/ControlHost.h191
-rw-r--r--include/cru/ui/controls/IconButton.h11
-rw-r--r--include/cru/ui/controls/LayoutControl.h67
-rw-r--r--include/cru/ui/controls/NoChildControl.h21
-rw-r--r--include/cru/ui/controls/SingleChildControl.h35
-rw-r--r--include/cru/ui/controls/TextBlock.h10
-rw-r--r--include/cru/ui/controls/TextBox.h8
-rw-r--r--include/cru/ui/controls/TreeView.h11
-rw-r--r--include/cru/ui/controls/Window.h168
-rw-r--r--include/cru/ui/events/RoutedEvent.h2
-rw-r--r--include/cru/ui/render/RenderObject.h2
-rw-r--r--src/ThemeBuilder/components/StyleRuleEditor.cpp2
-rw-r--r--src/ThemeBuilder/components/StyleRuleSetEditor.cpp2
-rw-r--r--src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp7
-rw-r--r--src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp5
-rw-r--r--src/platform/gui/win/Window.cpp1
-rw-r--r--src/ui/CMakeLists.txt2
-rw-r--r--src/ui/components/Menu.cpp9
-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
-rw-r--r--src/ui/events/MouseEventArgs.cpp7
-rw-r--r--src/ui/helper/ClickDetector.cpp9
-rw-r--r--src/ui/render/RenderObject.cpp12
-rw-r--r--src/ui/render/ScrollBar.cpp10
32 files changed, 844 insertions, 830 deletions
diff --git a/include/cru/ui/Base.h b/include/cru/ui/Base.h
index 9e24cfa2..06dd23c2 100644
--- a/include/cru/ui/Base.h
+++ b/include/cru/ui/Base.h
@@ -35,7 +35,7 @@ namespace colors = cru::platform::colors;
namespace controls {
class Control;
-class Window;
+class ControlHost;
} // namespace controls
//-------------------- region: basic types --------------------
diff --git a/include/cru/ui/controls/Button.h b/include/cru/ui/controls/Button.h
index e8fa50f1..64b9bba0 100644
--- a/include/cru/ui/controls/Button.h
+++ b/include/cru/ui/controls/Button.h
@@ -16,10 +16,6 @@ class CRU_UI_API Button : public SingleChildControl<render::BorderRenderObject>,
public:
Button();
- Button(const Button& other) = delete;
- Button(Button&& other) = delete;
- Button& operator=(const Button& other) = delete;
- Button& operator=(Button&& other) = delete;
~Button() override;
std::string GetControlType() const final { return std::string(kControlType); }
diff --git a/include/cru/ui/controls/CheckBox.h b/include/cru/ui/controls/CheckBox.h
index 8a2c84a0..6f4eae2e 100644
--- a/include/cru/ui/controls/CheckBox.h
+++ b/include/cru/ui/controls/CheckBox.h
@@ -1,13 +1,13 @@
#pragma once
#include "../helper/ClickDetector.h"
#include "../render/BorderRenderObject.h"
+#include "Control.h"
#include "IBorderControl.h"
#include "ICheckableControl.h"
#include "IClickableControl.h"
-#include "NoChildControl.h"
namespace cru::ui::controls {
-class CRU_UI_API CheckBox : public NoChildControl,
+class CRU_UI_API CheckBox : public Control,
public virtual IBorderControl,
public virtual ICheckableControl,
public virtual IClickableControl {
@@ -17,7 +17,9 @@ class CRU_UI_API CheckBox : public NoChildControl,
CheckBox();
~CheckBox() override;
- std::string GetControlType() const override { return std::string(kControlType); }
+ std::string GetControlType() const override {
+ return std::string(kControlType);
+ }
render::RenderObject* GetRenderObject() const override {
return container_render_object_.get();
diff --git a/include/cru/ui/controls/Control.h b/include/cru/ui/controls/Control.h
index 94de4cdc..9e5e86b8 100644
--- a/include/cru/ui/controls/Control.h
+++ b/include/cru/ui/controls/Control.h
@@ -24,9 +24,9 @@ namespace cru::ui::controls {
class CRU_UI_API Control : public Object,
public DeleteLaterImpl,
public SelfResolvable<Control> {
- friend class RootControl;
+ friend class ControlHost;
- CRU_DEFINE_CLASS_LOG_TAG("Control")
+ CRU_DEFINE_CLASS_LOG_TAG("cru::ui::controls::Control")
protected:
Control();
@@ -41,24 +41,33 @@ class CRU_UI_API Control : public Object,
//*************** region: tree ***************
public:
- Window* GetWindow();
+ ControlHost* GetControlHost();
- Control* GetParent() const { return parent_; }
- void SetParent(Control* parent);
+ Control* GetParent();
bool HasAncestor(Control* control);
+ const std::vector<Control*>& GetChildren();
+ void RemoveChild(Control* child);
+ void RemoveAllChild();
+ void RemoveFromParent();
- virtual void ForEachChild(const std::function<void(Control*)>& predicate) = 0;
-
- /**
- * \remarks This method should be permissive, which means if the specified
- * child control is not a real child of this then nothing will be done.
- */
- virtual void RemoveChild(Control* child) = 0;
+ template <typename F>
+ void TraverseDescendents(F&& f, bool include_this) {
+ if (include_this) {
+ f(this);
+ }
- void RemoveFromParent();
+ for (auto child : GetChildren()) {
+ child->TraverseDescendents(std::forward<F>(f), true);
+ }
+ }
controls::Control* HitTest(const Point& point);
+ protected:
+ void InsertChildAt(Control* control, Index index);
+ void RemoveChildAt(Index index);
+ void AddChild(Control* control);
+
public:
virtual render::RenderObject* GetRenderObject() const = 0;
@@ -89,7 +98,7 @@ class CRU_UI_API Control : public Object,
//*************** region: mouse ***************
public:
- bool IsMouseOver() const { return is_mouse_over_; }
+ bool IsMouseOver();
bool CaptureMouse();
@@ -137,12 +146,14 @@ class CRU_UI_API Control : public Object,
//*************** region: tree ***************
protected:
- virtual void OnParentChanged(Control* old_parent, Control* new_parent) {}
+ virtual void OnParentChanged(Control* old_parent, Control* new_parent);
+ virtual void OnChildInserted(Control* control, Index index);
+ virtual void OnChildRemoved(Control* control, Index index);
private:
+ ControlHost* host_ = nullptr;
Control* parent_ = nullptr;
-
- bool is_mouse_over_ = false;
+ std::vector<Control*> children_;
std::shared_ptr<platform::gui::ICursor> cursor_ = nullptr;
diff --git a/include/cru/ui/controls/ControlHost.h b/include/cru/ui/controls/ControlHost.h
new file mode 100644
index 00000000..c67e8a72
--- /dev/null
+++ b/include/cru/ui/controls/ControlHost.h
@@ -0,0 +1,191 @@
+#pragma once
+#include "Control.h"
+
+#include <cru/base/Base.h>
+#include <cru/base/Event.h>
+#include <cru/base/Guard.h>
+#include <cru/base/log/Logger.h>
+#include <cru/platform/gui/UiApplication.h>
+#include <cru/platform/gui/Window.h>
+
+namespace cru::ui::controls {
+class CRU_UI_API ControlHost : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG("cru::ui::controls::ControlHost")
+ friend Control;
+
+ public:
+ explicit ControlHost(Control* root_control);
+ ~ControlHost() override;
+
+ platform::gui::INativeWindow* GetNativeWindow();
+
+ void InvalidateLayout();
+ void InvalidatePaint();
+
+ void Repaint();
+ void Relayout();
+ void RelayoutWithSize(const Size& available_size = Size::Infinite(),
+ bool set_window_size_to_fit_content = false);
+
+ // If true, preferred size of root render object is set to window size when
+ // measure. Default is true.
+ bool IsLayoutPreferToFillWindow() const;
+ void SetLayoutPreferToFillWindow(bool value);
+
+ // Get current control that mouse hovers on. This ignores the mouse-capture
+ // control. Even when mouse is captured by another control, this function
+ // return the control under cursor. You can use `GetMouseCaptureControl` to
+ // get more info.
+ Control* GetMouseHoverControl() const { return mouse_hover_control_; }
+
+ Control* GetFocusControl();
+ void SetFocusControl(Control* control);
+
+ Control* GetMouseCaptureControl();
+ bool SetMouseCaptureControl(Control* control);
+
+ std::shared_ptr<platform::gui::ICursor> GetOverrideCursor();
+ void SetOverrideCursor(std::shared_ptr<platform::gui::ICursor> cursor);
+
+ bool IsInEventHandling();
+
+ CRU_DEFINE_EVENT(AfterLayout, std::nullptr_t)
+
+ private:
+ std::unique_ptr<platform::gui::INativeWindow> CreateNativeWindow();
+
+ void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t);
+ void OnNativePaint(platform::gui::INativeWindow* window, std::nullptr_t);
+ void OnNativeResize(platform::gui::INativeWindow* window, const Size& size);
+ void OnNativeFocus(platform::gui::INativeWindow* window,
+ cru::platform::gui::FocusChangeType focus);
+ void OnNativeMouseEnterLeave(platform::gui::INativeWindow* window,
+ cru::platform::gui::MouseEnterLeaveType enter);
+ void OnNativeMouseMove(platform::gui::INativeWindow* window,
+ const Point& point);
+ void OnNativeMouseDown(platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args);
+ void OnNativeMouseUp(platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args);
+ void OnNativeMouseWheel(platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseWheelEventArgs& args);
+ void OnNativeKeyDown(platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args);
+ void OnNativeKeyUp(platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args);
+
+ void DispatchFocusControlChangeEvent(Control* old_control,
+ Control* new_control, bool is_window);
+ void DispatchMouseHoverControlChangeEvent(Control* old_control,
+ Control* new_control,
+ const Point& point, bool no_leave,
+ bool no_enter);
+
+ template <typename EventArgs, typename... Args>
+ void DispatchEvent(Control* const original_sender,
+ events::RoutedEvent<EventArgs>* (Control::*event_ptr)(),
+ Control* const last_receiver, Args&&... args) {
+ constexpr auto kLogTag = "cru::ui::controls::DispatchEvent";
+
+ event_handling_count_++;
+ Guard event_handling_count_guard([this] { event_handling_count_--; });
+
+ if (original_sender == nullptr || original_sender == last_receiver) return;
+
+ std::string log = "Begin dispatching routed event " +
+ (original_sender->*event_ptr)()->GetName() +
+ ":\n\tTunnel:";
+
+ Guard logging_guard([&] {
+ log += "\nEnd dispatching routed event " +
+ (original_sender->*event_ptr)()->GetName() + ".";
+ CRU_LOG_TAG_DEBUG("{}", log);
+ });
+
+ std::vector<ObjectResolver<Control>> receive_list;
+
+ auto parent = original_sender;
+ while (parent != last_receiver) {
+ receive_list.push_back(parent->CreateResolver());
+ parent = parent->GetParent();
+ }
+
+ auto handled = false;
+
+ // tunnel
+ for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) {
+ auto control = i->Resolve();
+ log += " ";
+ if (!control) {
+ log += "(deleted)";
+ continue;
+ }
+ log += control->GetDebugId();
+ EventArgs event_args(control, original_sender,
+ std::forward<Args>(args)...);
+ (control->*event_ptr)()->tunnel_.Raise(event_args);
+ if (event_args.IsHandled()) {
+ log += " marked as handled.";
+ handled = true;
+ break;
+ }
+ }
+
+ // bubble
+ if (!handled) {
+ log += "\n\tBubble:";
+ for (auto resolver : receive_list) {
+ auto control = resolver.Resolve();
+ log += " ";
+ if (!control) {
+ log += "(deleted)";
+ continue;
+ }
+ log += control->GetDebugId();
+ EventArgs event_args(control, original_sender,
+ std::forward<Args>(args)...);
+ (control->*event_ptr)()->bubble_.Raise(event_args);
+ if (event_args.IsHandled()) {
+ log += " marked as handled.";
+ break;
+ }
+ }
+ }
+
+ log += "\n\tDirect:";
+ // direct
+ for (auto resolver : receive_list) {
+ auto control = resolver.Resolve();
+ log += " ";
+ if (!control) {
+ log += "(deleted)";
+ continue;
+ }
+ log += control->GetDebugId();
+ EventArgs event_args(control, original_sender,
+ std::forward<Args>(args)...);
+ (control->*event_ptr)()->direct_.Raise(event_args);
+ }
+ }
+
+ void UpdateCursor();
+ void NotifyControlParentChange(Control* control, Control* old_parent, Control* new_parent);
+
+ private:
+ int event_handling_count_;
+
+ Control* root_control_;
+ std::unique_ptr<platform::gui::INativeWindow> native_window_;
+
+ Control* focus_control_;
+ Control* mouse_hover_control_;
+ Control* mouse_captured_control_;
+
+ std::shared_ptr<platform::gui::ICursor> override_cursor_;
+
+ bool layout_prefer_to_fill_window_;
+
+ platform::gui::TimerAutoCanceler repaint_schedule_canceler_;
+ platform::gui::TimerAutoCanceler relayout_schedule_canceler_;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/IconButton.h b/include/cru/ui/controls/IconButton.h
index e52d2a26..0bbda327 100644
--- a/include/cru/ui/controls/IconButton.h
+++ b/include/cru/ui/controls/IconButton.h
@@ -1,18 +1,17 @@
#pragma once
-#include <memory>
-#include "NoChildControl.h"
-
#include "../helper/ClickDetector.h"
#include "../render/BorderRenderObject.h"
#include "../render/GeometryRenderObject.h"
+#include "Control.h"
#include "IBorderControl.h"
#include "IClickableControl.h"
#include "IContentBrushControl.h"
-#include "cru/base/Event.h"
-#include "cru/platform/graphics/Brush.h"
+
+#include <cru/base/Event.h>
+#include <cru/platform/graphics/Brush.h>
namespace cru::ui::controls {
-class CRU_UI_API IconButton : public NoChildControl,
+class CRU_UI_API IconButton : public Control,
public virtual IClickableControl,
public virtual IBorderControl,
public virtual IContentBrushControl {
diff --git a/include/cru/ui/controls/LayoutControl.h b/include/cru/ui/controls/LayoutControl.h
index 54407a3f..fad86530 100644
--- a/include/cru/ui/controls/LayoutControl.h
+++ b/include/cru/ui/controls/LayoutControl.h
@@ -10,17 +10,14 @@ class LayoutControl : public Control {
}
public:
- LayoutControl(const LayoutControl& other) = delete;
- LayoutControl(LayoutControl&& other) = delete;
- LayoutControl& operator=(const LayoutControl& other) = delete;
- LayoutControl& operator=(LayoutControl&& other) = delete;
- ~LayoutControl() override { ClearChildren(); }
+ using Control::AddChild;
+ using Control::InsertChildAt;
+ using Control::RemoveChildAt;
- public:
- const std::vector<Control*>& GetChildren() const { return children_; }
Index GetChildCount() const { return children_.size(); }
- Control* GetChild(Index index) const { return children_[index]; }
- Index IndexOf(Control* control) const {
+ Control* GetChildAt(Index index) const { return children_[index]; }
+
+ Index IndexOfChild(Control* control) const {
auto it = std::find(children_.begin(), children_.end(), control);
if (it == children_.end()) {
return -1;
@@ -28,19 +25,6 @@ class LayoutControl : public Control {
return it - children_.begin();
}
- void ForEachChild(const std::function<void(Control*)>& callback) override {
- for (auto child : children_) {
- callback(child);
- }
- }
-
- void RemoveChild(Control* child) override {
- auto index = IndexOf(child);
- if (index != -1) {
- RemoveChildAt(index);
- }
- }
-
render::RenderObject* GetRenderObject() const override {
return container_render_object_.get();
}
@@ -49,36 +33,6 @@ class LayoutControl : public Control {
return container_render_object_.get();
}
- void AddChildAt(Control* child, Index position) {
- Expects(child);
- Expects(child->GetParent() == nullptr);
- if (position < 0) position = 0;
- if (position > children_.size()) position = children_.size();
- children_.insert(children_.begin() + position, child);
- child->SetParent(this);
-
- assert(child->GetRenderObject());
- container_render_object_->AddChild(child->GetRenderObject(), position);
- }
-
- void AddChild(Control* child) { AddChildAt(child, GetChildCount()); }
-
- void RemoveChildAt(Index position) {
- if (position < 0 || position >= children_.size()) return;
- auto child = children_[position];
- children_.erase(children_.begin() + position);
- container_render_object_->RemoveChild(position);
- child->SetParent(nullptr);
- }
-
- void ClearChildren() {
- container_render_object_->ClearChildren();
- for (auto child : children_) {
- child->SetParent(nullptr);
- }
- children_.clear();
- }
-
const typename TRenderObject::ChildLayoutData& GetChildLayoutData(
Index position) {
return container_render_object_->GetChildLayoutDataAt(position);
@@ -89,6 +43,15 @@ class LayoutControl : public Control {
container_render_object_->SetChildLayoutDataAt(position, data);
}
+ protected:
+ void OnChildInserted(Control* control, Index index) override {
+ container_render_object_->AddChild(control->GetRenderObject(), index);
+ }
+
+ void OnChildRemoved([[maybe_unused]] Control* control, Index index) override {
+ container_render_object_->RemoveChild(index);
+ }
+
private:
std::unique_ptr<TRenderObject> container_render_object_;
diff --git a/include/cru/ui/controls/NoChildControl.h b/include/cru/ui/controls/NoChildControl.h
deleted file mode 100644
index f22fd85e..00000000
--- a/include/cru/ui/controls/NoChildControl.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-#include "Control.h"
-
-namespace cru::ui::controls {
-class CRU_UI_API NoChildControl : public Control {
- protected:
- NoChildControl() = default;
-
- public:
- NoChildControl(const NoChildControl& other) = delete;
- NoChildControl(NoChildControl&& other) = delete;
- NoChildControl& operator=(const NoChildControl& other) = delete;
- NoChildControl& operator=(NoChildControl&& other) = delete;
- ~NoChildControl() override = default;
-
- public:
- void ForEachChild(const std::function<void(Control*)>& callback) override;
-
- void RemoveChild(Control* child) override;
-};
-} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/SingleChildControl.h b/include/cru/ui/controls/SingleChildControl.h
index b2e58c5f..f748a38d 100644
--- a/include/cru/ui/controls/SingleChildControl.h
+++ b/include/cru/ui/controls/SingleChildControl.h
@@ -10,25 +10,17 @@ class SingleChildControl : public Control {
}
public:
- CRU_DELETE_COPY(SingleChildControl)
- CRU_DELETE_MOVE(SingleChildControl)
-
- ~SingleChildControl() override { SetChild(nullptr); }
+ Control* GetChild() {
+ return GetChildren().empty() ? nullptr : GetChildren().front();
+ }
- Control* GetChild() const { return child_; }
void SetChild(Control* child) {
- if (child == child_) return;
-
- assert(child == nullptr || child->GetParent() == nullptr);
-
- if (child_) {
- child_->SetParent(nullptr);
+ if (GetChild() == child) return;
+ if (!GetChildren().empty()) {
+ RemoveChildAt(0);
}
-
- child_ = child;
-
if (child) {
- child->SetParent(this);
+ InsertChildAt(child, 0);
}
container_render_object_->SetChild(
@@ -43,20 +35,7 @@ class SingleChildControl : public Control {
return container_render_object_.get();
}
- void ForEachChild(const std::function<void(Control*)>& predicate) override {
- if (child_) {
- predicate(child_);
- }
- }
-
- void RemoveChild(Control* child) override {
- if (child_ == child) {
- SetChild(nullptr);
- }
- }
-
private:
- Control* child_ = nullptr;
std::unique_ptr<TRenderObject> container_render_object_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/TextBlock.h b/include/cru/ui/controls/TextBlock.h
index a3b6407f..af9865c4 100644
--- a/include/cru/ui/controls/TextBlock.h
+++ b/include/cru/ui/controls/TextBlock.h
@@ -1,15 +1,15 @@
#pragma once
-#include "NoChildControl.h"
-
+#include "Control.h"
#include "../render/TextRenderObject.h"
#include "IContentBrushControl.h"
#include "IFontControl.h"
#include "TextHostControlService.h"
-#include "cru/platform/graphics/Brush.h"
-#include "cru/platform/graphics/Font.h"
+
+#include <cru/platform/graphics/Brush.h>
+#include <cru/platform/graphics/Font.h>
namespace cru::ui::controls {
-class CRU_UI_API TextBlock : public NoChildControl,
+class CRU_UI_API TextBlock : public Control,
public virtual ITextHostControl,
public virtual IFontControl,
public virtual IContentBrushControl {
diff --git a/include/cru/ui/controls/TextBox.h b/include/cru/ui/controls/TextBox.h
index adb9895e..4c6ef384 100644
--- a/include/cru/ui/controls/TextBox.h
+++ b/include/cru/ui/controls/TextBox.h
@@ -1,18 +1,18 @@
#pragma once
-#include "NoChildControl.h"
-
#include "../render/BorderRenderObject.h"
#include "../render/TextRenderObject.h"
+#include "Control.h"
#include "IBorderControl.h"
#include "IContentBrushControl.h"
#include "IFontControl.h"
#include "TextHostControlService.h"
-#include "cru/platform/graphics/Brush.h"
+
+#include <cru/platform/graphics/Brush.h>
#include <memory>
namespace cru::ui::controls {
-class CRU_UI_API TextBox : public NoChildControl,
+class CRU_UI_API TextBox : public Control,
public virtual IBorderControl,
public virtual ITextHostControl,
public virtual IContentBrushControl,
diff --git a/include/cru/ui/controls/TreeView.h b/include/cru/ui/controls/TreeView.h
index f4938259..62b1c69b 100644
--- a/include/cru/ui/controls/TreeView.h
+++ b/include/cru/ui/controls/TreeView.h
@@ -48,6 +48,11 @@ class CRU_UI_API TreeViewItem : public Object {
};
class CRU_UI_API TreeView : public Control {
+ friend TreeViewItem;
+
+ private:
+ using Control::AddChild;
+
public:
constexpr static std::string_view kControlType = "TreeView";
@@ -61,11 +66,11 @@ class CRU_UI_API TreeView : public Control {
}
render::TreeRenderObject* GetRenderObject() { return &render_object_; }
- void ForEachChild(const std::function<void(Control*)>& predicate) override;
- void RemoveChild(Control* control) override;
-
TreeViewItem* GetRootItem() { return &root_item_; }
+ protected:
+ void OnChildRemoved(Control* control, Index index) override;
+
private:
render::TreeRenderObject render_object_;
TreeViewItem root_item_;
diff --git a/include/cru/ui/controls/Window.h b/include/cru/ui/controls/Window.h
index 88320219..a2ae01f9 100644
--- a/include/cru/ui/controls/Window.h
+++ b/include/cru/ui/controls/Window.h
@@ -19,7 +19,6 @@ class CRU_UI_API Window
static constexpr std::string_view kControlType = "Window";
Window();
- ~Window() override;
static Window* CreatePopup();
@@ -29,175 +28,10 @@ class CRU_UI_API Window
platform::gui::INativeWindow* GetNativeWindow();
- void InvalidateLayout();
- void InvalidatePaint();
-
- void Repaint();
- void Relayout();
- void RelayoutWithSize(const Size& available_size = Size::Infinite(),
- bool set_window_size_to_fit_content = false);
-
void SetGainFocusOnCreateAndDestroyWhenLoseFocus(bool value);
- // If true, preferred size of root render object is set to window size when
- // measure. Default is true.
- bool IsLayoutPreferToFillWindow() const;
- void SetLayoutPreferToFillWindow(bool value);
-
- // Get current control that mouse hovers on. This ignores the mouse-capture
- // control. Even when mouse is captured by another control, this function
- // return the control under cursor. You can use `GetMouseCaptureControl` to
- // get more info.
- Control* GetMouseHoverControl() const { return mouse_hover_control_; }
-
- Control* GetFocusControl();
- void SetFocusControl(Control* control);
-
- Control* GetMouseCaptureControl();
- bool SetMouseCaptureControl(Control* control);
-
- std::shared_ptr<platform::gui::ICursor> GetOverrideCursor();
- void SetOverrideCursor(std::shared_ptr<platform::gui::ICursor> cursor);
-
- bool IsInEventHandling();
-
- CRU_DEFINE_EVENT(AfterLayout, std::nullptr_t)
-
- private:
- std::unique_ptr<platform::gui::INativeWindow> CreateNativeWindow();
-
- void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t);
- void OnNativePaint(platform::gui::INativeWindow* window, std::nullptr_t);
- void OnNativeResize(platform::gui::INativeWindow* window, const Size& size);
- void OnNativeFocus(platform::gui::INativeWindow* window,
- cru::platform::gui::FocusChangeType focus);
- void OnNativeMouseEnterLeave(platform::gui::INativeWindow* window,
- cru::platform::gui::MouseEnterLeaveType enter);
- void OnNativeMouseMove(platform::gui::INativeWindow* window,
- const Point& point);
- void OnNativeMouseDown(platform::gui::INativeWindow* window,
- const platform::gui::NativeMouseButtonEventArgs& args);
- void OnNativeMouseUp(platform::gui::INativeWindow* window,
- const platform::gui::NativeMouseButtonEventArgs& args);
- void OnNativeMouseWheel(platform::gui::INativeWindow* window,
- const platform::gui::NativeMouseWheelEventArgs& args);
- void OnNativeKeyDown(platform::gui::INativeWindow* window,
- const platform::gui::NativeKeyEventArgs& args);
- void OnNativeKeyUp(platform::gui::INativeWindow* window,
- const platform::gui::NativeKeyEventArgs& args);
-
- void DispatchFocusControlChangeEvent(Control* old_control,
- Control* new_control, bool is_window);
- void DispatchMouseHoverControlChangeEvent(Control* old_control,
- Control* new_control,
- const Point& point, bool no_leave,
- bool no_enter);
-
- template <typename EventArgs, typename... Args>
- void DispatchEvent(Control* const original_sender,
- events::RoutedEvent<EventArgs>* (Control::*event_ptr)(),
- Control* const last_receiver, Args&&... args) {
- constexpr auto kLogTag = "cru::ui::controls::DispatchEvent";
-
- event_handling_count_++;
- Guard event_handling_count_guard([this] { event_handling_count_--; });
-
- if (original_sender == nullptr || original_sender == last_receiver) return;
-
- std::string log = "Begin dispatching routed event " +
- (original_sender->*event_ptr)()->GetName() +
- ":\n\tTunnel:";
-
- Guard logging_guard([&] {
- log += "\nEnd dispatching routed event " +
- (original_sender->*event_ptr)()->GetName() + ".";
- CRU_LOG_TAG_DEBUG("{}", log);
- });
-
- std::vector<ObjectResolver<Control>> receive_list;
-
- auto parent = original_sender;
- while (parent != last_receiver) {
- receive_list.push_back(parent->CreateResolver());
- parent = parent->GetParent();
- }
-
- auto handled = false;
-
- // tunnel
- for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) {
- auto control = i->Resolve();
- log += " ";
- if (!control) {
- log += "(deleted)";
- continue;
- }
- log += control->GetDebugId();
- EventArgs event_args(control, original_sender,
- std::forward<Args>(args)...);
- (control->*event_ptr)()->tunnel_.Raise(event_args);
- if (event_args.IsHandled()) {
- log += " marked as handled.";
- handled = true;
- break;
- }
- }
-
- // bubble
- if (!handled) {
- log += "\n\tBubble:";
- for (auto resolver : receive_list) {
- auto control = resolver.Resolve();
- log += " ";
- if (!control) {
- log += "(deleted)";
- continue;
- }
- log += control->GetDebugId();
- EventArgs event_args(control, original_sender,
- std::forward<Args>(args)...);
- (control->*event_ptr)()->bubble_.Raise(event_args);
- if (event_args.IsHandled()) {
- log += " marked as handled.";
- break;
- }
- }
- }
-
- log += "\n\tDirect:";
- // direct
- for (auto resolver : receive_list) {
- auto control = resolver.Resolve();
- log += " ";
- if (!control) {
- log += "(deleted)";
- continue;
- }
- log += control->GetDebugId();
- EventArgs event_args(control, original_sender,
- std::forward<Args>(args)...);
- (control->*event_ptr)()->direct_.Raise(event_args);
- }
- }
-
- void UpdateCursor();
- void NotifyControlDestroyed(Control* control);
-
private:
- int event_handling_count_;
-
- std::unique_ptr<platform::gui::INativeWindow> native_window_;
-
- Control* focus_control_;
- Control* mouse_hover_control_;
- Control* mouse_captured_control_;
-
- std::shared_ptr<platform::gui::ICursor> override_cursor_;
-
- bool layout_prefer_to_fill_window_;
-
- platform::gui::TimerAutoCanceler repaint_schedule_canceler_;
- platform::gui::TimerAutoCanceler relayout_schedule_canceler_;
+ std::shared_ptr<ControlHost> control_host_;
Control* attached_control_;
diff --git a/include/cru/ui/events/RoutedEvent.h b/include/cru/ui/events/RoutedEvent.h
index 58e50d63..b14b6e42 100644
--- a/include/cru/ui/events/RoutedEvent.h
+++ b/include/cru/ui/events/RoutedEvent.h
@@ -8,7 +8,7 @@ namespace cru::ui::events {
// EventArgs must be reference because the IsHandled property must be settable.
template <typename TEventArgs>
class CRU_UI_API RoutedEvent {
- friend controls::Window;
+ friend controls::ControlHost;
public:
static_assert(std::is_base_of_v<UiEventArgs, TEventArgs>,
diff --git a/include/cru/ui/render/RenderObject.h b/include/cru/ui/render/RenderObject.h
index c299ea24..4c19ad3e 100644
--- a/include/cru/ui/render/RenderObject.h
+++ b/include/cru/ui/render/RenderObject.h
@@ -137,7 +137,7 @@ class CRU_UI_API RenderObject : public Object {
virtual RenderObject* HitTest(const Point& point) = 0;
public:
- controls::Window* GetWindow();
+ controls::ControlHost* GetControlHost();
void InvalidateLayout();
void InvalidatePaint();
diff --git a/src/ThemeBuilder/components/StyleRuleEditor.cpp b/src/ThemeBuilder/components/StyleRuleEditor.cpp
index f3de97b4..f57e4654 100644
--- a/src/ThemeBuilder/components/StyleRuleEditor.cpp
+++ b/src/ThemeBuilder/components/StyleRuleEditor.cpp
@@ -43,7 +43,7 @@ ui::style::StyleRule StyleRuleEditor::GetValue() const {
void StyleRuleEditor::SetValue(const ui::style::StyleRule& style_rule,
bool trigger_change) {
- body_layout_.ClearChildren();
+ body_layout_.RemoveAllChild();
condition_editor_ =
components::conditions::CreateConditionEditor(style_rule.GetCondition());
styler_editor_ =
diff --git a/src/ThemeBuilder/components/StyleRuleSetEditor.cpp b/src/ThemeBuilder/components/StyleRuleSetEditor.cpp
index f2509f4f..fd159a44 100644
--- a/src/ThemeBuilder/components/StyleRuleSetEditor.cpp
+++ b/src/ThemeBuilder/components/StyleRuleSetEditor.cpp
@@ -80,7 +80,7 @@ void StyleRuleSetEditor::UpdateView(
});
style_rule_editors_.insert(style_rule_editors_.cbegin() + i,
std::move(style_rule_editor));
- rules_layout_.AddChildAt(style_rule_editors_.back()->GetRootControl(),
+ rules_layout_.InsertChildAt(style_rule_editors_.back()->GetRootControl(),
i);
}
break;
diff --git a/src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp b/src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp
index b9b1fdef..d7324350 100644
--- a/src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp
+++ b/src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp
@@ -63,7 +63,8 @@ CompoundConditionEditor::CompoundConditionEditor() {
if (editor) {
ConnectChangeEvent(editor.get());
editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
- auto index = this->children_container_.IndexOf(c->GetRootControl());
+ auto index =
+ this->children_container_.IndexOfChild(c->GetRootControl());
this->children_.erase(this->children_.begin() + index);
this->children_container_.RemoveChildAt(index);
RaiseChangeEvent();
@@ -88,13 +89,13 @@ CompoundConditionEditor::GetChildren() {
void CompoundConditionEditor::SetChildren(
std::vector<ClonePtr<ui::style::Condition>> children, bool trigger_change) {
- children_container_.ClearChildren();
+ children_container_.RemoveAllChild();
children_.clear();
for (const auto& condition : children) {
auto editor = CreateConditionEditor(condition.get());
ConnectChangeEvent(editor.get());
editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
- auto index = this->children_container_.IndexOf(c->GetRootControl());
+ auto index = this->children_container_.IndexOfChild(c->GetRootControl());
this->children_.erase(this->children_.begin() + index);
this->children_container_.RemoveChildAt(index);
RaiseChangeEvent();
diff --git a/src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp b/src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp
index 1a20bf70..90d19a68 100644
--- a/src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp
+++ b/src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp
@@ -65,7 +65,8 @@ CompoundStylerEditor::CompoundStylerEditor() {
if (editor) {
ConnectChangeEvent(editor.get());
editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
- auto index = this->children_container_.IndexOf(c->GetRootControl());
+ auto index =
+ this->children_container_.IndexOfChild(c->GetRootControl());
this->children_.erase(this->children_.begin() + index);
this->children_container_.RemoveChildAt(index);
RaiseChangeEvent();
@@ -94,7 +95,7 @@ void CompoundStylerEditor::SetValue(ui::style::CompoundStyler* value,
auto editor = CreateStylerEditor(styler.get());
ConnectChangeEvent(editor.get());
editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
- auto index = this->children_container_.IndexOf(c->GetRootControl());
+ auto index = this->children_container_.IndexOfChild(c->GetRootControl());
this->children_.erase(this->children_.begin() + index);
this->children_container_.RemoveChildAt(index);
RaiseChangeEvent();
diff --git a/src/platform/gui/win/Window.cpp b/src/platform/gui/win/Window.cpp
index dfbe0f96..38e4a0b6 100644
--- a/src/platform/gui/win/Window.cpp
+++ b/src/platform/gui/win/Window.cpp
@@ -79,7 +79,6 @@ WinNativeWindow::~WinNativeWindow() {
void WinNativeWindow::Close() {
if (hwnd_) ::DestroyWindow(hwnd_);
- application_->UnregisterWindow(this);
}
void WinNativeWindow::SetParent(INativeWindow* parent) {
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 84849d44..e931b678 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -12,9 +12,9 @@ add_library(CruUi
controls/CheckBox.cpp
controls/Container.cpp
controls/Control.cpp
+ controls/ControlHost.cpp
controls/FlexLayout.cpp
controls/IconButton.cpp
- controls/NoChildControl.cpp
controls/ScrollView.cpp
controls/StackLayout.cpp
controls/TextBlock.cpp
diff --git a/src/ui/components/Menu.cpp b/src/ui/components/Menu.cpp
index a80ce92f..380da911 100644
--- a/src/ui/components/Menu.cpp
+++ b/src/ui/components/Menu.cpp
@@ -3,6 +3,7 @@
#include "cru/ui/ThemeManager.h"
#include "cru/ui/controls/Button.h"
#include "cru/ui/controls/Control.h"
+#include "cru/ui/controls/ControlHost.h"
#include "cru/ui/controls/FlexLayout.h"
#include "cru/ui/controls/TextBlock.h"
#include "cru/ui/controls/Window.h"
@@ -38,7 +39,7 @@ void Menu::AddItemAt(Component* item, Index index) {
Expects(index >= 0 && index <= GetItemCount());
items_.insert(items_.cbegin() + index, item);
- container_.AddChildAt(item->GetRootControl(), index);
+ container_.InsertChildAt(item->GetRootControl(), index);
}
Component* Menu::RemoveItemAt(Index index) {
@@ -53,7 +54,7 @@ Component* Menu::RemoveItemAt(Index index) {
}
void Menu::ClearItems() {
- container_.ClearChildren();
+ container_.RemoveAllChild();
for (auto item : items_) {
item->DeleteIfDeleteByParent();
@@ -79,7 +80,7 @@ PopupMenu::PopupMenu(controls::Control* attached_control)
menu_.SetOnItemClick([this](Index) { popup_->GetNativeWindow()->Close(); });
popup_ = controls::Window::CreatePopup();
popup_->SetAttachedControl(attached_control);
- popup_->AddChildAt(menu_.GetRootControl(), 0);
+ popup_->InsertChildAt(menu_.GetRootControl(), 0);
}
PopupMenu::~PopupMenu() { delete popup_; }
@@ -94,7 +95,7 @@ void PopupMenu::SetPosition(const Point& position) {
void PopupMenu::Show() {
auto native_window = popup_->GetNativeWindow();
native_window->SetVisibility(platform::gui::WindowVisibilityType::Show);
- popup_->RelayoutWithSize(Size::Infinite(), true);
+ popup_->GetControlHost()->RelayoutWithSize(Size::Infinite(), true);
native_window->RequestFocus();
native_window->SetToForeground();
}
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
diff --git a/src/ui/events/MouseEventArgs.cpp b/src/ui/events/MouseEventArgs.cpp
index bbbf5cd4..ff3a2372 100644
--- a/src/ui/events/MouseEventArgs.cpp
+++ b/src/ui/events/MouseEventArgs.cpp
@@ -1,7 +1,7 @@
#include "cru/ui/events/MouseEventArgs.h"
#include "cru/ui/controls/Control.h"
-#include "cru/ui/controls/Window.h"
+#include "cru/ui/controls/ControlHost.h"
#include "cru/ui/render/RenderObject.h"
namespace cru::ui::events {
@@ -17,9 +17,8 @@ Point MouseEventArgs::GetPointToContent(
Point MouseEventArgs::GetPointOfScreen() const {
auto sender = GetSender();
if (auto control = dynamic_cast<controls::Control*>(sender)) {
- if (auto window = control->GetWindow())
- return GetPoint() +
- window->GetNativeWindow()->GetClientRect().GetLeftTop();
+ if (auto host = control->GetControlHost())
+ return GetPoint() + host->GetNativeWindow()->GetClientRect().GetLeftTop();
}
return GetPoint();
}
diff --git a/src/ui/helper/ClickDetector.cpp b/src/ui/helper/ClickDetector.cpp
index be1cbca2..0ac0515f 100644
--- a/src/ui/helper/ClickDetector.cpp
+++ b/src/ui/helper/ClickDetector.cpp
@@ -3,14 +3,13 @@
#include "cru/base/log/Logger.h"
#include "cru/ui/DebugFlags.h"
#include "cru/ui/controls/Control.h"
-#include "cru/ui/controls/Window.h"
+#include "cru/ui/controls/ControlHost.h"
namespace cru::ui::helper {
Point ClickEventArgs::GetDownPointOfScreen() const {
- auto window = sender_->GetWindow();
- if (window != nullptr) {
- return down_point_ +
- window->GetNativeWindow()->GetClientRect().GetLeftTop();
+ auto host = sender_->GetControlHost();
+ if (host != nullptr) {
+ return down_point_ + host->GetNativeWindow()->GetClientRect().GetLeftTop();
} else {
return down_point_;
}
diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp
index a4da2414..fbb7c292 100644
--- a/src/ui/render/RenderObject.cpp
+++ b/src/ui/render/RenderObject.cpp
@@ -4,7 +4,7 @@
#include "cru/platform/GraphicsBase.h"
#include "cru/ui/DebugFlags.h"
#include "cru/ui/controls/Control.h"
-#include "cru/ui/controls/Window.h"
+#include "cru/ui/controls/ControlHost.h"
namespace cru::ui::render {
const BoxConstraint BoxConstraint::kNotLimit{Size::kMax, Size::kZero};
@@ -265,21 +265,21 @@ Rect RenderObject::GetContentRect() const {
return rect;
}
-controls::Window* RenderObject::GetWindow() {
+controls::ControlHost* RenderObject::GetControlHost() {
if (control_) {
- return control_->GetWindow();
+ return control_->GetControlHost();
}
return nullptr;
}
void RenderObject::InvalidateLayout() {
- if (auto window = GetWindow()) {
- window->InvalidateLayout();
+ if (auto host = GetControlHost()) {
+ host->InvalidateLayout();
}
}
void RenderObject::InvalidatePaint() {
- if (auto window = GetWindow()) {
+ if (auto window = GetControlHost()) {
window->InvalidatePaint();
}
}
diff --git a/src/ui/render/ScrollBar.cpp b/src/ui/render/ScrollBar.cpp
index 3834fffb..4343f15a 100644
--- a/src/ui/render/ScrollBar.cpp
+++ b/src/ui/render/ScrollBar.cpp
@@ -8,7 +8,7 @@
#include "cru/platform/gui/Cursor.h"
#include "cru/ui/Base.h"
#include "cru/ui/ThemeManager.h"
-#include "cru/ui/controls/Window.h"
+#include "cru/ui/controls/ControlHost.h"
#include "cru/ui/render/ScrollRenderObject.h"
#include <algorithm>
@@ -317,8 +317,8 @@ void ScrollBar::OnDraw(platform::graphics::IPainter* painter,
void ScrollBar::SetCursor() {
if (const auto control = render_object_->GetAttachedControl()) {
- if (const auto window = control->GetWindow()) {
- window->SetOverrideCursor(
+ if (const auto host = control->GetControlHost()) {
+ host->SetOverrideCursor(
GetUiApplication()->GetCursorManager()->GetSystemCursor(
platform::gui::SystemCursorType::Arrow));
cursor_overridden_ = true;
@@ -329,8 +329,8 @@ void ScrollBar::SetCursor() {
void ScrollBar::RestoreCursor() {
if (cursor_overridden_) {
if (const auto control = render_object_->GetAttachedControl()) {
- if (const auto window = control->GetWindow()) {
- window->SetOverrideCursor(nullptr);
+ if (const auto host = control->GetControlHost()) {
+ host->SetOverrideCursor(nullptr);
}
}
cursor_overridden_ = false;