Imgui: Virtual Scrolling Area

Created on 3 Mar 2015  路  20Comments  路  Source: ocornut/imgui

Hi,

I guess this is more of a general question of how I can accomplish this. Imagine that you want to show disassembly at a given address (say 0x500) What one usually do is the get a bunch of instructions before and after that so you perhaps have a range of 0x400 - 0x600 to show the surrounding instructions. Then If the user scrolls up one will fetch more instructions (from 0x300, 0x200 and so on)

What I'm getting at is I wonder how to implement this in ImGui? I can show the range 0x400 - 0x600 of course with no problem but I would need a scrollbar that the user can drag so I can fetch more instructions either before or after the range.

I guess this is really similar to showing a big text file as well (where you don't actually read the whole file to memory but try to fetch data on the fly as needed)

For some inspiration of how this could work is to do some debugging in VS, switch to disassembly, drag the scrollbar around a bit and watch how it behaves.

Cheers!

help wanted question scrolling

Most helpful comment

So to answer you original request, so you can do things like:

// page down-ish
imGui::SetScrollY(ImGui::GetScrollY() + ImGui::GetWindowSize().y); 

Or based on an actual cursor position (item position)

ImGui::SetScrollFromPosY(my_pos + 100)

All 20 comments

  • The range of the scrollbar is calculated every frame from the amount of contents output in the window (you may run into off-by-one-frame type of issues).
  • For large output, what one typically does is to bypass submitting the non-visible content and do a SetCursorPos() to provide the content size to the ImGui so it can draw a scrollbar. The function ImGui::CalcListClipping() is a helper to do that for you for the common case and probably of course to understand this usage pattern.

So suppose you have loaded 0x1000...0x4000 worth of disassembly and displaying 0x2200...0x2400
You could set the cursor to (0x2200-0x1000 == 0x1200), display 0x200 worth of contents, then set the cursor to (0x4000-0x1000 == 0x3000), the size of your content.

  • If you detect that your scrolling value puts you near the bound you can load more data and update the range for the next frame. There's currently no "explicit" way to query the scroll value, I should probably add one. But if you do:
  ImVec4 clip_rect = GetWindowDrawList()->clip_rect_stack.back();

in clip_rect.y and clip_rect.w you get the minimum and maximum visible bound of what's visible.
So say if clip_rect.w - GetWindowPos().y < frame_height then you are at the top of your disassembly, if clip_rect.y - GetWindowPos().y > content_height-frame_height then you are at the bottom.

You'll get an issue because holding mouse button on the scrollbar will always set your scroll to that % of the scroll range, so if you hold mouse at the top it will keep loading. We could improve the scrollbar to cope with resizing better.

What Visual appears to do is that it only sets the scroll % when you click or move the mouse, so holding doesn't reapply the scrolling until mouse move again (the threshold is set to like 1 pixel which is a little dodgy compared to how mouse threshold are usually handled, but works). That's also something we could do. For now what I would do on your application side to get started would be to use a timer to minimize reload. Bit odd but it would work and may feel more reliable than what Visual does.

  • The scrollbar is not well designed to render for large amount, that is, past a certain amount the cursor won't be very visible, and clicking always does an "absolute seek" which is rather unprecise for large content. Both of those things can be fixed rather easily and are already in my TODO list. The "grab" should be of a minimum size. Clicking inside the "grab box" should not seek, and movement afterward should be all relative. Does that makes sense? I could do those things if it's an immediate problem to you.

With all the info above you should be able to get it to work.

  • Additionally: ImGui::SetScrollPosHere() set the scroll value to be centred at the position of when it was called and we may useful.

Edited with fixes and for readability.

Thanks for a great reply :)

I will try this out within a few days and see how it works out.

Cheers!

I've done a few improvements as logged above.
You can now query the scroll position / max.

The scrollbar has been rewritten. Took a lot more time than expected to do it right (precision issues, etc.).

The grab now has a minimum size so it always shows even if you have thousands of lines.
When clicking on the grab it doesn't initially apply a scroll (it used to center the grab on the mouse position which was sort of destructive). From there movement are relative.
When clicking outside of the grab it does an absolute repositioning, but also clamp the grab if you clicked near the edges and switch to relative movement after that.

It's written in a way where it should kind of work ok if the window contents change while holding the grab, but some of the details are still undecided (should we lock the scroll in absolute pixels value (instead of %), overriding programmatic changes, and only updating the scroll if the mouse move?).

I'd appreciate if someone tried the new scrollbar, the change was sufficiently tedious that I might have broken something subtle (hopefully not).

