Skip to content

Commit 35045d6

Browse files
committed
Add 'Always on Top' and 'Reset Windows' features
'Always on Top' window flag and a 'Reset Windows' action. Persisted window_always_on_top is applied to the main GLFW window and any detached GLFW-backed ImGui viewports via apply_glfw_topmost_state_to_platform_windows. Native file dialogs temporarily suspend the topmost hint using a FileDialog native-dialog-scope hook and RAII guard to avoid dialogs being obscured. Add Window menu entries for Always on Top and Reset Windows, and wire reset to clear ImGui .ini settings and rebuild the dockspace (reset_window_layouts). Overhaul Preferences UI (sectioned form layout, helpers, OCIO browse integration, segmented backend selector), convert About modal to a centered window, improve focus/request handling for auxiliary windows, and various UI/layout tweaks and plumbing to set/clear the native dialog hook in the app run loop. Some UI refactoring Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com>
1 parent db4060f commit 35045d6

16 files changed

Lines changed: 780 additions & 397 deletions

src/doc/imiv_dev.rst

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ The main windows are:
461461
* `iv Info`;
462462
* `iv Preferences`;
463463
* `Preview`;
464-
* the `About imiv` modal;
464+
* the `About imiv` window;
465465
* optional Dear ImGui diagnostics from the runtime-enabled `Developer` menu.
466466

467467
Auxiliary windows in `src/imiv/imiv_aux_windows.cpp` use
@@ -503,6 +503,16 @@ Execution layer
503503
keeps file I/O, texture uploads, fullscreen changes, and navigation changes
504504
out of the immediate menu-building code.
505505

506+
The same layer now also handles two window-management actions:
507+
508+
* `Always on Top`
509+
persists a simple boolean flag in `imiv.inf` and applies it to the main
510+
GLFW window and any detached GLFW-backed ImGui viewport windows with
511+
`GLFW_FLOATING`;
512+
* `Reset Windows`
513+
clears live Dear ImGui ini settings, rebuilds the main dockspace, and
514+
reapplies the standard auxiliary-window defaults for the current frame.
515+
506516
Behavior layer
507517
--------------
508518

@@ -527,6 +537,27 @@ When adding a feature, the normal path is:
527537
3. put the durable behavior in `imiv_actions.cpp` or shared helpers.
528538

529539

540+
Native dialog wrapper
541+
=====================
542+
543+
`src/imiv/imiv_file_dialog.cpp` owns all native open/save/folder dialog calls.
544+
545+
It also owns the temporary topmost suppression used by the `Always on Top`
546+
feature:
547+
548+
* the app installs a lightweight begin/end callback once it has a live GLFW
549+
window;
550+
* the file-dialog wrapper enters that scope before opening an NFD dialog and
551+
leaves it afterward;
552+
* while the scope is active, the main GLFW window and any detached GLFW-backed
553+
ImGui viewport windows have `GLFW_FLOATING` temporarily cleared;
554+
* after the dialog returns, the wrapper restores the persistent
555+
`window_always_on_top` state.
556+
557+
Keeping that logic in the dialog wrapper avoids threading platform window
558+
details through every individual open/save action.
559+
560+
530561
Image window and helper modules
531562
===============================
532563

src/doc/imiv_user.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,22 @@ multi-open and `Open Folder...`. That means dropped files immediately appear
311311
in `Image List` and participate in the same per-view open/close/remove
312312
workflow.
313313

314+
The `Window` menu provides:
315+
316+
* `Always on Top`
317+
keeps the main :program:`imiv` window and detached auxiliary windows above
318+
ordinary desktop windows;
319+
* `Reset Windows`
320+
clears the saved Dear ImGui layout in the current session and restores the
321+
default dock/tool-window placement so hidden or off-screen auxiliary windows
322+
can be recovered.
323+
324+
When native open/save/folder dialogs are used, :program:`imiv` temporarily
325+
disables the topmost window hint while the dialog is open and restores it
326+
after the dialog closes. This avoids the main window covering the native file
327+
dialog on platforms where an always-on-top GLFW window would otherwise stay in
328+
front.
329+
314330

315331
Color management
316332
================
@@ -354,6 +370,7 @@ Saved state currently includes:
354370
* Dear ImGui docking/window layout;
355371
* viewer and preview defaults for the primary view;
356372
* backend preference (`renderer_backend`);
373+
* whether the main window is `Always on Top`;
357374
* OCIO settings;
358375
* recent images and sort mode.
359376

@@ -362,6 +379,7 @@ Example::
362379
[ImivApp][State]
363380
renderer_backend=vulkan
364381
fit_image_to_window=1
382+
window_always_on_top=0
365383
use_ocio=1
366384
ocio_display=default
367385
ocio_view=default

src/imiv/imiv_app.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ namespace Imiv {
5858

5959
namespace {
6060

61+
struct NativeDialogWindowScope {
62+
GLFWwindow* window = nullptr;
63+
PlaceholderUiState* ui_state = nullptr;
64+
int suspend_depth = 0;
65+
bool restore_floating_on_exit = false;
66+
};
67+
6168
std::filesystem::path executable_directory_path()
6269
{
6370
const std::string program_path = Sysutil::this_program_path();
@@ -254,6 +261,59 @@ namespace {
254261
backend_cli_name(active_backend));
255262
}
256263

264+
void apply_glfw_topmost_state_to_platform_windows(GLFWwindow* main_window,
265+
bool always_on_top)
266+
{
267+
if (main_window != nullptr)
268+
platform_glfw_set_window_floating(main_window, always_on_top);
269+
270+
ImGuiContext* ctx = ImGui::GetCurrentContext();
271+
if (ctx == nullptr)
272+
return;
273+
if ((ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
274+
== 0) {
275+
return;
276+
}
277+
278+
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
279+
for (int i = 0; i < platform_io.Viewports.Size; ++i) {
280+
ImGuiViewport* viewport = platform_io.Viewports[i];
281+
if (viewport == nullptr || viewport->PlatformHandle == nullptr)
282+
continue;
283+
GLFWwindow* viewport_window = static_cast<GLFWwindow*>(
284+
viewport->PlatformHandle);
285+
platform_glfw_set_window_floating(viewport_window, always_on_top);
286+
}
287+
}
288+
289+
void native_dialog_scope_callback(bool begin_scope, void* user_data)
290+
{
291+
auto* scope = static_cast<NativeDialogWindowScope*>(user_data);
292+
if (scope == nullptr || scope->window == nullptr
293+
|| scope->ui_state == nullptr)
294+
return;
295+
296+
if (begin_scope) {
297+
++scope->suspend_depth;
298+
if (scope->suspend_depth == 1
299+
&& scope->ui_state->window_always_on_top
300+
&& platform_glfw_is_window_floating(scope->window)) {
301+
apply_glfw_topmost_state_to_platform_windows(scope->window,
302+
false);
303+
scope->restore_floating_on_exit = true;
304+
}
305+
return;
306+
}
307+
308+
if (scope->suspend_depth > 0)
309+
--scope->suspend_depth;
310+
if (scope->suspend_depth == 0 && scope->restore_floating_on_exit) {
311+
apply_glfw_topmost_state_to_platform_windows(
312+
scope->window, scope->ui_state->window_always_on_top);
313+
scope->restore_floating_on_exit = false;
314+
}
315+
}
316+
257317
std::vector<std::string> expand_startup_input_paths(const AppConfig& config,
258318
bool verbose_logging,
259319
ImageSortMode sort_mode,
@@ -670,6 +730,12 @@ run(const AppConfig& config)
670730
}
671731
set_area_sample_enabled(viewer, ui_state, ui_state.show_area_probe_window);
672732
apply_imgui_app_style(sanitize_app_style_preset(ui_state.style_preset));
733+
apply_glfw_topmost_state_to_platform_windows(window,
734+
ui_state.window_always_on_top);
735+
736+
NativeDialogWindowScope native_dialog_scope = { window, &ui_state };
737+
FileDialog::set_native_dialog_scope_hook(native_dialog_scope_callback,
738+
&native_dialog_scope);
673739

674740
if (!run_config.input_paths.empty()) {
675741
append_loaded_image_paths(library, run_config.input_paths);
@@ -725,6 +791,10 @@ run(const AppConfig& config)
725791
center_glfw_window(window);
726792
--startup_center_frames;
727793
}
794+
if (native_dialog_scope.suspend_depth == 0) {
795+
apply_glfw_topmost_state_to_platform_windows(
796+
window, ui_state.window_always_on_top);
797+
}
728798

729799
int fb_width = 0;
730800
int fb_height = 0;
@@ -772,6 +842,10 @@ run(const AppConfig& config)
772842
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
773843
renderer_prepare_platform_windows(renderer_state);
774844
ImGui::UpdatePlatformWindows();
845+
if (native_dialog_scope.suspend_depth == 0) {
846+
apply_glfw_topmost_state_to_platform_windows(
847+
window, ui_state.window_always_on_top);
848+
}
775849
ImGui::RenderPlatformWindowsDefault();
776850
renderer_finish_platform_windows(renderer_state);
777851
}
@@ -825,6 +899,7 @@ run(const AppConfig& config)
825899
#if defined(IMGUI_ENABLE_TEST_ENGINE)
826900
test_engine_stop(test_engine_runtime);
827901
#endif
902+
FileDialog::set_native_dialog_scope_hook(nullptr, nullptr);
828903
uninstall_drag_drop(window);
829904
if (active_backend == BackendKind::OpenGL) {
830905
renderer_cleanup_window(renderer_state);

0 commit comments

Comments
 (0)