Skip to content

Commit e344422

Browse files
committed
[ECSCircuit] Add window titlebar functionality and adjust reflection
1 parent 40d1304 commit e344422

15 files changed

Lines changed: 233 additions & 57 deletions

File tree

Engine/Source/ECSCircuit/Private/ECSCircuit/CircuitManager.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,21 @@ void CircuitManager::Shutdown()
3333
}
3434

3535
void CircuitManager::Update()
36-
{}
36+
{
37+
for (Volt::WindowHandle handle : m_pendingCloses)
38+
{
39+
if (!m_windows.contains(handle))
40+
{
41+
continue;
42+
}
43+
44+
m_inputHandler.DeregisterWindowInput(handle);
45+
m_windows[handle]->Shutdown();
46+
m_windows.erase(handle);
47+
Volt::WindowManager_New::Get().DestroyWindow(handle);
48+
}
49+
m_pendingCloses.clear();
50+
}
3751

3852
Ref<UI::CircuitWindow> CircuitManager::CreateWindow()
3953
{
@@ -59,5 +73,11 @@ Ref<UI::CircuitWindow> CircuitManager::CreateWindow()
5973
circuitWindow->OnRender();
6074
});
6175

76+
// Close() fires mid render/input dispatch; defer destruction to Update.
77+
window.GetOnWindowClosed().AddLambda([this](Volt::Window_New& windowObject)
78+
{
79+
m_pendingCloses.push_back(windowObject.GetHandle());
80+
});
81+
6282
return circuitWindow;
6383
}

Engine/Source/ECSCircuit/Private/ECSCircuit/Input/CircuitInputHandler.cpp

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,6 @@ namespace UI
2525
// Cursor travel (in pixels) required before a press turns into a drag.
2626
constexpr float dragThreshold = 4.f;
2727

28-
bool IsDescendantOf(Widget widget, Widget root)
29-
{
30-
while (widget.IsValid())
31-
{
32-
if (widget.ID() == root.ID())
33-
{
34-
return true;
35-
}
36-
if (!widget.HasComponent<RelationshipC>())
37-
{
38-
return false;
39-
}
40-
widget = widget.GetComponent<RelationshipC>().parent;
41-
}
42-
return false;
43-
}
44-
4528
template<typename TCapability>
4629
Widget HitTest(UIScene& scene, Widget root, const glm::vec2& point)
4730
{
@@ -65,7 +48,7 @@ namespace UI
6548
}
6649

6750
Widget candidate{ entt::handle{ registry, entity } };
68-
if (!IsDescendantOf(candidate, root))
51+
if (!candidate.IsDescendantOf(root))
6952
{
7053
return;
7154
}
@@ -203,6 +186,11 @@ namespace UI
203186
}
204187
}
205188

