Skip to content

Commit 7e2a95c

Browse files
committed
Add UI style presets and refactor mouse modes
Introduce a new ImGui app style system (imiv_style.h/.cpp) with selectable presets (IV/ImGui light/dark/classic) and helper functions to apply/sanitize presets. Persist UI state in a single persistent settings file (imiv.inf) and embed ImGui .ini contents when saving/loading; keep backwards compatibility with legacy prefs/ini files. Replace dark_palette with an integer style_preset, add a style combo in preferences, and apply styles at startup and when changed. Refactor area-sample/selection and mouse-mode handling: add set_area_sample_enabled and set_mouse_mode_action, update status bar mouse-mode UI and menu entries, and gate navigation/drag behaviors when area-sampling is active. Update various input/interaction logic (pan/zoom/drag handling) and fix zoom-drag scaling sign. Also update function signatures, constants, and a couple of regression test cases to reflect drag-based interactions.
1 parent dbce66c commit 7e2a95c

18 files changed

Lines changed: 789 additions & 182 deletions

src/imiv/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ set (_imiv_core_sources
8181
imiv_ocio.cpp
8282
imiv_overlays.cpp
8383
imiv_shader_compile.cpp
84+
imiv_style.cpp
8485
imiv_ui.cpp
8586
imiv_viewer.cpp
8687
imiv_vulkan_setup.cpp

src/imiv/imiv_actions.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,35 @@ deselect_selection_action(ViewerState& viewer,
518518
viewer.last_error.clear();
519519
}
520520

521+
void
522+
set_area_sample_enabled(ViewerState& viewer, PlaceholderUiState& ui_state,
523+
bool enabled)
524+
{
525+
ui_state.show_area_probe_window = enabled;
526+
if (enabled) {
527+
ui_state.mouse_mode = 3;
528+
sync_area_probe_to_selection(viewer, ui_state);
529+
return;
530+
}
531+
532+
if (ui_state.mouse_mode == 3)
533+
ui_state.mouse_mode = 0;
534+
clear_image_selection(viewer);
535+
reset_area_probe_overlay(viewer);
536+
}
537+
538+
void
539+
set_mouse_mode_action(ViewerState& viewer, PlaceholderUiState& ui_state,
540+
int mouse_mode)
541+
{
542+
ui_state.mouse_mode = std::clamp(mouse_mode, 0, 4);
543+
if (ui_state.mouse_mode == 3)
544+
ui_state.mouse_mode = 0;
545+
if (ui_state.show_area_probe_window) {
546+
set_area_sample_enabled(viewer, ui_state, false);
547+
}
548+
}
549+
521550
void
522551
set_sort_mode_action(ViewerState& viewer, ImageSortMode mode)
523552
{

src/imiv/imiv_actions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ void
3434
deselect_selection_action(ViewerState& viewer,
3535
const PlaceholderUiState& ui_state);
3636
void
37+
set_area_sample_enabled(ViewerState& viewer, PlaceholderUiState& ui_state,
38+
bool enabled);
39+
void
40+
set_mouse_mode_action(ViewerState& viewer, PlaceholderUiState& ui_state,
41+
int mouse_mode);
42+
void
3743
set_sort_mode_action(ViewerState& viewer, ImageSortMode mode);
3844
void
3945
toggle_sort_reverse_action(ViewerState& viewer);

src/imiv/imiv_app.cpp

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "imiv_menu.h"
1111
#include "imiv_navigation.h"
1212
#include "imiv_ocio.h"
13+
#include "imiv_style.h"
1314
#include "imiv_test_engine.h"
1415
#include "imiv_types.h"
1516
#include "imiv_ui.h"
@@ -56,15 +57,6 @@ namespace Imiv {
5657

5758
namespace {
5859

59-
void apply_imgui_style_defaults()
60-
{
61-
ImGuiStyle& style = ImGui::GetStyle();
62-
style.WindowBorderSize = 0.0f;
63-
style.ChildBorderSize = 0.0f;
64-
style.PopupBorderSize = 0.0f;
65-
style.FrameBorderSize = 0.0f;
66-
}
67-
6860
std::filesystem::path executable_directory_path()
6961
{
7062
const std::string program_path = Sysutil::this_program_path();
@@ -435,10 +427,25 @@ run(const AppConfig& config)
435427
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
436428
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
437429
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
438-
io.IniFilename = "imiv.ini";
430+
io.IniFilename = nullptr;
439431
const AppFonts fonts = setup_app_fonts(verbose_logging);
440-
ImGui::StyleColorsDark();
441-
apply_imgui_style_defaults();
432+
apply_imgui_app_style(AppStylePreset::ImGuiDark);
433+
const std::filesystem::path settings_load_path
434+
= persistent_state_file_path_for_load();
435+
std::error_code settings_load_ec;
436+
if (settings_load_path.filename() == "imiv.inf"
437+
&& std::filesystem::exists(settings_load_path, settings_load_ec)
438+
&& !settings_load_ec) {
439+
ImGui::LoadIniSettingsFromDisk(settings_load_path.string().c_str());
440+
} else {
441+
const std::filesystem::path legacy_imgui_path
442+
= legacy_imgui_ini_file_path();
443+
settings_load_ec.clear();
444+
if (std::filesystem::exists(legacy_imgui_path, settings_load_ec)
445+
&& !settings_load_ec) {
446+
ImGui::LoadIniSettingsFromDisk(legacy_imgui_path.string().c_str());
447+
}
448+
}
442449
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
443450
ImGuiStyle& style = ImGui::GetStyle();
444451
style.WindowRounding = 0.0f;
@@ -597,11 +604,8 @@ run(const AppConfig& config)
597604
ui_state.show_area_probe_window = true;
598605
ui_state.mouse_mode = 3;
599606
}
600-
if (ui_state.dark_palette)
601-
ImGui::StyleColorsDark();
602-
else
603-
ImGui::StyleColorsLight();
604-
apply_imgui_style_defaults();
607+
set_area_sample_enabled(viewer, ui_state, ui_state.show_area_probe_window);
608+
apply_imgui_app_style(sanitize_app_style_preset(ui_state.style_preset));
605609

606610
if (!run_config.input_paths.empty()) {
607611
append_loaded_image_paths(viewer, run_config.input_paths);
@@ -634,8 +638,17 @@ run(const AppConfig& config)
634638
glfwPollEvents();
635639
force_center_glfw_window(window);
636640

641+
auto save_combined_settings = [&](std::string& save_error_message) {
642+
size_t imgui_ini_size = 0;
643+
const char* imgui_ini_text = ImGui::SaveIniSettingsToMemory(
644+
&imgui_ini_size);
645+
io.WantSaveIniSettings = false;
646+
return save_persistent_state(ui_state, viewer, imgui_ini_text,
647+
imgui_ini_size, save_error_message);
648+
};
649+
637650
bool request_exit = false;
638-
bool applied_dark_palette = ui_state.dark_palette;
651+
int applied_style_preset = ui_state.style_preset;
639652
int startup_center_frames = 90;
640653
while (!glfwWindowShouldClose(window)) {
641654
glfwPollEvents();
@@ -676,13 +689,12 @@ run(const AppConfig& config)
676689
# endif
677690
,
678691
window, vk_state);
679-
if (ui_state.dark_palette != applied_dark_palette) {
680-
if (ui_state.dark_palette)
681-
ImGui::StyleColorsDark();
682-
else
683-
ImGui::StyleColorsLight();
684-
apply_imgui_style_defaults();
685-
applied_dark_palette = ui_state.dark_palette;
692+
if (ui_state.style_preset != applied_style_preset) {
693+
ui_state.style_preset = static_cast<int>(
694+
sanitize_app_style_preset(ui_state.style_preset));
695+
apply_imgui_app_style(
696+
sanitize_app_style_preset(ui_state.style_preset));
697+
applied_style_preset = ui_state.style_preset;
686698
}
687699
# if defined(IMGUI_ENABLE_TEST_ENGINE)
688700
test_engine_maybe_show_windows(test_engine_runtime, test_engine_cfg);
@@ -713,12 +725,19 @@ run(const AppConfig& config)
713725
if (test_engine_should_close(test_engine_runtime, test_engine_cfg))
714726
glfwSetWindowShouldClose(window, GLFW_TRUE);
715727
# endif
728+
if (io.WantSaveIniSettings) {
729+
std::string save_error_message;
730+
if (!save_combined_settings(save_error_message)) {
731+
print(stderr, "imiv: failed to save preferences: {}\n",
732+
save_error_message);
733+
}
734+
}
716735
if (request_exit)
717736
glfwSetWindowShouldClose(window, GLFW_TRUE);
718737
}
719738

720739
std::string prefs_save_error;
721-
if (!save_persistent_state(ui_state, viewer, prefs_save_error))
740+
if (!save_combined_settings(prefs_save_error))
722741
print(stderr, "imiv: failed to save preferences: {}\n",
723742
prefs_save_error);
724743

src/imiv/imiv_aux_windows.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,24 @@ draw_preferences_window(PlaceholderUiState& ui, bool& show_window)
246246

247247
ImGui::Spacing();
248248
ImGui::Checkbox("Linear interpolation", &ui.linear_interpolation);
249-
ImGui::Checkbox("Dark palette", &ui.dark_palette);
249+
const AppStylePreset current_style_preset = sanitize_app_style_preset(
250+
ui.style_preset);
251+
if (ImGui::BeginCombo("UI style",
252+
app_style_preset_name(current_style_preset))) {
253+
for (int preset_value = static_cast<int>(AppStylePreset::IvLight);
254+
preset_value <= static_cast<int>(AppStylePreset::ImGuiClassic);
255+
++preset_value) {
256+
const AppStylePreset preset = static_cast<AppStylePreset>(
257+
preset_value);
258+
const bool selected = preset == current_style_preset;
259+
if (ImGui::Selectable(app_style_preset_name(preset), selected)) {
260+
ui.style_preset = preset_value;
261+
}
262+
if (selected)
263+
ImGui::SetItemDefaultFocus();
264+
}
265+
ImGui::EndCombo();
266+
}
250267
ImGui::Checkbox("Generate mipmaps (requires restart)", &ui.auto_mipmap);
251268

252269
ImGui::Spacing();

src/imiv/imiv_frame.cpp

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -487,31 +487,10 @@ draw_viewer_ui(ViewerState& viewer, PlaceholderUiState& ui_state,
487487
}
488488
#endif
489489

490-
if (env_flag_is_truthy("IMIV_IMGUI_TEST_ENGINE_SHOW_INFO"))
491-
ui_state.show_info_window = true;
492-
if (env_flag_is_truthy("IMIV_IMGUI_TEST_ENGINE_SHOW_PREFS"))
493-
ui_state.show_preferences_window = true;
494-
if (env_flag_is_truthy("IMIV_IMGUI_TEST_ENGINE_SHOW_PREVIEW"))
495-
ui_state.show_preview_window = true;
496-
if (env_flag_is_truthy("IMIV_IMGUI_TEST_ENGINE_SHOW_PIXEL"))
497-
ui_state.show_pixelview_window = true;
498-
if (env_flag_is_truthy("IMIV_IMGUI_TEST_ENGINE_SHOW_AREA")) {
499-
ui_state.show_area_probe_window = true;
500-
ui_state.mouse_mode = 3;
501-
sync_area_probe_to_selection(viewer, ui_state);
502-
}
503-
if (env_flag_is_truthy("IMIV_IMGUI_TEST_ENGINE_SHOW_AUX_WINDOWS")) {
504-
ui_state.show_info_window = true;
505-
ui_state.show_preferences_window = true;
506-
ui_state.show_preview_window = true;
507-
ui_state.show_pixelview_window = true;
508-
ui_state.show_area_probe_window = true;
509-
ui_state.mouse_mode = 3;
510-
sync_area_probe_to_selection(viewer, ui_state);
511-
}
512490
if (env_flag_is_truthy("IMIV_IMGUI_TEST_ENGINE_SHOW_DRAG_OVERLAY"))
513491
viewer.drag_overlay_active = true;
514492
apply_test_engine_ocio_overrides(ui_state);
493+
set_area_sample_enabled(viewer, ui_state, ui_state.show_area_probe_window);
515494

516495
collect_viewer_shortcuts(viewer, ui_state, developer_ui, actions,
517496
request_exit);

src/imiv/imiv_image_view.cpp

Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
184184
ImTextureRef closeup_texture_ref;
185185
bool has_main_texture = false;
186186
bool has_closeup_texture = false;
187-
bool image_canvas_pressed = false;
188187
bool image_canvas_hovered = false;
189188
bool image_canvas_active = false;
190189
PendingZoomRequest pending_zoom = shortcut_zoom_request;
@@ -260,11 +259,10 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
260259
coord_map.window_pos = ImGui::GetWindowPos();
261260
if (has_main_texture && coord_map.valid) {
262261
ImGui::SetCursorScreenPos(coord_map.image_rect_min);
263-
image_canvas_pressed = ImGui::InvisibleButton(
264-
"##image_canvas", image_size,
265-
ImGuiButtonFlags_MouseButtonLeft
266-
| ImGuiButtonFlags_MouseButtonRight
267-
| ImGuiButtonFlags_MouseButtonMiddle);
262+
ImGui::InvisibleButton("##image_canvas", image_size,
263+
ImGuiButtonFlags_MouseButtonLeft
264+
| ImGuiButtonFlags_MouseButtonRight
265+
| ImGuiButtonFlags_MouseButtonMiddle);
268266
image_canvas_hovered = ImGui::IsItemHovered(
269267
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
270268
image_canvas_active = ImGui::IsItemActive();
@@ -318,11 +316,10 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
318316
const ImGuiIO& io = ImGui::GetIO();
319317
const ImVec2 mouse = io.MousePos;
320318
const bool area_probe_mode = ui_state.show_area_probe_window;
321-
const bool selection_capable_mode = (ui_state.mouse_mode == 3)
322-
|| area_probe_mode;
323-
const bool mouse_in_image = point_in_rect(mouse,
324-
coord_map.image_rect_min,
325-
coord_map.image_rect_max);
319+
const bool selection_capable_mode = area_probe_mode;
320+
const bool mouse_in_image = point_in_rect(mouse,
321+
coord_map.image_rect_min,
322+
coord_map.image_rect_max);
326323
const bool mouse_in_viewport
327324
= point_in_rect(mouse, coord_map.viewport_rect_min,
328325
coord_map.viewport_rect_max);
@@ -332,24 +329,16 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
332329
&& mouse_in_viewport;
333330
const bool image_canvas_accepts_mouse = image_canvas_hovered
334331
|| image_canvas_active;
335-
const bool image_canvas_clicked_left
336-
= image_canvas_pressed && io.MouseReleased[ImGuiMouseButton_Left];
337-
const bool image_canvas_clicked_right
338-
= image_canvas_pressed && io.MouseReleased[ImGuiMouseButton_Right];
339332
const bool empty_viewport_clicked_left
340333
= viewport_accepts_mouse && !mouse_in_image
341334
&& ImGui::IsMouseClicked(ImGuiMouseButton_Left);
342-
const bool empty_viewport_clicked_right
343-
= viewport_accepts_mouse && !mouse_in_image
344-
&& ImGui::IsMouseClicked(ImGuiMouseButton_Right);
345335
const ImVec2 clamped_mouse
346336
= clamp_pos_to_rect(mouse, coord_map.image_rect_min,
347337
coord_map.image_rect_max);
348338
ImVec2 selection_source_uv(0.5f, 0.5f);
349339
const bool have_selection_source_uv
350340
= screen_to_source_uv(coord_map, clamped_mouse,
351341
selection_source_uv);
352-
bool selection_consumed_left_release = false;
353342

354343
ImVec2 source_uv(0.0f, 0.0f);
355344
int px = 0;
@@ -423,15 +412,13 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
423412
viewer.selection_xend, viewer.selection_yend);
424413
viewer.last_error.clear();
425414
}
426-
selection_consumed_left_release = true;
427415
} else if ((selection_capable_mode || area_probe_mode)
428416
&& (viewport_accepts_mouse
429417
|| image_canvas_accepts_mouse)) {
430418
clear_image_selection(viewer);
431419
sync_area_probe_to_selection(viewer, ui_state);
432420
viewer.status_message = "Selection cleared";
433421
viewer.last_error.clear();
434-
selection_consumed_left_release = true;
435422
}
436423
viewer.selection_press_active = false;
437424
viewer.selection_drag_active = false;
@@ -445,7 +432,6 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
445432
sync_area_probe_to_selection(viewer, ui_state);
446433
viewer.status_message = "Selection cleared";
447434
viewer.last_error.clear();
448-
selection_consumed_left_release = true;
449435
}
450436

451437
bool want_pan = false;
@@ -455,38 +441,23 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
455441
if (ui_state.mouse_mode == 1) {
456442
want_pan = (!area_probe_mode
457443
&& ImGui::IsMouseDown(ImGuiMouseButton_Left))
458-
|| ImGui::IsMouseDown(ImGuiMouseButton_Right)
459444
|| ImGui::IsMouseDown(ImGuiMouseButton_Middle);
460445
} else if (ui_state.mouse_mode == 0) {
461-
const bool want_middle_pan
462-
= ImGui::IsMouseDown(ImGuiMouseButton_Middle)
446+
const bool want_left_pan
447+
= !area_probe_mode
448+
&& ImGui::IsMouseDown(ImGuiMouseButton_Left)
463449
&& (viewer.pan_drag_active || image_canvas_accepts_mouse
464450
|| viewport_accepts_mouse);
465-
const bool want_alt_left_pan
466-
= !area_probe_mode && io.KeyAlt
467-
&& ImGui::IsMouseDown(ImGuiMouseButton_Left)
451+
const bool want_middle_pan
452+
= ImGui::IsMouseDown(ImGuiMouseButton_Middle)
468453
&& (viewer.pan_drag_active || image_canvas_accepts_mouse
469454
|| viewport_accepts_mouse);
470-
want_pan = want_middle_pan || want_alt_left_pan;
471-
want_zoom_drag = io.KeyAlt
455+
want_pan = want_left_pan || want_middle_pan;
456+
want_zoom_drag = !area_probe_mode
472457
&& ImGui::IsMouseDown(ImGuiMouseButton_Right)
473458
&& (viewer.zoom_drag_active
474459
|| image_canvas_accepts_mouse
475460
|| viewport_accepts_mouse);
476-
if (!area_probe_mode && !io.KeyAlt
477-
&& (image_canvas_clicked_left || image_canvas_clicked_right
478-
|| empty_viewport_clicked_left
479-
|| empty_viewport_clicked_right)) {
480-
if (!selection_consumed_left_release
481-
&& (image_canvas_clicked_left
482-
|| empty_viewport_clicked_left)) {
483-
request_zoom_scale(pending_zoom, 2.0f, true);
484-
}
485-
if (image_canvas_clicked_right
486-
|| empty_viewport_clicked_right) {
487-
request_zoom_scale(pending_zoom, 0.5f, true);
488-
}
489-
}
490461
}
491462
}
492463

@@ -519,7 +490,7 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
519490
} else {
520491
const float dx = mouse.x - viewer.drag_prev_mouse.x;
521492
const float dy = mouse.y - viewer.drag_prev_mouse.y;
522-
const float scale = 1.0f + 0.005f * (dx + dy);
493+
const float scale = 1.0f + 0.005f * (dy - dx);
523494
if (scale > 0.0f)
524495
request_zoom_scale(pending_zoom, scale, true);
525496
viewer.drag_prev_mouse = mouse;

0 commit comments

Comments
 (0)