You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
Rather than piling multiple roles onto KidArea, the roles should be split up into separate types and stages:
The KidAreadefines the Rect upon which children widgets may be placed. Foo's KidAreaRect 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.
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.
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.
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.
Produce the additional_scroll_offset by summing:
Scrolling of the mouse wheel.
Movement of the scrollbar.
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.
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.
The text was updated successfully, but these errors were encountered:
Problem
At the moment, the job of a
Widget
'sKidArea
is a little ambiguous. It has progressively undertaken multiple roles:Widget
's childrenWidget
s are placed when using placementPositionable
methods, i.e..mid_top()
,.bottom_right()
.Widget
s.Widget
s 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.
.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'sKidArea
is the root rectangle.KidArea
, the roles should be split up into separate types and stages:KidArea
defines theRect
upon which children widgets may be placed. Foo'sKidArea
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 theWidget
's entire boundingRect
, however it can be specified by overriding theWidget::kid_area
method.scrollable_rect
(ascrollable_range
for each axis) at the beginning of the scroll calculation stage during theset_widget
function. This rect specifies the total area which may be "offset" from the _root_ rectangle (theKidArea
). Thescrollable_rect
will be determined by the bounding box around both theKidArea
and all _un-scrolled_ _visible_ children widgets. Note that this means when reading all children widgets'Rect
s to construct the boundingRect
, we must first subtract the scrollable parent widget's _current_scroll_offset
from their absolutexy
positions in order to get their _original_ aka _un-scrolled_ position. Thus, the calculatedscrollable_rect
should be identical each update regardless of changing scroll offset. It should only change if:scrollable_rect
to determine the min and max scroll_offset bounds for each axis, where:min
bound of an axis is the distance between the start of the scrollable_rect range and the start of theKidArea
rect range.max
bound of an axis is the distance between the end of theKidArea
rect range and the end of the scrollable_rect_range.current_scroll_offset
.scroll_offset
.0
, or perhaps some other value specified by a giveninitial_scroll_alignment
.additional_scroll_offset
by summing:new_scroll_offset
by summing theadditional_scroll_offset
onto thecurrent_scroll_offset
, clamping the result to the previously calculatedmin
andmax
bounds. Thisnew_scroll_offset
will then be stored as the widget's newscroll_offset
, which children widgets will then sum onto their own positions during thecalc_xy
function..crop_kids()
method, which crops the children widgets of some widget to itsKidArea
. 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.The text was updated successfully, but these errors were encountered: