From b7ebeb1610d27868b78206bf28cf5b5f7ee68013 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 21 May 2016 22:53:08 +0200 Subject: [PATCH] Added SetNextWindowSizeConstraint() + demo code (#668) --- imgui.cpp | 48 +++++++++++++++++++++++++++++++++++++++++------- imgui.h | 15 ++++++++++++++- imgui_demo.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ imgui_internal.h | 6 ++++++ 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8ac23977..6f79ef82 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -457,7 +457,6 @@ The list below consist mostly of notes of things to do before they are requested/discussed by users (at that point it usually happens on the github) - doc: add a proper documentation+regression testing system (#435) - - window: maximum window size settings (per-axis). for large popups in particular user may not want the popup to fill all space. - window: add a way for very transient windows (non-saved, temporary overlay over hundreds of objects) to "clean" up from the global window list. perhaps a lightweight explicit cleanup pass. - window: calling SetNextWindowSize() every frame with <= 0 doesn't do anything, may be useful to allow (particularly when used for a single axis). - window: auto-fit feedback loop when user relies on any dynamic layout (window width multiplier, column) appears weird to end-user. clarify. @@ -3416,7 +3415,7 @@ static inline void ClearSetNextWindowData() { ImGuiContext& g = *GImGui; g.SetNextWindowPosCond = g.SetNextWindowSizeCond = g.SetNextWindowContentSizeCond = g.SetNextWindowCollapsedCond = 0; - g.SetNextWindowFocus = false; + g.SetNextWindowSizeConstraint = g.SetNextWindowFocus = false; } static bool BeginPopupEx(const char* str_id, ImGuiWindowFlags extra_flags) @@ -3731,6 +3730,31 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl return window; } +static void ApplySizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) +{ + ImGuiContext& g = *GImGui; + if (g.SetNextWindowSizeConstraint) + { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.SetNextWindowSizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.SetNextWindowSizeConstraintCallback) + { + ImGuiSizeConstraintCallbackData data; + data.UserData = g.SetNextWindowSizeConstraintCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.SetNextWindowSizeConstraintCallback(&data); + new_size = data.DesiredSize; + } + } + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) + new_size = ImMax(new_size, g.Style.WindowMinSize); + window->SizeFull = new_size; +} + // Push a new ImGui window to add widgets to. // - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. // - Begin/End can be called multiple times during the frame with the same window name to append content. @@ -3972,9 +3996,8 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us } } - // Apply window size constraints and final size - if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) - window->SizeFull = ImMax(window->SizeFull, style.WindowMinSize); + // Apply minimum/maximum window size constraints and final size + ApplySizeFullWithConstraint(window, window->SizeFull); window->Size = window->Collapsed ? window->TitleBarRect().GetSize() : window->SizeFull; // POSITION @@ -4096,7 +4119,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0]) { // Manual auto-fit when double-clicking - window->SizeFull = size_auto_fit; + ApplySizeFullWithConstraint(window, size_auto_fit); if (!(flags & ImGuiWindowFlags_NoSavedSettings)) MarkSettingsDirty(); SetActiveID(0); @@ -4104,7 +4127,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us else if (held) { // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position - window->SizeFull = ImMax((g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize()) - window->Pos, style.WindowMinSize); + ApplySizeFullWithConstraint(window, (g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize()) - window->Pos); if (!(flags & ImGuiWindowFlags_NoSavedSettings)) MarkSettingsDirty(); } @@ -4177,6 +4200,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us // Setup drawing context window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.GroupOffsetX = 0.0f; window->DC.ColumnsOffsetX = 0.0f; window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); window->DC.CursorPos = window->DC.CursorStartPos; @@ -4268,6 +4292,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us if (first_begin_of_the_frame) window->Accessed = false; window->BeginCount++; + g.SetNextWindowSizeConstraint = false; // Child window can be out of sight and have "negative" clip windows. // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar). @@ -4906,6 +4931,15 @@ void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiSetCond cond) g.SetNextWindowSizeCond = cond ? cond : ImGuiSetCond_Always; } +void ImGui::SetNextWindowSizeConstraint(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeConstraintCallback custom_callback, void* custom_callback_user_data) +{ + ImGuiContext& g = *GImGui; + g.SetNextWindowSizeConstraint = true; + g.SetNextWindowSizeConstraintRect = ImRect(size_min, size_max); + g.SetNextWindowSizeConstraintCallback = custom_callback; + g.SetNextWindowSizeConstraintCallbackUserData = custom_callback_user_data; +} + void ImGui::SetNextWindowContentSize(const ImVec2& size) { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index 3b624d08..9c2cc288 100644 --- a/imgui.h +++ b/imgui.h @@ -52,7 +52,8 @@ struct ImGuiStorage; // Simple custom key value storage struct ImGuiStyle; // Runtime data for styling/colors struct ImGuiTextFilter; // Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" struct ImGuiTextBuffer; // Text buffer for logging/accumulating text -struct ImGuiTextEditCallbackData; // Shared state of ImGui::InputText() when using custom callbacks (advanced) +struct ImGuiTextEditCallbackData; // Shared state of ImGui::InputText() when using custom ImGuiTextEditCallback (rare/advanced use) +struct ImGuiSizeConstraintCallbackData;// Structure used to constraint window size in custom ways when using custom ImGuiSizeConstraintCallback (rare/advanced use) struct ImGuiListClipper; // Helper to manually clip large list of items struct ImGuiContext; // ImGui context (opaque) @@ -73,6 +74,7 @@ typedef int ImGuiInputTextFlags; // flags for InputText*() // e typedef int ImGuiSelectableFlags; // flags for Selectable() // enum ImGuiSelectableFlags_ typedef int ImGuiTreeNodeFlags; // flags for TreeNode*(), Collapsing*() // enum ImGuiTreeNodeFlags_ typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); +typedef void (*ImGuiSizeConstraintCallback)(ImGuiSizeConstraintCallbackData* data); // Others helpers at bottom of the file: // class ImVector<> // Lightweight std::vector like class. @@ -138,6 +140,7 @@ namespace ImGui IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiSetCond cond = 0); // set next window position. call before Begin() IMGUI_API void SetNextWindowPosCenter(ImGuiSetCond cond = 0); // set next window position to be centered on screen. call before Begin() IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiSetCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() + IMGUI_API void SetNextWindowSizeConstraint(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeConstraintCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (enforce the range of scrollbars). set axis to 0.0f to leave it automatic. call before Begin() IMGUI_API void SetNextWindowContentWidth(float width); // set next window content width (enforce the range of horizontal scrollbar). call before Begin() IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiSetCond cond = 0); // set next window collapsed state. call before Begin() @@ -1029,6 +1032,16 @@ struct ImGuiTextEditCallbackData bool HasSelection() const { return SelectionStart != SelectionEnd; } }; +// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraint(). Callback is called during the next Begin(). +// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraint() parameters are enough. +struct ImGuiSizeConstraintCallbackData +{ + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraint() + ImVec2 Pos; // Read-only. Window position, for reference. + ImVec2 CurrentSize; // Read-only. Current window size. + ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. +}; + // ImColor() helper to implicity converts colors to either ImU32 (packed 4x1 byte) or ImVec4 (4x1 float) // Prefer using IM_COL32() macros if you want a guaranteed compile-time ImU32 for usage with ImDrawList API. // Avoid storing ImColor! Store either u32 of ImVec4. This is not a full-featured color class. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c77adc9b..242ea707 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -46,6 +46,7 @@ #endif #define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) +#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B)) //----------------------------------------------------------------------------- // DEMO CODE @@ -59,6 +60,7 @@ static void ShowExampleAppLayout(bool* p_open); static void ShowExampleAppPropertyEditor(bool* p_open); static void ShowExampleAppLongText(bool* p_open); static void ShowExampleAppAutoResize(bool* p_open); +static void ShowExampleAppConstrainedResize(bool* p_open); static void ShowExampleAppFixedOverlay(bool* p_open); static void ShowExampleAppManipulatingWindowTitle(bool* p_open); static void ShowExampleAppCustomRendering(bool* p_open); @@ -105,6 +107,7 @@ void ImGui::ShowTestWindow(bool* p_open) static bool show_app_property_editor = false; static bool show_app_long_text = false; static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; static bool show_app_fixed_overlay = false; static bool show_app_manipulating_window_title = false; static bool show_app_custom_rendering = false; @@ -120,6 +123,7 @@ void ImGui::ShowTestWindow(bool* p_open) if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); if (show_app_manipulating_window_title) ShowExampleAppManipulatingWindowTitle(&show_app_manipulating_window_title); if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); @@ -183,6 +187,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); ImGui::MenuItem("Long text display", NULL, &show_app_long_text); ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); ImGui::MenuItem("Simple overlay", NULL, &show_app_fixed_overlay); ImGui::MenuItem("Manipulating window title", NULL, &show_app_manipulating_window_title); ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); @@ -1793,6 +1798,43 @@ static void ShowExampleAppAutoResize(bool* p_open) ImGui::End(); } +static void ShowExampleAppConstrainedResize(bool* p_open) +{ + struct CustomConstraints // Helper functions to demonstrate programmatic constraints + { + static void Square(ImGuiSizeConstraintCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); } + static void Step(ImGuiSizeConstraintCallbackData* data) { float step = (float)(int)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + }; + + static int type = 0; + if (type == 0) ImGui::SetNextWindowSizeConstraint(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 1) ImGui::SetNextWindowSizeConstraint(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 2) ImGui::SetNextWindowSizeConstraint(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 3) ImGui::SetNextWindowSizeConstraint(ImVec2(300, 0), ImVec2(400, FLT_MAX)); // Width 300-400 + if (type == 4) ImGui::SetNextWindowSizeConstraint(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 5) ImGui::SetNextWindowSizeConstraint(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step + + if (ImGui::Begin("Example: Constrained Resize", p_open)) + { + const char* desc[] = + { + "Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 300-400", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); + if (ImGui::Button("200x200")) ImGui::SetWindowSize(ImVec2(200,200)); ImGui::SameLine(); + if (ImGui::Button("500x500")) ImGui::SetWindowSize(ImVec2(500,500)); ImGui::SameLine(); + if (ImGui::Button("800x200")) ImGui::SetWindowSize(ImVec2(800,200)); + for (int i = 0; i < 10; i++) + ImGui::Text("Hello, sailor! Making this line long enough for the example."); + } + ImGui::End(); +} + static void ShowExampleAppFixedOverlay(bool* p_open) { ImGui::SetNextWindowPos(ImVec2(10,10)); diff --git a/imgui_internal.h b/imgui_internal.h index b4db7228..c787dbd1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -394,6 +394,10 @@ struct ImGuiContext ImGuiSetCond SetNextWindowSizeCond; ImGuiSetCond SetNextWindowContentSizeCond; ImGuiSetCond SetNextWindowCollapsedCond; + ImRect SetNextWindowSizeConstraintRect; // Valid if 'SetNextWindowSizeConstraint' is true + ImGuiSizeConstraintCallback SetNextWindowSizeConstraintCallback; + void* SetNextWindowSizeConstraintCallbackUserData; + bool SetNextWindowSizeConstraint; bool SetNextWindowFocus; bool SetNextTreeNodeOpenVal; ImGuiSetCond SetNextTreeNodeOpenCond; @@ -472,6 +476,8 @@ struct ImGuiContext SetNextWindowContentSizeCond = 0; SetNextWindowCollapsedCond = 0; SetNextWindowFocus = false; + SetNextWindowSizeConstraintCallback = NULL; + SetNextWindowSizeConstraintCallbackUserData = NULL; SetNextTreeNodeOpenVal = false; SetNextTreeNodeOpenCond = 0;