yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
layout_helpers.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <vector>
5
6#include "absl/strings/str_format.h"
11#include "imgui/imgui.h"
12#include "imgui/imgui_internal.h"
13
14#if defined(__APPLE__)
15#include <TargetConditionals.h>
16#endif
17
18#if defined(__APPLE__) && TARGET_OS_IOS == 1
20#endif
21
22namespace yaze {
23namespace gui {
24
25// Core sizing functions
27 const auto& theme = GetTheme();
28 return GetBaseFontSize() * theme.widget_height_multiplier *
29 theme.compact_factor;
30}
31
33 const auto& theme = GetTheme();
34 return GetBaseFontSize() * 0.5f * theme.spacing_multiplier *
35 theme.compact_factor;
36}
37
39 const auto& theme = GetTheme();
40 return GetBaseFontSize() * theme.toolbar_height_multiplier *
41 theme.compact_factor;
42}
43
45 const auto& theme = GetTheme();
46 return GetBaseFontSize() * 0.5f * theme.panel_padding_multiplier *
47 theme.compact_factor;
48}
49
51 const auto& theme = GetTheme();
52 return GetBaseFontSize() * 8.0f * theme.input_width_multiplier *
53 theme.compact_factor;
54}
55
59
64
69
73
75 const auto& theme = GetTheme();
76 return GetBaseFontSize() * 0.3f * theme.button_padding_multiplier *
77 theme.compact_factor;
78}
79
81 const auto& theme = GetTheme();
82 return GetBaseFontSize() * theme.table_row_height_multiplier *
83 theme.compact_factor;
84}
85
87 const auto& theme = GetTheme();
88 return GetBaseFontSize() * theme.canvas_toolbar_multiplier *
89 theme.compact_factor;
90}
91
93 SafeAreaInsets insets{};
94#if defined(__APPLE__) && TARGET_OS_IOS == 1
95 const auto ios_insets = ::yaze::platform::ios::GetSafeAreaInsets();
96 insets.left = ios_insets.left;
97 insets.right = ios_insets.right;
98 insets.top = ios_insets.top;
99 insets.bottom = ios_insets.bottom;
100#else
101 // Desktop (macOS, Linux, Windows): no safe area insets needed.
102 // DisplaySafeAreaPadding default (3px) is meant for mobile notch, not desktop.
103 insets = {};
104#endif
105 return insets;
106}
107
109 float top = GetSafeAreaInsets().top;
110#if defined(__APPLE__) && TARGET_OS_IOS == 1
111 top = std::max(top, ::yaze::platform::ios::GetOverlayTopInset());
112#endif
113 return top;
114}
115
117 ImGuiIO& io = ImGui::GetIO();
118 return (io.ConfigFlags & ImGuiConfigFlags_IsTouchScreen) != 0;
119}
120
122 return IsTouchDevice() ? 44.0f : 0.0f;
123}
124
128
130 const ImVec2& pos, const ImVec2& size, const ImVec2& rect_pos,
131 const ImVec2& rect_size, float min_visible) {
132 WindowClampResult result{pos, false};
133
134 if (rect_size.x <= 0.0f || rect_size.y <= 0.0f) {
135 return result;
136 }
137
138 const float max_visible_x = std::max(1.0f, size.x - 1.0f);
139 const float max_visible_y = std::max(1.0f, size.y - 1.0f);
140 float min_visible_x = std::min(min_visible, max_visible_x);
141 float min_visible_y = std::min(min_visible, max_visible_y);
142
143 // Avoid impossible constraints on tiny viewports.
144 min_visible_x = std::min(min_visible_x, rect_size.x * 0.5f);
145 min_visible_y = std::min(min_visible_y, rect_size.y * 0.5f);
146
147 const float min_x = rect_pos.x + min_visible_x - size.x;
148 const float max_x = rect_pos.x + rect_size.x - min_visible_x;
149 const float min_y = rect_pos.y + min_visible_y - size.y;
150 const float max_y = rect_pos.y + rect_size.y - min_visible_y;
151
152 result.pos.x = std::clamp(pos.x, min_x, max_x);
153 result.pos.y = std::clamp(pos.y, min_y, max_y);
154 result.clamped = (result.pos.x != pos.x) || (result.pos.y != pos.y);
155 return result;
156}
157
158// Layout utilities
159void LayoutHelpers::BeginPaddedPanel(const char* label, float padding) {
160 if (padding < 0.0f) {
161 padding = GetPanelPadding();
162 }
163 ImGui::BeginChild(label, ImVec2(0, 0), true);
164 ImGui::Dummy(ImVec2(padding, padding));
165 ImGui::SameLine();
166 ImGui::BeginGroup();
167 ImGui::Dummy(ImVec2(0, padding));
168}
169
171 ImGui::Dummy(ImVec2(0, GetPanelPadding()));
172 ImGui::EndGroup();
173 ImGui::SameLine();
174 ImGui::Dummy(ImVec2(GetPanelPadding(), 0));
175 ImGui::EndChild();
176}
177
178bool LayoutHelpers::BeginTableWithTheming(const char* str_id, int columns,
179 ImGuiTableFlags flags,
180 const ImVec2& outer_size,
181 float inner_width) {
182 const auto& theme = GetTheme();
183
184 // Apply theme colors to table
185 ImGui::PushStyleColor(ImGuiCol_TableHeaderBg,
186 ConvertColorToImVec4(theme.table_header_bg));
187 ImGui::PushStyleColor(ImGuiCol_TableBorderStrong,
188 ConvertColorToImVec4(theme.table_border_strong));
189 ImGui::PushStyleColor(ImGuiCol_TableBorderLight,
190 ConvertColorToImVec4(theme.table_border_light));
191 ImGui::PushStyleColor(ImGuiCol_TableRowBg,
192 ConvertColorToImVec4(theme.table_row_bg));
193 ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt,
194 ConvertColorToImVec4(theme.table_row_bg_alt));
195
196 // Set row height if not overridden by caller
197 if (!(flags & ImGuiTableFlags_NoHostExtendY)) {
198 ImGui::PushStyleVar(
199 ImGuiStyleVar_CellPadding,
200 ImVec2(ImGui::GetStyle().CellPadding.x, GetTableRowHeight() * 0.25f));
201 }
202
203 return ImGui::BeginTable(str_id, columns, flags, outer_size, inner_width);
204}
205
207 ImGui::EndTable();
208 // Pop style colors (5 colors pushed in BeginTableWithTheming)
209 ImGui::PopStyleColor(5);
210 // Pop style var if it was pushed (CellPadding)
211 ImGui::PopStyleVar(1);
212}
213
214void LayoutHelpers::BeginCanvasPanel(const char* label, ImVec2* canvas_size) {
215 const auto& theme = GetTheme();
216
217 // Apply theme to canvas container
218 ImGui::PushStyleColor(ImGuiCol_ChildBg,
219 ConvertColorToImVec4(theme.editor_background));
220 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
221
222 if (canvas_size) {
223 ImGui::BeginChild(label, *canvas_size, true);
224 } else {
225 ImGui::BeginChild(label, ImVec2(0, 0), true);
226 }
227}
228
230 ImGui::EndChild();
231 ImGui::PopStyleVar(1);
232 ImGui::PopStyleColor(1);
233}
234
235bool LayoutHelpers::BeginContentChild(const char* id, const ImVec2& min_size,
236 bool border, ImGuiWindowFlags flags) {
237 const ImVec2 size = GetContentRegionAvailClamped(min_size);
238 return ImGui::BeginChild(id, size, border, flags);
239}
240
241// Input field helpers
242bool LayoutHelpers::AutoSizedInputField(const char* label, char* buf,
243 size_t buf_size,
244 ImGuiInputTextFlags flags) {
245 ImGui::SetNextItemWidth(GetStandardInputWidth());
246 return ImGui::InputText(label, buf, buf_size, flags);
247}
248
249bool LayoutHelpers::AutoSizedInputInt(const char* label, int* v, int step,
250 int step_fast,
251 ImGuiInputTextFlags flags) {
252 ImGui::SetNextItemWidth(GetStandardInputWidth());
253 return ImGui::InputInt(label, v, step, step_fast, flags);
254}
255
256bool LayoutHelpers::AutoSizedInputFloat(const char* label, float* v, float step,
257 float step_fast, const char* format,
258 ImGuiInputTextFlags flags) {
259 ImGui::SetNextItemWidth(GetStandardInputWidth());
260 return ImGui::InputFloat(label, v, step, step_fast, format, flags);
261}
262
263// Input preset functions for common patterns
264bool LayoutHelpers::InputHexRow(const char* label, uint8_t* data) {
265 float input_width = GetHexInputWidth();
266
267 ImGui::AlignTextToFramePadding();
268 ImGui::Text("%s", label);
269 ImGui::SameLine();
270
271 // Use theme-aware input width for hex byte (2 chars + controls)
272 ImGui::SetNextItemWidth(input_width);
273
274 char buf[8];
275 snprintf(buf, sizeof(buf), "%02X", *data);
276
277 bool changed = ImGui::InputText(
278 ("##" + std::string(label)).c_str(), buf, sizeof(buf),
279 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
280
281 if (changed) {
282 unsigned int temp;
283 if (sscanf(buf, "%X", &temp) == 1) {
284 *data = static_cast<uint8_t>(temp & 0xFF);
285 }
286 }
287
288 return changed;
289}
290
291bool LayoutHelpers::InputHexRow(const char* label, uint16_t* data) {
292 float input_width = GetHexInputWidth() * 1.2f; // Hex word slightly wider
293
294 ImGui::AlignTextToFramePadding();
295 ImGui::Text("%s", label);
296 ImGui::SameLine();
297
298 // Use theme-aware input width for hex word (4 chars + controls)
299 ImGui::SetNextItemWidth(input_width);
300
301 char buf[8];
302 snprintf(buf, sizeof(buf), "%04X", *data);
303
304 bool changed = ImGui::InputText(
305 ("##" + std::string(label)).c_str(), buf, sizeof(buf),
306 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
307
308 if (changed) {
309 unsigned int temp;
310 if (sscanf(buf, "%X", &temp) == 1) {
311 *data = static_cast<uint16_t>(temp & 0xFFFF);
312 }
313 }
314
315 return changed;
316}
317
318void LayoutHelpers::BeginPropertyGrid(const char* label) {
319 const auto& theme = GetTheme();
320
321 // Create a 2-column table for property editing
322 if (ImGui::BeginTable(label, 2,
323 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
324 // Setup columns: label column (30%) and value column (70%)
325 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed,
326 GetStandardInputWidth() * 1.5f);
327 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
328 }
329}
330
332 ImGui::EndTable();
333}
334
335bool LayoutHelpers::InputToolbarField(const char* label, char* buf,
336 size_t buf_size) {
337 // Compact input field for toolbars
338 float compact_width =
340 ImGui::SetNextItemWidth(compact_width);
341
342 return ImGui::InputText(label, buf, buf_size,
343 ImGuiInputTextFlags_AutoSelectAll);
344}
345
346// Toolbar helpers
347void LayoutHelpers::BeginToolbar(const char* label) {
348 const auto& theme = GetTheme();
349 ImGui::PushStyleColor(ImGuiCol_ChildBg,
350 ConvertColorToImVec4(theme.menu_bar_bg));
351 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
353
354 // Ensure the toolbar is tall enough to fit standard widgets without clipping,
355 // especially at higher DPI or with larger icon glyphs. Use touch-safe height
356 // so iOS toolbars meet the 44px Apple HIG minimum.
357 const float min_height =
358 (GetTouchSafeWidgetHeight() + 6.0f) + (GetButtonPadding() * 2.0f) + 2.0f;
359 const float height = std::max(GetToolbarHeight(), min_height);
360
361 ImGui::BeginChild(
362 label, ImVec2(0, height), true,
363 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
364}
365
367 ImGuiContext* ctx = ImGui::GetCurrentContext();
368 const bool has_child_window =
369 ctx != nullptr && ctx->CurrentWindow != nullptr &&
370 ((ctx->CurrentWindow->Flags & ImGuiWindowFlags_ChildWindow) != 0);
371
372 // Guard against scope recovery edge-cases where the current window stack has
373 // already been unwound before toolbar teardown.
374 if (has_child_window) {
375 ImGui::EndChild();
376 }
377
378 if (ctx != nullptr && ctx->StyleVarStack.Size > 0) {
379 ImGui::PopStyleVar(1);
380 }
381 if (ctx != nullptr && ctx->ColorStack.Size > 0) {
382 ImGui::PopStyleColor(1);
383 }
384}
385
387 ImGui::SameLine();
388 ImGui::Dummy(ImVec2(GetStandardSpacing(), 0));
389 ImGui::SameLine();
390 ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
391 ImGui::SameLine();
392 ImGui::Dummy(ImVec2(GetStandardSpacing(), 0));
393 ImGui::SameLine();
394}
395
396bool LayoutHelpers::ToolbarButton(const char* label, const ImVec2& size) {
397 const float min_touch = GetMinTouchTarget();
398 ImVec2 effective_size = size;
399 if (min_touch > 0.0f) {
400 effective_size.x = std::max(effective_size.x, min_touch);
401 effective_size.y = std::max(effective_size.y, min_touch);
402 }
403 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
405 bool result = ImGui::Button(label, effective_size);
406 ImGui::PopStyleVar(1);
407 return result;
408}
409
410// Common layout patterns
411void LayoutHelpers::PropertyRow(const char* label,
412 std::function<void()> widget_callback) {
413 ImGui::TableNextRow();
414 ImGui::TableSetColumnIndex(0);
415 ImGui::AlignTextToFramePadding();
416 ImGui::Text("%s", label);
417 ImGui::TableSetColumnIndex(1);
418 ImGui::SetNextItemWidth(-1);
419 widget_callback();
420}
421
422void LayoutHelpers::SectionHeader(const char* label) {
423 const auto& theme = GetTheme();
424 ImGui::PushStyleColor(ImGuiCol_Text, ConvertColorToImVec4(theme.accent));
425 ImGui::SeparatorText(label);
426 ImGui::PopStyleColor(1);
427}
428
429void LayoutHelpers::HelpMarker(const char* desc) {
430 ImGui::TextDisabled("(?)");
431 if (ImGui::BeginItemTooltip()) {
432 ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
433 ImGui::TextUnformatted(desc);
434 ImGui::PopTextWrapPos();
435 ImGui::EndTooltip();
436 }
437}
438
439} // namespace gui
440} // namespace yaze
static float GetHexInputWidth()
static WindowClampResult ClampWindowToRect(const ImVec2 &pos, const ImVec2 &size, const ImVec2 &rect_pos, const ImVec2 &rect_size, float min_visible=32.0f)
static float GetMinTouchTarget()
static float GetSliderWidth()
static void SectionHeader(const char *label)
static float GetPanelPadding()
static float GetTouchSafeWidgetHeight()
static bool BeginContentChild(const char *id, const ImVec2 &min_size, bool border=false, ImGuiWindowFlags flags=0)
static void BeginToolbar(const char *label)
static float GetCompactInputWidth()
static float GetCanvasToolbarHeight()
static void EndTableWithTheming()
static void HelpMarker(const char *desc)
static float GetTableRowHeight()
static const Theme & GetTheme()
static float GetStandardInputWidth()
static void BeginPropertyGrid(const char *label)
static SafeAreaInsets GetSafeAreaInsets()
static bool AutoSizedInputFloat(const char *label, float *v, float step=0.0f, float step_fast=0.0f, const char *format="%.3f", ImGuiInputTextFlags flags=0)
static bool AutoSizedInputInt(const char *label, int *v, int step=1, int step_fast=100, ImGuiInputTextFlags flags=0)
static bool InputToolbarField(const char *label, char *buf, size_t buf_size)
static void BeginCanvasPanel(const char *label, ImVec2 *canvas_size=nullptr)
static void PropertyRow(const char *label, std::function< void()> widget_callback)
static float GetBaseFontSize()
static float GetButtonPadding()
static float GetToolbarHeight()
static void BeginPaddedPanel(const char *label, float padding=-1.0f)
static bool ToolbarButton(const char *label, const ImVec2 &size=ImVec2(0, 0))
static bool InputHexRow(const char *label, uint8_t *data)
static float GetStandardWidgetHeight()
static float GetStandardSpacing()
static bool BeginTableWithTheming(const char *str_id, int columns, ImGuiTableFlags flags=0, const ImVec2 &outer_size=ImVec2(0, 0), float inner_width=0.0f)
static bool AutoSizedInputField(const char *label, char *buf, size_t buf_size, ImGuiInputTextFlags flags=0)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
ImVec2 GetContentRegionAvailClamped(const ImVec2 &min_size)
Returns content region available size clamped to a minimum.
Definition ui_config.h:168
SafeAreaInsets GetSafeAreaInsets()