Skip to content

Commit 89335ae

Browse files
committed
Use backend buttons in Preferences UI
Replace the renderer-backend dropdown with a row of per-backend buttons in the Preferences window, including disabled styling for unavailable backends, synthetic layout items/rects for testing, and a small "Reset to Auto" button. Validate requested backend, show stored/current/next-launch info, and reset invalid selections to Auto when the window closes. Remove the unused backend_combo_label helper. Also remove DockNodeFlags_NoDockingSplit from the image window class. Update the backend preferences regression tool to locate backend button rects and centers (rename helpers and parameters) and to emit clicks against the new button-based UI.
1 parent c7f43b2 commit 89335ae

3 files changed

Lines changed: 92 additions & 97 deletions

File tree

src/imiv/imiv_aux_windows.cpp

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,6 @@ namespace {
118118
ImGui::PopTextWrapPos();
119119
}
120120

121-
std::string backend_combo_label(const BackendInfo& info)
122-
{
123-
std::string label = std::string(backend_display_name(info.kind));
124-
if (!info.compiled)
125-
label += " (not built)";
126-
return label;
127-
}
128-
129121
bool input_text_string(const char* label, std::string& value)
130122
{
131123
char buffer[4096];
@@ -277,52 +269,85 @@ draw_preferences_window(PlaceholderUiState& ui, bool& show_window,
277269
ImGui::Checkbox("Generate mipmaps (requires restart)", &ui.auto_mipmap);
278270

279271
ImGui::Spacing();
280-
const BackendKind requested_backend = sanitize_backend_kind(
272+
BackendKind requested_backend = sanitize_backend_kind(
281273
ui.renderer_backend);
274+
const float spacing = ImGui::GetStyle().ItemSpacing.x;
275+
const float row_width = ImGui::GetContentRegionAvail().x;
276+
const float button_width = std::max(
277+
1.0f, (row_width - spacing * 2.0f) / 3.0f);
278+
bool have_backend_row_rect = false;
279+
ImVec2 backend_row_min(0.0f, 0.0f);
280+
ImVec2 backend_row_max(0.0f, 0.0f);
281+
int backend_button_index = 0;
282+
for (const BackendInfo& info : compiled_backend_info()) {
283+
const bool selected = requested_backend == info.kind;
284+
if (backend_button_index > 0)
285+
ImGui::SameLine(0.0f, spacing);
286+
ImGui::PushID(static_cast<int>(info.kind));
287+
if (!info.compiled)
288+
ImGui::BeginDisabled();
289+
push_preview_active_button_style(selected);
290+
if (ImGui::Button(backend_display_name(info.kind),
291+
ImVec2(button_width, 0.0f))
292+
&& info.compiled) {
293+
ui.renderer_backend = static_cast<int>(info.kind);
294+
}
295+
pop_preview_active_button_style(selected);
296+
if (!info.compiled)
297+
ImGui::EndDisabled();
298+
register_layout_dump_synthetic_item("button",
299+
backend_display_name(info.kind));
300+
const ImVec2 item_min = ImGui::GetItemRectMin();
301+
const ImVec2 item_max = ImGui::GetItemRectMax();
302+
if (!have_backend_row_rect) {
303+
backend_row_min = item_min;
304+
backend_row_max = item_max;
305+
have_backend_row_rect = true;
306+
} else {
307+
backend_row_min.x = std::min(backend_row_min.x, item_min.x);
308+
backend_row_min.y = std::min(backend_row_min.y, item_min.y);
309+
backend_row_max.x = std::max(backend_row_max.x, item_max.x);
310+
backend_row_max.y = std::max(backend_row_max.y, item_max.y);
311+
}
312+
ImGui::PopID();
313+
++backend_button_index;
314+
}
315+
if (have_backend_row_rect) {
316+
register_layout_dump_synthetic_rect("button", "Renderer backend",
317+
backend_row_min,
318+
backend_row_max);
319+
}
320+
321+
requested_backend = sanitize_backend_kind(ui.renderer_backend);
282322
const BackendKind next_launch_backend = resolve_backend_request(
283323
requested_backend);
284324
const bool requested_backend_compiled
285325
= requested_backend == BackendKind::Auto
286326
|| backend_kind_is_compiled(requested_backend);
287-
std::string backend_label
288-
= std::string(backend_display_name(requested_backend));
289-
if (requested_backend != BackendKind::Auto
290-
&& !requested_backend_compiled) {
291-
backend_label += " (not built)";
292-
}
293-
if (ImGui::BeginCombo("Renderer backend", backend_label.c_str())) {
294-
register_test_engine_item_label("Renderer backend", true);
295-
const bool auto_selected = requested_backend == BackendKind::Auto;
296-
if (ImGui::Selectable("Auto", auto_selected)) {
297-
ui.renderer_backend = static_cast<int>(BackendKind::Auto);
298-
}
299-
if (auto_selected)
300-
ImGui::SetItemDefaultFocus();
301-
for (const BackendInfo& info : compiled_backend_info()) {
302-
const bool selected = requested_backend == info.kind;
303-
const std::string label = backend_combo_label(info);
304-
if (!info.compiled)
305-
ImGui::BeginDisabled();
306-
if (ImGui::Selectable(label.c_str(), selected)
307-
&& info.compiled) {
308-
ui.renderer_backend = static_cast<int>(info.kind);
309-
}
310-
if (!info.compiled)
311-
ImGui::EndDisabled();
312-
if (selected)
313-
ImGui::SetItemDefaultFocus();
314-
}
315-
ImGui::EndCombo();
316-
} else {
317-
register_test_engine_item_label("Renderer backend", true);
318-
}
327+
const bool invalid_requested_backend = requested_backend != BackendKind::Auto
328+
&& !requested_backend_compiled;
329+
if (requested_backend == BackendKind::Auto)
330+
ImGui::TextUnformatted("Stored preference: Auto");
331+
else
332+
ImGui::Text("Stored preference: %s",
333+
backend_display_name(requested_backend));
334+
ImGui::SameLine();
335+
if (requested_backend == BackendKind::Auto)
336+
ImGui::BeginDisabled();
337+
if (ImGui::SmallButton("Reset to Auto"))
338+
ui.renderer_backend = static_cast<int>(BackendKind::Auto);
339+
register_layout_dump_synthetic_item("button", "Reset to Auto");
340+
if (requested_backend == BackendKind::Auto)
341+
ImGui::EndDisabled();
319342
ImGui::TextUnformatted("Current backend");
320343
ImGui::SameLine();
321344
ImGui::TextUnformatted(backend_display_name(active_backend));
322-
if (requested_backend != BackendKind::Auto
323-
&& !requested_backend_compiled) {
345+
ImGui::Text("Next launch backend: %s",
346+
backend_display_name(resolve_backend_request(
347+
sanitize_backend_kind(ui.renderer_backend))));
348+
if (invalid_requested_backend) {
324349
ImGui::TextUnformatted(
325-
"Requested backend is not built into this binary.");
350+
"Requested backend is not built in this binary and will be ignored when Preferences closes.");
326351
} else if (next_launch_backend != active_backend) {
327352
ImGui::TextUnformatted("Backend change requires restart.");
328353
}
@@ -422,6 +447,11 @@ draw_preferences_window(PlaceholderUiState& ui, bool& show_window,
422447
}
423448
ImGui::End();
424449
ImGui::PopStyleVar();
450+
const BackendKind close_backend = sanitize_backend_kind(ui.renderer_backend);
451+
if (!show_window && close_backend != BackendKind::Auto
452+
&& !backend_kind_is_compiled(close_backend)) {
453+
ui.renderer_backend = static_cast<int>(BackendKind::Auto);
454+
}
425455
}
426456

427457
void

src/imiv/imiv_frame.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,7 @@ setup_image_window_policy(ImGuiID dockspace_id, bool force_dock)
501501

502502
ImGuiWindowClass window_class;
503503
window_class.ClassId = ImGui::GetID("imiv.image.window");
504-
window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoDockingSplit
505-
| ImGuiDockNodeFlags_AutoHideTabBar;
504+
window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_AutoHideTabBar;
506505
ImGui::SetNextWindowClass(&window_class);
507506
}
508507

src/imiv/tools/imiv_backend_preferences_regression.py

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ def _write_scenario(
9797
path: Path,
9898
runtime_dir_rel: str,
9999
*,
100-
combo_center: tuple[float, float],
101-
choice_points: dict[str, tuple[float, float]],
100+
button_points: dict[str, tuple[float, float]],
102101
) -> None:
103102
root = ET.Element("imiv-scenario")
104103
root.set("out_dir", runtime_dir_rel)
@@ -110,47 +109,26 @@ def _write_scenario(
110109
state=True,
111110
post_action_delay_frames=2,
112111
)
113-
_scenario_step(
114-
root,
115-
"open_alternate_backend_combo",
116-
mouse_pos=f"{combo_center[0]:.3f},{combo_center[1]:.3f}",
117-
mouse_click_button=0,
118-
post_action_delay_frames=1,
119-
)
120112
_scenario_step(
121113
root,
122114
"select_alternate_backend",
123-
mouse_pos=f"{choice_points['alternate'][0]:.3f},{choice_points['alternate'][1]:.3f}",
115+
mouse_pos=f"{button_points['alternate'][0]:.3f},{button_points['alternate'][1]:.3f}",
124116
mouse_click_button=0,
125117
state=True,
126118
post_action_delay_frames=2,
127119
)
128-
_scenario_step(
129-
root,
130-
"open_active_backend_combo",
131-
mouse_pos=f"{combo_center[0]:.3f},{combo_center[1]:.3f}",
132-
mouse_click_button=0,
133-
post_action_delay_frames=1,
134-
)
135120
_scenario_step(
136121
root,
137122
"select_active_backend",
138-
mouse_pos=f"{choice_points['active'][0]:.3f},{choice_points['active'][1]:.3f}",
123+
mouse_pos=f"{button_points['active'][0]:.3f},{button_points['active'][1]:.3f}",
139124
mouse_click_button=0,
140125
state=True,
141126
post_action_delay_frames=2,
142127
)
143-
_scenario_step(
144-
root,
145-
"open_auto_backend_combo",
146-
mouse_pos=f"{combo_center[0]:.3f},{combo_center[1]:.3f}",
147-
mouse_click_button=0,
148-
post_action_delay_frames=1,
149-
)
150128
_scenario_step(
151129
root,
152130
"select_auto_backend",
153-
mouse_pos=f"{choice_points['auto'][0]:.3f},{choice_points['auto'][1]:.3f}",
131+
mouse_pos=f"{button_points['auto'][0]:.3f},{button_points['auto'][1]:.3f}",
154132
mouse_click_button=0,
155133
state=True,
156134
post_action_delay_frames=2,
@@ -184,7 +162,7 @@ def _load_state(path: Path) -> dict:
184162
return json.loads(path.read_text(encoding="utf-8"))
185163

186164

187-
def _find_combo_rect(layout: dict, label: str) -> dict:
165+
def _find_item_rect(layout: dict, label: str) -> dict:
188166
for window in layout.get("windows", []):
189167
if window.get("name") != "iv Preferences":
190168
continue
@@ -205,26 +183,17 @@ def _rect_center(rect: dict) -> tuple[float, float]:
205183
)
206184

207185

208-
def _backend_popup_choice_points(
209-
combo_rect: dict, *, alternate_backend: str, active_backend: str
186+
def _backend_button_points(
187+
layout: dict, *, alternate_backend: str, active_backend: str
210188
) -> dict[str, tuple[float, float]]:
211-
order = {"auto": 0, "vulkan": 1, "metal": 2, "opengl": 3}
212-
rect_min = combo_rect.get("min", [0.0, 0.0])
213-
rect_max = combo_rect.get("max", [0.0, 0.0])
214-
min_x = float(rect_min[0])
215-
min_y = float(rect_min[1])
216-
max_x = float(rect_max[0])
217-
max_y = float(rect_max[1])
218-
row_h = max(1.0, max_y - min_y)
219-
choice_x = min_x + max(24.0, (max_x - min_x) * 0.35)
220-
221-
def _point(name: str) -> tuple[float, float]:
222-
return (choice_x, max_y + row_h * (order[name] + 0.5))
223-
224189
return {
225-
"alternate": _point(alternate_backend),
226-
"active": _point(active_backend),
227-
"auto": _point("auto"),
190+
"alternate": _rect_center(
191+
_find_item_rect(layout, _backend_display_name(alternate_backend))
192+
),
193+
"active": _rect_center(
194+
_find_item_rect(layout, _backend_display_name(active_backend))
195+
),
196+
"auto": _rect_center(_find_item_rect(layout, "Reset to Auto")),
228197
}
229198

230199

@@ -380,10 +349,8 @@ def main() -> int:
380349
return _fail(f"probe runner exited with code {probe_proc.returncode}")
381350

382351
probe_layout = json.loads(probe_layout_path.read_text(encoding="utf-8"))
383-
combo_rect = _find_combo_rect(probe_layout, "Renderer backend")
384-
combo_center = _rect_center(combo_rect)
385-
choice_points = _backend_popup_choice_points(
386-
combo_rect,
352+
button_points = _backend_button_points(
353+
probe_layout,
387354
alternate_backend=alternate_backend,
388355
active_backend=active_backend,
389356
)
@@ -392,8 +359,7 @@ def main() -> int:
392359
_write_scenario(
393360
scenario_path,
394361
runtime_dir_rel,
395-
combo_center=combo_center,
396-
choice_points=choice_points,
362+
button_points=button_points,
397363
)
398364

399365
cmd = [

0 commit comments

Comments
 (0)