Hello @zep. I'm writing this thread not really as a bug report, but maybe it is one, I'm not really sure at the moment. I wanted to gather my notes about the current state of the GUI lib (OS 0.1.1d) as I'm trying to deepen my use of it as I'm making my tools and doodles with it.
First, to the other people that might read this thread, I wanted just to say that I'm aware that it's an evolving library like the rest of the OS. Thus, the details discussed here might either stay relevant or fall out of date through time and releases. In the same movement I would like to list the lexicon I'm most likely going to use just so we are on the same tracks.
- Every time you use
gui:attach
orgui:new
, you create aGuiElement
instance, so I'm going to stick with "GUI element" or just "element" to denominate everything attached to the GUI built from those functions. - A GUI tree (or sub-tree) is composed of a root element and all the children elements down to the leaves. Usually, the root element created with
create_gui
is usually a program's GUI tree root. I also tend to call an element's tree what is actually the whole sub-tree composed of that element and its children. GuiElement:event
tries to call an element's event handler depending on the event's type (msg.event
), so the element's method named like so. For instance,update
anddraw
are two (special) event handlers.- I mostly use the term "property" for any element member that isn't a function and has actual usage in the library, such as
x, y, width, etc...
.
So, let's see what I have found so far.
MAYBE BUG
The hidden burden of the draw handler
Last seen 0.1.1d
First, I noted that in a GUI tree, if an element doesn't have a draw
handler, a few key points of the GUI logic falls short of working on it or its children. For instance, screen clipping doesn't apply on that one element and the hidden
property actually stops hiding the element's tree. This quirks shows itself most often when one tries to use the scrollbar element. Here's an example of the issue:
This snippet should create a scrollable list element on a grey background on the left and a scrollable list element on a blue background. Both of those elements have the same size and contains the same inner
and scrollbar elements. The main difference here is that the list's outer
element (the one that contains the scrollbar) on the left has its own draw
handler and thus propagates its own clipping region to its inner
element (the scrollable element) and the latter is properly clipped. The list on the right doesn't have a draw handler
and thus doesn't clip its region and the list ends up "overflowing", and this even with the clip_to_parent
property's default value as true
. The GUI tree should look like that:
root - grey framer, clips the screen - outer element, clips the screen - inner element - scrollbar - blue framer, clips the screen - outer element, doesn't clip - inner element, clipped by the blue frame's clipping - scrollbar |
I'm not totally sure whether it's a bug. Granted, clipping might be expensive and using the existence of a draw
handler makes sense because it's going to be used in its context, but I end up adding quite a few nop functions to elements to let them properly visually clip, hence why I'm listing this quirk here: I'd like to know if it's indeed an intended behavior and what should be expected to clipping elements or if it's just a side effect. If it's an intended behavior, will there be an alternative solution in the future (like a clipping
property)?
In a related quirk, the logic around the hidden
property also depends on the presence of the draw
handler in an unexpected way: if the draw
handler is missing for a hidden element, the hidden
property will block the update
event from moving down the element's tree's handles but not the draw
event, causing the element and its tree to not react to input but still draw. Here's a variant of the precedent screen that shows the issue:
The difference from the previous snippet is that the list on the left should be invisible where the one on the right side is visible but you can't interact with. Again, I'm not sure if it's an intended behavior. I'd expect instead to have an inhibiting property to halt the event from browsing the tree or having the
update
handler return true
which is already something that looks like thought of and designed when reading the library. Is that going to stay or change in the future?
MAYBE BUG
Scrollbar content are clickable past their parents
Last seen 0.1.1d
Another issue involving scrollable elements I'm noticing are that their child elements can end up be interactive past the clipping region of the scrollable element's container. This actually might be closely related to the previous quirk but I'm not fully certain, so I'm going to leave it separate for now.
The way I conceptualize scrollbar elements are elements that convert their parents into a clipping "window" of the scrollable child (the parent's first child element). If they have a draw
event handler, the scrollable's draw region will be then clipped by the parent's draw region (see the section about the "hidden burden of the draw handler"), which makes it visibly work fine, but I get unexpected click
or tap
events from children of the scrollable element where they shouldn't be clickable due to them being out of the parent's region. Let's consider the following tree:
- root element - header element, just to have something with height here. - button element, visibly on the header - contianer element - inner element, a.k.a. the scrollable element - some element used as item in a list - some element used as item in a list - scrollbar element ... |
And consider the following snippet that reflects the given hieararchy:
If the scrollable element's y is at zero (so, effectively no scrolling applied), the user will be able to tap on the item through the region as expected. Yet, as soon as enough scrolling is done, the user can still click on the now out of frame list item elements by clicking them where the header is. The behavior can be confirmed visually by disabling parent clipping with the clip_to_parent
property set to false
: the items will draw over the header element and tapping on them will work. That is shown when running the snippet where you can press the first buttons of the list through the blue banner if you scroll down enough. Interestingly enough, any element past the clipping region's bottom border won't register input events.
What issues could this cause is that the header's button element can be effectively covered by the scrollable element and if the scrollable's elements were scrolled up enough to have an interactive element on top of said button, they'll "steal" the event. The result is that an item made invisible by being clipped out by the parenting hierarchy will still process the input event as if not clipped.
So far, even though clip_to_parent
is set by default to true
, I found no real "clean" solution with the GUI's API to prevent this and I suspect this might be a bug related to how the clipping region is passed from parent to child in el_at_xy_recursive
but I have to test that on my side before anymore finger pointing at the library. In the meantime, can you confirm that listening to even out of the drawing region with clip_to_parent
set to true
is an intended behavior or a bug?
While iterating over this post's draft I noticed three more quirks: if I move the header element's creation after the container's, the header will prevent the contained buttons from being clicked. Also, it looks like the container's clipping region only affects the scrollable element, not its children, which would explain somewhat the issue. And the cherry on the top: the header element can be removed and the same issue will still appear.
Addendum: There's a slight tweak you seem to have done as an optimization in el_at_xy_recursive
, the if (true or is_inside or not el.clip_to_parent)
part at line 691. If I turn true
into false
or remove it, the problem disappears and the clipping works as intended. Is that a regression from previous versions? I don't remember such problem appearing before.
Addendum 2: removing the shortcut is actually faster in one of my GUI-heavy projects than keeping it. I'm not sure if that'd be a global improvement but that was funny to see such drastic change (20% when idling in a specific region down to 16% post fix)
MAYBE BUG
Nested scrollbar regions
Last seen 0.1.1d
I noticed that nested scrollbar regions don't consume scrolling events, potentially leaving scenarios where you scroll in two regions at once if they're nested. There might be a problem with the scrollbar elements themselves too where they're not always properly sized or clipped. Consider the following snippet:
In the screenshot, you can see a white scrollable element inside another scrollable element which also is nested into another element. We end up with two scrollbars as expected but if one scrolls in the white region, they'll end up scrolling in both regions. You can also clearly see that the inner scrollbar goes past the associated scrollable element and its parent, which might not be expected.
From what I understand, input events are processed from the deepest element up to the GUI as long as an event handler doesn't return true
. Scrollbar elements process the mousewheel
event but do not prevent the event from bubbling up, which allows their parent to also process and eventually scroll too. I'm not sure if it's intended or a bug, should they inhibit the event instead? Regarding the scrollbar's wrong size, I don't have a clue about what could be changed yet.
QUESTION
Extending the GuiElement class
Last seen 0.1.1d
This item is not really a bug report but closer to a design question to follow your library's philosophy. Because GUI elements come with a lot of nifty bits here and there such as clipping, automatic camera
calls and event handlers, I tend to make a lot of small elements like composite items in scrollable lists but the way I'm usually doing involves creating the same table over and over where a metatable would lighten the memory (and CPU for initialization) usage. Given that GUI elements already depend on a metatable (GuiElement
, the one never to be used outside gui.lua) and that it's being set in both new
and attach
(by the calling the former), if I were to try implementing another class, what would be the proper way of handling the change of metatable if attach
would just remove it? I'd like to overwrite the smallest amount of properties or methods from the base class in order to be forward-compatible. The closest I ever was something like this:
The used class system is mostly irrelevant, I could have tried with 30log instead or another class library, but the idea of extending GuiElement
with a custom class stays the central point of the question. Sadly, as mentioned at the end of the snippet, as long as we call gui:attach
, the element's metatable gets overriden with GuiElement, rendering any attempt at using the inheritance moot unless I alter GuiElement.attach
, like in the following snippet:
do local prev_attach = gui.attach function gui:attach(el) local mt = getmetatable(el) local res = prev_attach(self, el) if (mt) setmetatable(res, mt) return res end end |
If done before creating the test
element, the metatable will be preserved and test:do_something()
will actually do something but I'm not fully satisfied with this solution because it does replace something directly in GuiElement
, potientially causing subtle issues in the future, at least, not the ones involving trying to cram an Object-oriented design into the library's usage. Is replacing gui.attach
okay like this? Is there going to be a "cleaner" or at least official way in the future to extended the GUI library without hot-swapping methods or the metatable on the fly like this?
That's all I have for now, I don't know if I'll do many of those posts where I gather many issues at once. I'll try to tend to the post with more findings about those quirks at the very least. Anyway, I hope you'll have a nice day!
[Please log in to post a comment]