We recently removed callbacks for key events. This is pushing the immediate-mode paradigm forward in an unexpected way.
For background, widget relationships can affect who handles each event. With immediate-mode, a parent widget does not know what children it will see each frame.
Mouse events:
This works by checking the event against widget rectangles. A parent can know if a child got a chance, because all the children are inside the parent’s rectangle.
Key events are different, they target the widget with keyboard focus.
Key events:
To make this work, we used to have each widget propagate non-processed key events up the parent chain, just in case a parent wanted to process them.
This works, but uses callbacks. To add a keyboard shortcut to only a section of the gui, you had to write a whole Widget that was the parent of that whole section. Ugh.
We finally found the key question:
More generally:
The answer is dvui.lastFocusedIdInFrame()
. It returns the last widget id we saw during this frame that had focus (might be none).
If you call that twice, and the return is different, then a widget that ran between the two calls had focus. And you have the id to match events against.
Now to add a keyboard shortcut to a gui section:
This is also now what widgets use internally to process key events after children. It also improves code-flow understanding by not having parent code called during a child’s lifetime.
8 people contributed this month with 301 commits.
Colton Franklin contributed a whole tree widget!
Teodor Källman continued their impressive contributions:
Paul Hatchman put a lot of work into:
nat3 was back:
Meiko contributed:
Thank you to everyone who asked questions, filed issues, and contributed!
We all want our applications to repaint smoothly while the OS window is being resized.
Unfortunately Windows and Mac make this difficult. The normal event loop is suspended during the entire resize.
This meant during the resize you saw:
The underlying problem on both platforms is that they run a nested event loop during the resize, so we lose control.
There seem to be 2 solutions:
We now use the callback solution by default in the SDL3 backend if you are using dvui.App (so already using callbacks).
This works, you can see the content reflow while resizing!
But it is not perfect:
-Dsdl3-callbacks=false
.As best I can tell, to improve on this you have to use multiple threads and put a lot of bespoke code in.
Teodor Källman has successfully removed errors from almost all dvui functions. Horay!
Now dvui handles most errors internally:
Some functions like textureCreate()
can still fail.
Up to this point we hadn’t spent much time thinking about errors. Everybody (that I saw) put try
in front of all dvui functions, and any error took down the whole app.
Now dvui is aiming to be more robust to errors, especially cases under end-user control.
For example, if your gui shows user-controlled text or graphs user data, then we’d like for dvui to not crash if the user tries to show way too much text/info. Ideally dvui continues running and shows some visual indication that something is wrong, allowing the user to get back out of the situation.
This is the first big push in that direction, and we’ll continue to refine how we handle errors going forward.
If you have an application where dvui error handling is causing problems, I want to know! Please file an issue.
@sooriya on discord recently asked if we could do variable framerate on raylib, and now we can!
For waiting on an event without any timeout, raylib already had the necessary pieces with EnableEventWaiting().
For waiting with a timeout, I submitted a proposal PR to raylib, but it was turned down. So for that we are assuming that raylib is using glfw and we are directly calling glfwWaitEventsTimeout().
You can get this either by using dvui.App or copying the relevant parts from the updated examples/raylib-standalone.zig.
It’s not perfect:
But it’s good enough for applications that don’t want to peg the cpu.
7 people contributed this month with 192 commits.
nat3 rewrote and improved our icon rendering and compatibility:
Thank you!
Paul Hatchman wrote an entire GridWidget:
Thank you!
Teodor Källman was back again:
See “Styling” part of the demo for the color picker, box shadows, and gradients.
Thank you!
Thank you to everyone who asked questions, filed issues, and contributed!
What’s going on here:
In the end a store assistant came over and knew some other place on the screen you could touch to get rid of the dialog.
This kind of thing is why DVUI put a lot of work into the interaction between events and subwindows (dvui’s layers).
DVUI’s main geometry struct is a Rect
(rectangle with x,y and width/height). But what are the units?
We have 3 common units:
For example, you might specify a textEntry
with a font that is 16px tall and .min_content_size = .{ .w = 100 }
. Those are logical pixels, so they mean something together, but don’t say anything about how large it will be on the screen.
To convert, widgets call WidgetData.rectScale()
(or similar) which calls parent.screenRectScale()
to get a RectScale
. That is a physical rect plus a scale, and represents a rectangle of screen pixels and the scale factor between logical and physical pixels.
From the beginning we’ve had a steady stream of units errors because the units were implicit.
We’re trying out adding units to the types. Now we have:
For example RectScale.rectToPhysical()
converts from a (logical) Rect to Rect.Physical. Rect.scale()
now requires a unit type representing the units being converted to.
The drawing functions all take Rect.Physical, and the floating functions take Rect.Natural, hopefully making most units errors into compile errors.
This has already caught a few bugs, and I’m hoping it helps going forward.
Special thank you this month to Teodor Källman, who implemented:
Thank you to @Yinameah for making the docs look much better!
7 people contributed this month with 227 commits.
Thank you to everyone who asked questions, filed issues, and contributed!
We recently split Texture
into two types: Texture
and TextureTarget
.
Both have the same fields (pointer, width, height), so what gives?
Backends make two varieties of textures: “Normal” ones that are drawn from, and “Target” ones that are drawn to.
Some backends (web, raylib) treat these interchangeably. But sdl can’t read from a “Normal”, and dx11 can’t draw from a “Target”.
Generally the type you get from the backend is the same, but there is a bit of state associated with it. We could have done the same:
pub const Texture = struct {
is_target: bool,
But then we have to check that field (easy to mess up), and have a way to notify developers if they used the wrong kind in the wrong place.
We are now putting that bit of state into the type:
// this is always "normal"
pub const Texture = struct { ... };
// this is always "target"
pub const TextureTarget = struct { ... };
Now you get a compile error when trying to use the wrong one in the wrong place. Great!
dvui got a bunch of new stuff this month:
Plus a whole ton of minor fixes and enhancements.
We had 6 people contribute this month!
Thank you to everyone who asked questions, filed issues, and contributed!
dvui does not currently have built-in support for double-click, double-tap, or long-tap. My current thinking is that those interaction patterns are generally worse than the alternatives.
The general advantage of these is to have a second way to interact with a widget besides a normal click/tap. Otherwise you need a separate widget, or the widget be modal, or change depending on zoom level, or some other redesign.
The downsides are poor discoverability and accessibility. I’ve personally had the experience of trying to double-click or long-tap something, nothing happens, and I’m left unsure if it doesn’t exist or if I did the interaction wrong.
I’ve watched people with slight hand shakiness try to long-tap repeatedly but only ever get a touch-drag.
This is why dvui’s textLayout and textEntry widgets provide a modal touch interface. A single tap transitions between normal mode and selection mode.
They do have mouse-only 2-click word select and 3-click line select. Those are features that are useful to some while not requiring use by all.
On one hand dvui must serve its users (developers) in providing the UI features they need and want.
On the other hand dvui should serve its users by making it easier to build good UIs than bad ones. Nobody wants a button that by default only responds to a triple-click.
My strategy is to make requested features possible, but use my experience to guide developers by making some features easier to use than others.
We see a similar strategy in Zig design, where the core team uses “friction” to guide developers - some patterns are intentionally left more awkward to use than others to discorage their use while not preventing it outright.
If the parent does not have enough space, child widgets get less than their min size.
What about max_size_content - can a widget get more?
Yes - if that widget is expanded, it can be larger than max_size_content.
min_size_content and max_size_content are both constraints imposed on the minimum size that dvui saves for each widget from frame to frame.
To make it clear that padding/border/margin is added on top of that.
Why not just min_size?
This is an example of naming tension in API design.
When you want a widget to be larger than its minimum size (but not expanded):
.{ .min_size_content = .{ .w = 100, .h = 50 } }
We could make it shorter and easier to remember by using just min_size, but then you have to remember (or lookup) if that already includes padding/border/margin.
So min_size_content costs more characters but makes reading the code (especially for people new to DVUI) slightly easier.
Hard to know which way to go, but I think about stuff like this a lot.