Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No horizontal scrollbar in a window when calling SetNextWindowContentSize with size larger than the screen #7604

Closed
kotturtech opened this issue May 19, 2024 · 6 comments

Comments

@kotturtech
Copy link

Version/Branch of Dear ImGui:

Version 1.89.9, Branch: docking

Back-ends:

imgui_impl_sdl2cpp + imgui_imp_sdlrenderer2cpp

Compiler, OS:

Windows 11 + MSVC 19.39.33521.0

Full config/build information:

Dear ImGui 1.89.9 (18990)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _WIN64
define: _MSC_VER=1939
define: _MSVC_LANG=201402
define: IMGUI_HAS_VIEWPORT
define: IMGUI_HAS_DOCK
--------------------------------
io.BackendPlatformName: imgui_impl_sdl2
io.BackendRendererName: imgui_impl_sdlrenderer2
io.ConfigFlags: 0x00000041
 NavEnableKeyboard
 DockingEnable
io.ConfigViewportsNoDecoration
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x00000C0E
 HasMouseCursors
 HasSetMousePos
 PlatformHasViewports
 HasMouseHoveredViewport
 RendererHasVtxOffset
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,64
io.DisplaySize: 1536.00,793.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

I am trying to specify a window client area which is certainly larger than the display, and to enable scrolling through it.
To test that, I am drawing a line, and test the scroll behavior visually.

My brief version of the code is:

bool show = true;
ImGui::SetNextWindowContentSize(ImVec2(25000.0f,25000.0f)); 
ImGui::Begin("Map Area", &show); 
ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x,    				 
                                    ImGui::GetContentRegionAvail().y),false,
                   					ImGuiWindowFlags_HorizontalScrollbar);

//Initial position of the line
ImVec2 lineStartPos = ImGui::GetWindowPos();
//Read scroll
scrollX = ImGui::GetScrollX();
scrollY = ImGui::GetScrollY(); 
//Translate the start of the line to scroll
lineStartPos.x -= scrollX;
lineStartPos.y -= scrollY;
//Draw line 
ImGui::GetWindowDrawList()->AddLine(lineStartPos, {16000, 16000}, 0xFFFFFFFF);
ImGui::EndChild();
ImGui::End();

The vertical scrolling works just as expected - The scroll bar appears in the window and the line portion is displayed relative to the viewed area.
However, the horizontal scroll bar is not being displayed despite specifying the ImGuiWindowFlags_HorizontalScrollbar flag for the child. Specifying the ImGuiWindowFlags_AlwaysHorizontalScrollbar doesn't help either.
Am I missing something?
Thanks,
Tim

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

#include <SDL.h>
#include <imgui.h>
#include <imgui_impl_sdl2.h>
#include <imgui_impl_sdlrenderer2.h>
#include <iostream>

static float get_scale(SDL_Window *window, SDL_Renderer *renderer) {
  int window_width{0};
  int window_height{0};
  SDL_GetWindowSize(window, &window_width, &window_height);

  int render_output_width{0};
  int render_output_height{0};
  SDL_GetRendererOutputSize(renderer, &render_output_width,
                            &render_output_height);

  const auto scale_x{static_cast<float>(render_output_width) /
                     static_cast<float>(window_width)};

  return scale_x;
}

int main(int argc, char **argv) {
  // Initialize SDL
  unsigned int init_flags{SDL_INIT_VIDEO | SDL_INIT_TIMER |
                          SDL_INIT_GAMECONTROLLER};
  if (SDL_Init(init_flags) != 0) {
    std::cout << "Error: " << SDL_GetError() << std::endl;
    return -1;
  }

  // Creating window and renderer
  auto window_flags{static_cast<SDL_WindowFlags>(
      SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MAXIMIZED)};
  constexpr int window_center_flag{SDL_WINDOWPOS_CENTERED};

  auto window = SDL_CreateWindow("Example", window_center_flag,
                                 window_center_flag, 5,
                                 5, window_flags);
  // TODO: Check if window was created successfully.
  SDL_SetWindowResizable(window, SDL_FALSE);

  auto renderer_flags{static_cast<SDL_RendererFlags>(SDL_RENDERER_PRESENTVSYNC |
                                                     SDL_RENDERER_ACCELERATED)};
  auto renderer = SDL_CreateRenderer(window, -1, renderer_flags);

  if (renderer == nullptr) {
    std::cout << "Error creating SDL_Renderer!\n";
    return -1;
  }

  // Set render scale for high DPI displays
  const float scale{get_scale(window, renderer)};
  SDL_RenderSetScale(renderer, scale, scale);

  // ImGui initialization
  IMGUI_CHECKVERSION();
  ImGui::CreateContext();
  ImGuiIO &io{ImGui::GetIO()};

  io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
  io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
  io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;

  ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);
  ImGui_ImplSDLRenderer2_Init(renderer);
  
  //The loop
  bool running = true;
  while (running) {
    SDL_Event event{};
    while (SDL_PollEvent(&event) == 1) {
      ImGui_ImplSDL2_ProcessEvent(&event);

      if (event.type == SDL_QUIT) {
        running = false;
      }
    }

    ImGui_ImplSDLRenderer2_NewFrame();
    ImGui_ImplSDL2_NewFrame();
    ImGui::NewFrame();
    bool open = true;

    auto dockSpaceId = ImGui::DockSpaceOverViewport();

    if (ImGui::BeginMainMenuBar()) {
      if (ImGui::BeginMenu("File")) {
        if (ImGui::MenuItem("Exit")) {
          running = false;
        }
        ImGui::EndMenu();
      }
      ImGui::EndMainMenuBar();
    }

    ImGui::SetNextWindowContentSize(ImVec2(25000.0f, 25000.0f));
    ImGui::Begin("Scroll Window", NULL);
    ImGui::BeginChild("Child",
                      ImVec2(ImGui::GetContentRegionAvail().x,
                             ImGui::GetContentRegionAvail().y),
                      false, ImGuiWindowFlags_HorizontalScrollbar);

    ImVec2 lineStartPos = ImGui::GetWindowPos();
    auto scrollX = ImGui::GetScrollX();
    auto scrollY = ImGui::GetScrollY();
    // Translate
    lineStartPos.x -= scrollX;
    lineStartPos.y -= scrollY;

    ImGui::GetWindowDrawList()->AddLine(lineStartPos, {16000, 16000},
                                        0xFFFFFFFF);
    ImGui::EndChild();
    ImGui::End();

    ImGui::Render();

    SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
    SDL_RenderClear(renderer);
    ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
    SDL_RenderPresent(renderer);
  }

  // Cleanup
  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);
  ImGui_ImplSDLRenderer2_Shutdown();
  ImGui_ImplSDL2_Shutdown();
  ImGui::DestroyContext();
  SDL_Quit();
  return 0;
}
@kotturtech
Copy link
Author

