yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
resize_handles.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5
8#include "imgui/imgui.h"
9#include "imgui/imgui_internal.h"
10
11namespace yaze {
12namespace gui {
13namespace resize_handles_internal {
14
15float SnapCoord(float v, float snap) {
16 if (snap <= 0.0f)
17 return v;
18 return std::round(v / snap) * snap;
19}
20
21HandleZone HitTestZone(ImVec2 mouse, const ImRect& rect, HandleMask mask,
22 float handle_size) {
23 const float s = handle_size;
24 const float half = s * 0.5f;
25 const ImVec2 mn = rect.Min;
26 const ImVec2 mx = rect.Max;
27
28 auto contains = [&](float x0, float y0, float x1, float y1) {
29 return mouse.x >= x0 && mouse.x <= x1 && mouse.y >= y0 && mouse.y <= y1;
30 };
31
32 // Corner hit-tests first (priority over edges).
33 if (Has(mask, HandleMask::kNW) &&
34 contains(mn.x - half, mn.y - half, mn.x + half, mn.y + half)) {
35 return HandleZone::kNW;
36 }
37 if (Has(mask, HandleMask::kNE) &&
38 contains(mx.x - half, mn.y - half, mx.x + half, mn.y + half)) {
39 return HandleZone::kNE;
40 }
41 if (Has(mask, HandleMask::kSW) &&
42 contains(mn.x - half, mx.y - half, mn.x + half, mx.y + half)) {
43 return HandleZone::kSW;
44 }
45 if (Has(mask, HandleMask::kSE) &&
46 contains(mx.x - half, mx.y - half, mx.x + half, mx.y + half)) {
47 return HandleZone::kSE;
48 }
49
50 // Edge strips (inset from corners).
51 if (Has(mask, HandleMask::kN) &&
52 contains(mn.x + half, mn.y - half, mx.x - half, mn.y + half)) {
53 return HandleZone::kN;
54 }
55 if (Has(mask, HandleMask::kS) &&
56 contains(mn.x + half, mx.y - half, mx.x - half, mx.y + half)) {
57 return HandleZone::kS;
58 }
59 if (Has(mask, HandleMask::kW) &&
60 contains(mn.x - half, mn.y + half, mn.x + half, mx.y - half)) {
61 return HandleZone::kW;
62 }
63 if (Has(mask, HandleMask::kE) &&
64 contains(mx.x - half, mn.y + half, mx.x + half, mx.y - half)) {
65 return HandleZone::kE;
66 }
67
68 return HandleZone::kNone;
69}
70
71void ApplyDragDelta(ImRect* rect, HandleZone zone, ImVec2 delta, float snap,
72 float min_width, float min_height) {
73 if (!rect || zone == HandleZone::kNone)
74 return;
75
76 const bool move_min_x = zone == HandleZone::kNW || zone == HandleZone::kW ||
77 zone == HandleZone::kSW;
78 const bool move_max_x = zone == HandleZone::kNE || zone == HandleZone::kE ||
79 zone == HandleZone::kSE;
80 const bool move_min_y = zone == HandleZone::kNW || zone == HandleZone::kN ||
81 zone == HandleZone::kNE;
82 const bool move_max_y = zone == HandleZone::kSW || zone == HandleZone::kS ||
83 zone == HandleZone::kSE;
84
85 if (move_min_x)
86 rect->Min.x = SnapCoord(rect->Min.x + delta.x, snap);
87 if (move_max_x)
88 rect->Max.x = SnapCoord(rect->Max.x + delta.x, snap);
89 if (move_min_y)
90 rect->Min.y = SnapCoord(rect->Min.y + delta.y, snap);
91 if (move_max_y)
92 rect->Max.y = SnapCoord(rect->Max.y + delta.y, snap);
93
94 // Enforce minimum size by pushing the dragged side, not the anchor side.
95 if (move_min_x && rect->Max.x - rect->Min.x < min_width)
96 rect->Min.x = rect->Max.x - min_width;
97 if (move_max_x && rect->Max.x - rect->Min.x < min_width)
98 rect->Max.x = rect->Min.x + min_width;
99 if (move_min_y && rect->Max.y - rect->Min.y < min_height)
100 rect->Min.y = rect->Max.y - min_height;
101 if (move_max_y && rect->Max.y - rect->Min.y < min_height)
102 rect->Max.y = rect->Min.y + min_height;
103}
104
106 const auto& theme = ThemeManager::Get().GetCurrentTheme();
107 return ConvertColorToImVec4(theme.selection_handle);
108}
109
110} // namespace resize_handles_internal
111
112namespace {
113
114ImGuiMouseCursor CursorForZone(HandleZone zone) {
115 switch (zone) {
116 case HandleZone::kN:
117 case HandleZone::kS:
118 return ImGuiMouseCursor_ResizeNS;
119 case HandleZone::kW:
120 case HandleZone::kE:
121 return ImGuiMouseCursor_ResizeEW;
122 case HandleZone::kNE:
123 case HandleZone::kSW:
124 return ImGuiMouseCursor_ResizeNESW;
125 case HandleZone::kNW:
126 case HandleZone::kSE:
127 return ImGuiMouseCursor_ResizeNWSE;
128 default:
129 return ImGuiMouseCursor_Arrow;
130 }
131}
132
133ImRect HandleRect(HandleZone zone, const ImRect& rect, float handle_size) {
134 const float half = handle_size * 0.5f;
135 const ImVec2 mn = rect.Min;
136 const ImVec2 mx = rect.Max;
137 switch (zone) {
138 case HandleZone::kNW:
139 return ImRect(mn.x - half, mn.y - half, mn.x + half, mn.y + half);
140 case HandleZone::kNE:
141 return ImRect(mx.x - half, mn.y - half, mx.x + half, mn.y + half);
142 case HandleZone::kSW:
143 return ImRect(mn.x - half, mx.y - half, mn.x + half, mx.y + half);
144 case HandleZone::kSE:
145 return ImRect(mx.x - half, mx.y - half, mx.x + half, mx.y + half);
146 case HandleZone::kN:
147 return ImRect(mn.x + half, mn.y - half, mx.x - half, mn.y + half);
148 case HandleZone::kS:
149 return ImRect(mn.x + half, mx.y - half, mx.x - half, mx.y + half);
150 case HandleZone::kW:
151 return ImRect(mn.x - half, mn.y + half, mn.x + half, mx.y - half);
152 case HandleZone::kE:
153 return ImRect(mx.x - half, mn.y + half, mx.x + half, mx.y - half);
154 default:
155 return ImRect();
156 }
157}
158
159} // namespace
160
161bool ResizeHandles(ImRect* rect, HandleMask mask, float snap, ImGuiID id,
162 const ResizeHandleStyle& style) {
163 if (!rect)
164 return false;
165
166 ImGuiContext* ctx = ImGui::GetCurrentContext();
167 if (ctx == nullptr)
168 return false;
169
170 ImGuiWindow* window = ImGui::GetCurrentWindow();
171 if (window == nullptr || window->SkipItems)
172 return false;
173
174 if (id == 0) {
175 id = window->GetID(rect);
176 }
177
178 const ImVec2 mouse = ImGui::GetMousePos();
180 mouse, *rect, mask, style.handle_size);
181
182 // Per-zone button behavior: tracks which zone owns the active drag.
183 static thread_local HandleZone s_active_zone = HandleZone::kNone;
184 static thread_local ImGuiID s_active_id = 0;
185 static thread_local ImVec2 s_last_mouse = ImVec2(0, 0);
186
187 bool committed = false;
188
189 if (s_active_id == id && s_active_zone != HandleZone::kNone) {
190 if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
191 const ImVec2 delta(mouse.x - s_last_mouse.x, mouse.y - s_last_mouse.y);
193 rect, s_active_zone, delta, snap, style.min_width, style.min_height);
194 s_last_mouse = mouse;
195 ImGui::SetMouseCursor(CursorForZone(s_active_zone));
196 } else {
197 // Mouse released — commit the drag.
198 committed = true;
199 s_active_zone = HandleZone::kNone;
200 s_active_id = 0;
201 }
202 } else if (hovered_zone != HandleZone::kNone &&
203 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
204 s_active_id = id;
205 s_active_zone = hovered_zone;
206 s_last_mouse = mouse;
207 ImGui::SetMouseCursor(CursorForZone(hovered_zone));
208 } else if (hovered_zone != HandleZone::kNone) {
209 ImGui::SetMouseCursor(CursorForZone(hovered_zone));
210 }
211
212 // Draw handles.
213 ImVec4 color = style.color;
214 if (color.w == 0.0f) {
216 }
217 const ImU32 color_u32 = ImGui::ColorConvertFloat4ToU32(color);
218
219 ImDrawList* draw_list = window->DrawList;
220 static constexpr HandleZone kAllZones[] = {
223 };
224 static constexpr HandleMask kZoneMask[] = {
227 };
228 for (std::size_t i = 0; i < 8; ++i) {
229 if (!Has(mask, kZoneMask[i]))
230 continue;
231 const ImRect r = HandleRect(kAllZones[i], *rect, style.handle_size);
232 draw_list->AddRectFilled(r.Min, r.Max, color_u32);
233 }
234
235 return committed;
236}
237
238} // namespace gui
239} // namespace yaze
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
ImRect HandleRect(HandleZone zone, const ImRect &rect, float handle_size)
ImGuiMouseCursor CursorForZone(HandleZone zone)
float SnapCoord(float v, float snap)
void ApplyDragDelta(ImRect *rect, HandleZone zone, ImVec2 delta, float snap, float min_width, float min_height)
HandleZone HitTestZone(ImVec2 mouse, const ImRect &rect, HandleMask mask, float handle_size)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
constexpr bool Has(HandleMask all, HandleMask flag)
bool ResizeHandles(ImRect *rect, HandleMask mask, float snap, ImGuiID id, const ResizeHandleStyle &style)