189+
Widget CircuitInputHandler::HitTestClickable(UIScene& scene, Widget root, const glm::vec2& point)
190+
{
191+
return HitTest<ClickableC>(scene, root, point);
192+
}
193+
206194
CircuitInputHandler::~CircuitInputHandler()
207195
{
208196
for (const auto& [windowHandle, delegateHandle] : m_registeredWindows)
@@ -299,7 +287,7 @@ namespace UI
299287
{
300288
hit = HitTest<HoverableC>(scene, root, m_mouseState.position);
301289
}
302-
else if (!(m_hoveredWidget.IsValid() && IsDescendantOf(m_hoveredWidget, root)))
290+
else if (!(m_hoveredWidget.IsValid() && m_hoveredWidget.IsDescendantOf(root)))
303291
{
304292
// Cursor is in another window; leave its hover state alone.
305293
return;
@@ -342,7 +330,7 @@ namespace UI
342330
UpdateHover(scene, root, ownsMouse && m_mouseState.cursorInside);
343331

344332
// Cursor position is only valid in the owning window's coordinate space.
345-
if (ownsMouse && m_draggedWidget.IsValid() && IsDescendantOf(m_draggedWidget, root))
333+
if (ownsMouse && m_draggedWidget.IsValid() && m_draggedWidget.IsDescendantOf(root))
346334
{
347335
if (m_mouseState.position != m_dragLastPosition)
348336
{
@@ -355,7 +343,7 @@ namespace UI
355343
FireDrag(scene, m_draggedWidget, dragInteraction);
356344
}
357345
}
358-
else if (ownsMouse && m_dragCandidate.IsValid() && IsDescendantOf(m_dragCandidate, root))
346+
else if (ownsMouse && m_dragCandidate.IsValid() && m_dragCandidate.IsDescendantOf(root))
359347
{
360348
if (glm::distance(m_mouseState.position, m_dragPressPosition) >= dragThreshold)
361349
{

Engine/Source/ECSCircuit/Private/ECSCircuit/Rendering/CircuitRenderer.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ namespace UI
5252
{
5353
VT_PROFILE_FUNCTION();
5454

55+
//skip render if the window is minimized
56+
if (m_targetWindow.IsMinimized())
57+
{
58+
return;
59+
}
60+
5561
const uint32_t swapchainWidth = m_targetWindow.GetSwapchain().GetWidth();
5662
const uint32_t swapchainHeight = m_targetWindow.GetSwapchain().GetHeight();
5763

Engine/Source/ECSCircuit/Private/ECSCircuit/Window/CircuitWindow.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "ECSCircuit/Window/CircuitWindow.h"
44

55
#include "ECSCircuit/Components/BoundsC.h"
6+
#include "ECSCircuit/Components/ClickableC.h"
67
#include "ECSCircuit/Components/LayoutC.h"
78
#include "ECSCircuit/Components/PainterC.h"
89
#include "ECSCircuit/Components/RelationshipC.h"
@@ -35,11 +36,77 @@ namespace UI
3536
if (m_scene)
3637
{
3738
m_root = m_scene->Spawn<WindowW>(Widget{});
39+
40+
// Capture the handle by value: these outlive any `this` guarantees.
41+
const Volt::WindowHandle handle = m_windowHandle;
42+
43+
WindowW& windowWidget = m_root.GetComponent<WindowW>();
44+
windowWidget.closeButton.GetComponent<ClickableC>().onClicked =
45+
[handle](UIScene&, Widget, const ClickInteraction&)
46+
{
47+
Volt::WindowManager_New::Get().GetWindow(handle).Close();
48+
};
49+
windowWidget.minimizeButton.GetComponent<ClickableC>().onClicked =
50+
[handle](UIScene&, Widget, const ClickInteraction&)
51+
{
52+
Volt::WindowManager_New::Get().GetWindow(handle).Minimize();
53+
};
54+
windowWidget.maximizeRestoreButton.GetComponent<ClickableC>().onClicked =
55+
[handle](UIScene&, Widget, const ClickInteraction&)
56+
{
57+
Volt::Window_New& window = Volt::WindowManager_New::Get().GetWindow(handle);
58+
if (window.IsMaximized())
59+
{
60+
window.Restore();
61+
}
62+
else
63+
{
64+
window.Maximize();
65+
}
66+
};
3867
}
68+
69+
// The OS asks via WM_NCHITTEST; returning true means HTCAPTION (native drag/snap).
70+
Volt::Window_New& window = Volt::WindowManager_New::Get().GetWindow(m_windowHandle);
71+
window.GetIsHoveringTitlebar().BindLambda([this](int32_t x, int32_t y)
72+
{
73+
return IsHoveringTitlebar(x, y);
74+
});
3975
}
4076

4177
CircuitWindow::~CircuitWindow() = default;
4278

79+
void CircuitWindow::Shutdown()
80+
{
81+
Volt::Window_New& window = Volt::WindowManager_New::Get().GetWindow(m_windowHandle);
82+
window.GetIsHoveringTitlebar().Unbind();
83+
84+
if (m_scene && m_root.IsValid())
85+
{
86+
m_scene->Destroy(m_root);
87+
}
88+
m_root = Widget{};
89+
m_scene = nullptr;
90+
}
91+
92+
bool CircuitWindow::IsHoveringTitlebar(int32_t x, int32_t y)
93+
{
94+
if (!m_scene || !m_root.IsValid())
95+
{
96+
return false;
97+
}
98+
99+
const WindowW& windowWidget = m_root.GetComponent<WindowW>();
100+
const glm::vec2 point{ static_cast<float>(x), static_cast<float>(y) };
101+
if (!windowWidget.IsPointHoveringTitlebar(point))
102+
{
103+
return false;
104+
}
105+
106+
// Interactive widgets (window buttons) stay client area.
107+
return !CircuitInputHandler::HitTestClickable(*m_scene, m_root, point).IsValid();
108+
}
109+
43110
void CircuitWindow::Layout()
44111
{
45112
VT_PROFILE_FUNCTION();

Engine/Source/ECSCircuit/Public/ECSCircuit/CircuitManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <WindowModule/WindowHandle.h>
99

1010
#include <CoreUtilities/Containers/Map.h>
11+
#include <CoreUtilities/Containers/Vector.h>
1112
#include <CoreUtilities/Pointers/Unique.h>
1213
#include <CoreUtilities/Pointers/Ref.h>
1314

@@ -30,6 +31,8 @@ class CircuitManager
3031
private:
3132
static Unique<CircuitManager> s_instance;
3233
Map<Volt::WindowHandle, Ref<UI::CircuitWindow>> m_windows;
34+
// Windows requesting close mid-frame; destroyed in Update, outside any render callstack.
35+
Vector<Volt::WindowHandle> m_pendingCloses;
3336

3437
UI::UIScene m_scene;
3538
UI::CircuitInputHandler m_inputHandler;

Engine/Source/ECSCircuit/Public/ECSCircuit/Components/RelationshipC.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,22 @@ namespace UI
1313
// Distance from a root widget; deeper widgets paint on top of ancestors.
1414
int32_t depth = 0;
1515
};
16+
17+
inline bool Widget::IsDescendantOf(const Widget root) const
18+
{
19+
Widget current = *this;
20+
while (current.IsValid())
21+
{
22+
if (current.ID() == root.ID())
23+
{
24+
return true;
25+
}
26+
if (!current.HasComponent<RelationshipC>())
27+
{
28+
return false;
29+
}
30+
current = current.GetComponent<RelationshipC>().parent;
31+
}
32+
return false;
33+
}
1634
}

Engine/Source/ECSCircuit/Public/ECSCircuit/Core/Reflection.h

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ namespace UI
4949

5050
//Begin HasPaint
5151
template<typename T>
52-
concept HasPaintWithStyle = requires(Painter & painter, const Widget self, const T & w, const BoundsC & bounds, const T::Style & style)
52+
concept HasPaintWithStyle = requires
5353
{
54-
{ T::Paint(painter, self, w, bounds, style) };
54+
static_cast<void (*)(Painter&, const Widget, const T&, const BoundsC&, const typename T::Style&)>(&T::Paint);
5555
};
5656
template<typename T>
57-
concept HasPaintWithoutStyle = requires(Painter & painter, const Widget self, const T & w, const BoundsC & bounds)
57+
concept HasPaintWithoutStyle = requires
5858
{
59-
{ T::Paint(painter, self, w, bounds) };
59+
static_cast<void (*)(Painter&, const Widget, const T&, const BoundsC&)>(&T::Paint);
6060
};
6161
template<typename T>
6262
concept HasPaint = HasPaintWithStyle<T> || HasPaintWithoutStyle<T>;
@@ -71,14 +71,14 @@ namespace UI
7171

7272
//Begin HasLayout
7373
template<typename T>
74-
concept HasLayoutWithStyle = requires(const Widget self, const T & w, const BoundsC & bounds, const T::Style & style)
74+
concept HasLayoutWithStyle = requires
7575
{
76-
{ T::Layout(self, w, bounds, style) };
76+
static_cast<void (*)(Widget, T&, const BoundsC&, const typename T::Style&)>(&T::Layout);
7777
};
7878
template<typename T>
79-
concept HasLayoutWithoutStyle = requires(const Widget self, const T & w, const BoundsC & bounds)
79+
concept HasLayoutWithoutStyle = requires
8080
{
81-
{ T::Layout(self, w, bounds) };
81+
static_cast<void (*)(Widget, T&, const BoundsC&)>(&T::Layout);
8282
};
8383
template<typename T>
8484
concept HasLayout = HasLayoutWithStyle<T> || HasLayoutWithoutStyle<T>;
@@ -93,29 +93,29 @@ namespace UI
9393

9494
//Begin click handlers (OnClickBegin / OnClicked / OnClickCancelled)
9595
template<typename T>
96-
concept HasOnClickBegin = requires(UIScene & ui, Widget self, T & w, const ClickInteraction & interaction)
96+
concept HasOnClickBegin = requires
9797
{
98-
{ T::OnClickBegin(ui, self, w, interaction) };
98+
static_cast<void (*)(UIScene&, Widget, T&, const ClickInteraction&)>(&T::OnClickBegin);
9999
};
100100
template<typename T>
101101
concept HasAnyOnClickBegin = requires { T::OnClickBegin; };
102102
template<typename T>
103103
concept HasOnClickBeginWithWrongParameters = !HasOnClickBegin<T> && HasAnyOnClickBegin<T>;
104104

105105
template<typename T>
106-
concept HasOnClicked = requires(UIScene & ui, Widget self, T & w, const ClickInteraction & interaction)
106+
concept HasOnClicked = requires
107107
{
108-
{ T::OnClicked(ui, self, w, interaction) };
108+
static_cast<void (*)(UIScene&, Widget, T&, const ClickInteraction&)>(&T::OnClicked);
109109
};
110110
template<typename T>
111111
concept HasAnyOnClicked = requires { T::OnClicked; };
112112
template<typename T>
113113
concept HasOnClickedWithWrongParameters = !HasOnClicked<T> && HasAnyOnClicked<T>;
114114

115115
template<typename T>
116-
concept HasOnClickCancelled = requires(UIScene & ui, Widget self, T & w, const ClickInteraction & interaction)
116+
concept HasOnClickCancelled = requires
117117
{
118-
{ T::OnClickCancelled(ui, self, w, interaction) };
118+
static_cast<void (*)(UIScene&, Widget, T&, const ClickInteraction&)>(&T::OnClickCancelled);
119119
};
120120
template<typename T>
121121
concept HasAnyOnClickCancelled = requires { T::OnClickCancelled; };
@@ -129,19 +129,19 @@ namespace UI
129129

130130
//Begin hover handlers (OnHoverBegin / OnHoverEnd)
131131
template<typename T>
132-
concept HasOnHoverBegin = requires(UIScene & ui, Widget self, T & w)
132+
concept HasOnHoverBegin = requires
133133
{
134-
{ T::OnHoverBegin(ui, self, w) };
134+
static_cast<void (*)(UIScene&, Widget, T&)>(&T::OnHoverBegin);
135135
};
136136
template<typename T>
137137
concept HasAnyOnHoverBegin = requires { T::OnHoverBegin; };
138138
template<typename T>
139139
concept HasOnHoverBeginWithWrongParameters = !HasOnHoverBegin<T> && HasAnyOnHoverBegin<T>;
140140

141141
template<typename T>
142-
concept HasOnHoverEnd = requires(UIScene & ui, Widget self, T & w)
142+
concept HasOnHoverEnd = requires
143143
{
144-
{ T::OnHoverEnd(ui, self, w) };
144+
static_cast<void (*)(UIScene&, Widget, T&)>(&T::OnHoverEnd);
145145
};
146146
template<typename T>
147147
concept HasAnyOnHoverEnd = requires { T::OnHoverEnd; };
@@ -155,29 +155,29 @@ namespace UI
155155

156156
//Begin drag handlers (OnDragBegin / OnDrag / OnDragEnd)
157157
template<typename T>
158-
concept HasOnDragBegin = requires(UIScene & ui, Widget self, T & w, const DragInteraction & interaction)
158+
concept HasOnDragBegin = requires
159159
{
160-
{ T::OnDragBegin(ui, self, w, interaction) };
160+
static_cast<void (*)(UIScene&, Widget, T&, const DragInteraction&)>(&T::OnDragBegin);
161161
};
162162
template<typename T>
163163
concept HasAnyOnDragBegin = requires { T::OnDragBegin; };
164164
template<typename T>
165165
concept HasOnDragBeginWithWrongParameters = !HasOnDragBegin<T> && HasAnyOnDragBegin<T>;
166166

167167
template<typename T>
168-
concept HasOnDrag = requires(UIScene & ui, Widget self, T & w, const DragInteraction & interaction)
168+
concept HasOnDrag = requires
169169
{
170-
{ T::OnDrag(ui, self, w, interaction) };
170+
static_cast<void (*)(UIScene&, Widget, T&, const DragInteraction&)>(&T::OnDrag);
171171
};
172172
template<typename T>
173173
concept HasAnyOnDrag = requires { T::OnDrag; };
174174
template<typename T>
175175
concept HasOnDragWithWrongParameters = !HasOnDrag<T> && HasAnyOnDrag<T>;
176176

177177
template<typename T>
178-
concept HasOnDragEnd = requires(UIScene & ui, Widget self, T & w, const DragInteraction & interaction)
178+
concept HasOnDragEnd = requires
179179
{
180-
{ T::OnDragEnd(ui, self, w, interaction) };
180+
static_cast<void (*)(UIScene&, Widget, T&, const DragInteraction&)>(&T::OnDragEnd);
181181
};
182182
template<typename T>
183183
concept HasAnyOnDragEnd = requires { T::OnDragEnd; };

0 commit comments

Comments
 (0)