Figuro now has a scrollpane along with vertical and horizontal layouts!
Example:
Scrolling is implemented as a regular widget and is much cleaner way than in Fidgetty. It was pretty straightforward to make. However, it required fixing a bunch of bugs in CSSGrid and the Figuro layout engine. An important improvement has been getting the default layouts to make sense and documenting some of the options. I made a short YouTube video talking about the scrollpane and describing some of the implementation details.
Here's some stuff I've been working on:
Congrats @elcritch! It is great to see continuos progress.
I saw this widgets wanted issue and I was wondering if there is a better explanation about how to make them.
Looking at widget model it is a bit unexplained. Here is mix of notes for myself (and others) and doubts that I have:
It looks like the following pattern is used quite often:
var main = Main.new()
connect(main, doDraw, main, Main.draw())
app.width = 400
app.height = 140
startFiguro(main)
Maybe it could be simplified? I don't know, maybe a concept for a Figuro`with a `draw slot?Hi @mantielero, good question and points. It's useful to have others comment as I don't know what's unclear to others.
Now that the widget model is mostly stable I'll probably start documenting it. It's a bit different from many GUI libraries but it still uses common patterns used in them all.
It is clear that widget needs to inherit from Figuro. But what is mainRect's purpose and why is of type Figuro.
Yes, all widgets must inherit from Figuro or existing subtypes like StatefulFiguro[T]. The UI is made up of Figuro nodes that represent basic UI elements. Figuro nodes have a standard draw slot that will automatically be connected. Widgets can declare their own draw slots which is where their layout is done.
The mainRect is just a leftover while I was figuring things out.
I see that hasHovered will be true when EventKind is Enter. I think some brackets would help the readability to more unexperienced programmers like me.
Sure, I'll make it simpler. Note that I still consider Figuro to be in "development" phase and you'll need to be willing to dig in a bit as there's little documentation or often finished code. :)
Do slots need to have special signature? It looks like the first argument is self: NewWidgetType, but I don't understand for instance kind: EventKind in hover.
Yep, signals and slots are typed. Slots take a Figuro object as the first argument with the rest of the arguments matching the signal you want to connect it to.
What is the purpose of refresh(self)? Is it always needed? If so, could slot pragma take care of that?
The purpose of refresh is for slots to notify the UI frontend to redraw the node passed to it. This will end up calling the draw slot for the given widget. It can't be done automatically since slots could do other things than update the UI.
Note that Figuro is now event driven. Drawing only occurs when refresh is called. Events like hover and click occur before the draw stage. If you don't call refresh then you effectively ignore the event and the widget won't be re-drawn.
Where are rectangle, box, cornerRadius, fill, darken, spin coming from? From pixie? button: it creates 5 buttons. What does captures do?
There are a few default "widgets" like rectangle and text. rectangle is meant for generic rectangles similar to div in HTML. They're just plain Figuro nodes though.
All the APIs that can be called directly like box and cornerRadius are part of Figuro and can be found at figuro/ui/api.nim’.
APIs on colors like darken and spin come from Chroma. Pixie isn't really exposed directly.
Regarding connect, I would state its signature: Agent, signal, Agent, slot. By the way, current and self are not the same?
Good idea. Nope self is a convention for the argument to the draw slot. The current variable is implicit to each widget used in the draw slot.
connect(main, onDraw, main, Main.draw): I understand that Figuro provides the signal onDraw that with connect to the slot draw that we just defined.
The draw doesn't need to be manually connected anymore. I just haven't cleaned them up quite yet.
connect(main, onTick, main, Main.tick): where is onTick defined?
The signals used by Figuro are defined in common/nodes/ui.nim.
The default slots are defined right after them. Any of the slots defined for a widget will automatically be connected using a compile time check. That's how draw and hover are connected in the examples.
Generally custom slots will only be needed for custom logic when using a widget, like in tclick.nim.
It is not clear to me where is the appropriate place to connect. Could those two connection be done before the instantiation?
connect needs to be used on an object instance. Think of them as runtime connected methods but with static types.
Example:
proc draw*(self: Main) {.slot.} =
rectangle "body", parent=self:
with node:
box 10'ux, 10'ux, 600'ux, 120'ux
cornerRadius 10.0
fill whiteColor.darken(self.bkgFade.amount)
horizontal "horiz":
offset node, 10'ux, 0'ux
itemWidth node, cx"min-content", gap = 20'ui
for i in 0 .. 4:
button "btn", captures=i:
size node, 100'ux, 100'ux
connect(node, doHover, self, buttonHover)
var main = Main.new()
let frame = newAppFrame(main, size=(720'ui, 140'ui))
startFiguro(frame)
I think the next big thing I want to get working is dynamic theming using PEG's. Essentially being able to have a text file with list of PEG's to match certain paths along with basic properties like size, offset, color, etc.
Though, I also want to get some basic threaded message passing working so it's easy to spawn tasks to perform things without blocking the main UI thread. The design for this will extend the signal / slot mechanism to provide a multi-thread option. Under the covers use threading/channels and Isolate to ensure thread safety. This would make it possible to actually build apps with Figuro. That'd be nifty!
If you want you can take a look at ThreadButler (https://github.com/PhilippMDoerner/ThreadButler) for the multithreading bits, where I do exactly that.
Not even just "kinda" that, I mean exactly that.
I'm still refining bits and bobs here and there, mostly docs, before I make any alpha announcement, but the code and examples are functional.