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