Awesome! I will give it a try as soon as I can (hopefully tomorrow)

Closing this (hopefully all the info you need is there for when you need it, else please reopen)

FYI i have added a higher level helper ImGuiListClipper to handle the most common pattern of using CalcListClipping().
https://github.com/ocornut/imgui/commit/eb4ffd5dbd7f6f9f4260e5d66df2347e7ef12270

So code like

int displayStart, displayEnd;
ImGui::CalcListClipping(lines_count, lines_height, &displayStart, &displayEnd);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (displayStart * lines_height));
for (int i = displayStart; i < displayEnd; ++i)
[...]
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ((lines_count - displayEnd) * lines_height));

Can become

ImGuiListClipper clipper(lines_count, lines_height);
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
[...]
clipper.End();

CalcListClipping() is still available as the lower level function.

I see that I missed this! Thanks for adding it :)

I will try it out now for my Scintilla impl as I currently have no way of scrolling the text (scrolling the text using a scrollbar that is) as it doesn't behave like a regular ImGui widget I need to be able to set a line height so this should hopefully work fine for it.

Hi again,

So using this I can now scroll my text just fine which is great :)

I have one question though. Say that the user presses down / up / home / etc on the keyboard I want make sure that this is handled correctly (and that the scrollbar gets updated etc)

I tried to do:

ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ItemHeight);

On the keypress but that doesn't seem to work correct. I wonder if you have any pointers on how to do it?

Thanks!

Friendly poke @ocornut :)

SetCursorPosY move your "draw position" for rendering subsequent widgets. If you want to scroll you probably don't want to touch the cursor position but rather the scroll position. A few calls are missing to freely manipulate the scroll position though. (working on it)

Yeah I use the ImGuiListClipper as outlined above and everything works just fine as long as the user scrolls using the scrollbar on the side. The problem comes if I want (from code) reset the position (say the user press up/down/goto to start of text/etc)

See what I mean?

Yes you need a missing call like SetScrollPosY().

Right now there's only SetScrollPosHere() which center scrolling on the current cursor pos.

I need to get back to that. There is a problem with using scrolling amount vs cursor positions (which starts at 0.0f-scroll_y) they are in different spaces. I may want to unify that preferably to use cursor positions in which case I would have to change the unit of GetScrollPosY()/GetScrollMaxY() - may not even affect your code. Not sure what's the best way yet, will get back to it.

Alright. Sounds good.

I think it's good now. I needed to make some fixes to the existing API.

  • GetScrollPosY() was renamed to GetScrollY().
    This was really necessary because positions (e.g. cursor position) are not equivalent to scrolling amount and old name was misleading. I have added this function for you (in this thread) and with no google match I took the liberty to just rename it and not keep a confusing old-name.
  • Renamed SetScrollPosHere() to SetScrollHere(). Keep inline redirection function.
    Went back and forth on this one because I could have avoided to rename it. Using 'From' in two functions name seemed like the best way to convey the difference between positions and scrolling amount so I first had SetScrollFromCursorPos() with would be "more precise", then went back to the "Here" notation that is also used in SetKeyboardFocusHere() which doesn't and can't refer to CursorPos. Any opinion?

Hopefully the new set of functions allows to do everything:

float GetScrollY();                                                // get scrolling amount [0..GetScrollMaxY()]
float GetScrollMaxY();                                             // get maximum scrolling amount == ContentSize.Y - WindowSize.Y
void  SetScrollY(float scroll_y);                                  // set scrolling amount [0..GetScrollMaxY()]
void  SetScrollHere(float center_y_ratio = 0.5f);                  // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom.
void  SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions.
static inline void SetScrollPosHere() { SetScrollFromCursorPos(); } // OBSOLETE 1.42+

The only thing I'm not confident about is the effect of horizontal scrolling.on the optional parameter of SetScrollHere(). I suppose it'll have to be a second optional parameter which will be weird when that happen.

The new parameter center_y_ratio=0.5f allows to specify how to set the scrolling (center by default), e.g.

scroll

So to answer you original request, so you can do things like:

// page down-ish
imGui::SetScrollY(ImGui::GetScrollY() + ImGui::GetWindowSize().y); 

Or based on an actual cursor position (item position)

ImGui::SetScrollFromPosY(my_pos + 100)

Sweet! Thanks. I will try it out

Works perfect. Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bizehao picture bizehao  路  3Comments

noche-x picture noche-x  路  3Comments

namuda picture namuda  路  3Comments

DarkLinux picture DarkLinux  路  3Comments

SlNPacifist picture SlNPacifist  路  3Comments