Interestingly enough, if I change the window and child creation part to the following:

ImGui::SetNextWindowContentSize(ImVec2(25000.0f, 0.0f));
    ImGui::Begin("Scroll Window", NULL);
    ImGui::BeginChild("Child",
                      ImVec2(ImGui::GetContentRegionAvail().x,
                             ImGui::GetContentRegionAvail().y),
                      false, ImGuiWindowFlags_AlwaysHorizontalScrollbar);

The horizontal scroll bar does appear, but still doesn't behave the same as the vertical scroller - It will just be non-functional.
Hope that would help identifying the root cause of the issue

@GamingMinds-DanielC
Copy link
Contributor

GamingMinds-DanielC commented May 21, 2024

You set the content size for the parent window, but give the horizontal scroll bar to the child window which doesn't have a content size specified. Even though the child is embedded into the parent, that are two different windows that you are mixing up.

Btw, just drawing through a draw list doesn't add "content" of which the window logic is aware of. You could also submit a dummy item (f.e. with ImGui::Dummy()) for that purpose.

@ocornut
Copy link
Owner

ocornut commented May 21, 2024

Daniel is right, but it is also confusing why the only contents of your window is a child window and nothing else (maybe it's due to stripping down the code to reproduce the repro?). If the only content is another window it seems like the nested child may be extraneous.

@kotturtech
Copy link
Author

True!
Removing the child and adding ImGuiWindowFlags_HorizontalScrollbar to the parent window did the trick, thanks!
Btw, what I've used as a workaround is to use ImGui::SetCursorPos({20000.0,20000.0}); - Which also solved the problem, but I'm not sure whether the position of the cursor is expressed in pixels, or as a caret position within the window.
In any case, my mistake, after the clarification things work as expected and the issue may be closed.
Thanks everyone!

@ocornut
Copy link
Owner

ocornut commented May 22, 2024

Btw, what I've used as a workaround is to use ImGui::SetCursorPos({20000.0,20000.0}); - Which also solved the problem, but I'm not sure whether the position of the cursor is expressed in pixels, or as a caret position within the window.

Positions with SetCursorPos() are relatives to upper-left position of window, aka this is a pretty rubbish scheme which I would like to get rid of in v2.0, but nevertheless it is almost never a good idea to use SetCursorPos() with a constant/absolute value.
By calling Dummy({20000,20000}) after Begin() you would state the window has that much of contents and its scrollbar needs to take account of that + padding, which should be the same as calling SetNextWindowContentSize({20000.0f,20000.0f}) before Begin(). All values are currently in pixels.

Quite importantly, however, since 1.89 it is technically obsolete to use SetCursorPos() without an item to extent windows boundaries. See the Breaking Changes explanation of v1.89, but ErrorCheckUsingSetCursorPosToExtendParentBoundaries() if IMGUI_DISABLE_OBSOLETE_FUNCTIONS is not set currently emulate the old behavior.

@ocornut ocornut closed this as completed May 22, 2024
@ocornut
Copy link
Owner

ocornut commented May 22, 2024

Quite importantly, however, since 1.89 it is technically obsolete to use SetCursorPos() without an item to extent windows boundaries. See the Breaking Changes explanation of v1.89, but ErrorCheckUsingSetCursorPosToExtendParentBoundaries() if IMGUI_DISABLE_OBSOLETE_FUNCTIONS is not set currently emulate the old behavior.

For reference, pasting the contents of ErrorCheckUsingSetCursorPosToExtendParentBoundaries() here:

// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g. window or table cell)
// This is causing issues and ambiguity and we need to retire that.
// See https://github.com/ocornut/imgui/issues/5548 for more details.
// [Scenario 1]
//  Previously this would make the window content size ~200x200:
//    Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End();  // NOT OK
//  Instead, please submit an item:
//    Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK
//  Alternative:
//    Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK
// [Scenario 2]
//  For reference this is one of the issue what we aim to fix with this change:
//    BeginGroup() + SomeItem("foobar") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup()
//  The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously incorporate ItemSpacing.y after the item into content size, making the group taller!
//  While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger this issue.
void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
{
    ImGuiContext& g = *GImGui;
    ImGuiWindow* window = g.CurrentWindow;
    IM_ASSERT(window->DC.IsSetPos);
    window->DC.IsSetPos = false;
#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
    if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y)
        return;
    if (window->SkipItems)
        return;
    IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent.");
#else
    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
#endif
}

It is expected that near the end of this year (two years after 1.89.0) the #else block is removed and replaced by the other block.

@ocornut ocornut added the layout label May 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants