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

Refactor Scrolling (again). #659

Closed
mitchmindtree opened this issue Jan 5, 2016 · 0 comments · Fixed by #662
Closed

Refactor Scrolling (again). #659

mitchmindtree opened this issue Jan 5, 2016 · 0 comments · Fixed by #662

Comments

@mitchmindtree
Copy link
Contributor

Problem

At the moment, the job of a Widget's KidArea is a little ambiguous. It has progressively undertaken multiple roles:

  • The place upon which that Widget's children Widgets are placed when using placement Positionable methods, i.e. .mid_top(), .bottom_right().
  • The area which is cropped when scrolling children Widgets.
  • The area upon which Widgets are offset when scrolled.

This has slowly introduced complexity into the widget positioning logic, especially with relation to scrolling, which has lead to some bugs that I've noticed in my own personal projects that are extremely difficult to reason about. Thus, it is clear that the scrolling logic needs to be refactored into a more palatable, clearly defined, robust, step by step process.

Solution

It's quite difficult to find thorough writing on the topic of handling scrolling logic, and even harder for immediate-mode interfacing GUIs. The following draws some inspiration from servo's scrolling overview, however only loosely as servo is retained and event based, whereas conrod is immediately reactive so requires more fine-grained, step-by-step details to be covered.

  1. First, the name of the opt-in method for scrolling should probably change from .scrolling(bool) to .scroll_kids() to clarify that opting-in to scrolling on the widget Foo means that Foo's children widgets are offset by scrolling, where Foo's KidArea is the root rectangle.
  2. Rather than piling multiple roles onto KidArea, the roles should be split up into separate types and stages:
    1. The KidAreadefines the Rect upon which children widgets may be placed. Foo's KidArea Rect does not change with respect to Foo's scrolling and is simply used for handling the placement of child widgets specified by their continuous positioning arguments. By default, this is the Widget's entire bounding Rect, however it can be specified by overriding the Widget::kid_area method.
    2. For scrollable widgets, produce a scrollable_rect (a scrollable_range for each axis) at the beginning of the scroll calculation stage during the set_widget function. This rect specifies the total area which may be "offset" from the _root_ rectangle (the KidArea). The scrollable_rect will be determined by the bounding box around both the KidArea and all _un-scrolled_ _visible_ children widgets. Note that this means when reading all children widgets' Rects to construct the bounding Rect, we must first subtract the scrollable parent widget's _current_ scroll_offset from their absolute xy positions in order to get their _original_ aka _un-scrolled_ position. Thus, the calculated scrollable_rect should be identical each update regardless of changing scroll offset. It should only change if:
      • One or more of the child widgets are manually moved by the user, changing the dimensions of the bounding box.
      • One or more new child widgets appear, expanding the size of the bounding box.
      • One or more child widgets disappear, reducing the size of the bounding box.
    3. Use the calculated scrollable_rect to determine the min and max scroll_offset bounds for each axis, where:
      • The min bound of an axis is the distance between the start of the scrollable_rect range and the start of the KidArea rect range.
      • The max bound of an axis is the distance between the end of the KidArea rect range and the end of the scrollable_rect_range.
    4. Get the current_scroll_offset.
      • If this is not the first time the widget has been instantiated, this value will be the widget's currently stored scroll_offset.
      • If this is the first time the widget is instantiated, this will either be 0, or perhaps some other value specified by a given initial_scroll_alignment.
    5. Produce the additional_scroll_offset by summing:
      • Scrolling of the mouse wheel.
      • Movement of the scrollbar.
    6. Determine the new_scroll_offset by summing the additional_scroll_offset onto the current_scroll_offset, clamping the result to the previously calculated min and max bounds. This new_scroll_offset will then be stored as the widget's new scroll_offset, which children widgets will then sum onto their own positions during the calc_xy function.
  3. Introduce a .crop_kids() method, which crops the children widgets of some widget to its KidArea. The .scroll_kids() method will then call this internally, rather than having its own special-case cropping as is currently the case. This will allow the user more flexibility, being able to crop children widgets whether or not the parent is scrollable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant