Porting to Figuro?
Hi @elcritch ,
I have an desktop application, currently using the IUP library, that looks like this:
Would like to port it to
Figuro. Is Figuro currently up to the task of something like this?
It's a simple design: a vertical layout, two textboxes (one for time, the other is the statusbar) and two buttons?
Would like to port it to Figuro. Is Figuro currently up to the task of something like this?
I think it is! Aside from lack of documentation and minor bugs.
This would be a perfect little GUI to try out. If you're up for it I'd add it as a test or example too.
It'd be especially helpful to get feedback on someone else using Figuro. Asking any questions on what's confusing helps me figure out if something can be simplified. Feature requests would be helpful too.
The best starting point would be tests/tclick.nim. It uses Horizontal already. There's a Vertical widget as well. If you want a more sophisticated layout you can checkout tests/tgrid.nim which is built for this sort of dialog. The CSS Grid is more complex at first but lets you do much nicer layouts when you do things like resize.
Lastly you can also see what I've been doing in examples/hnbrowser.nim that I just added. It's very rough still but does load Hackernews stories and displays the titles using threads, which was one of the last key blockers.
Note the CSS will be unstable until I get the new file monitor stuff merged, but I blanked out the themes.css for now. You can try it out but note it'll break after running for a while.
the other is the statusbar
Actually not sure about the status bar. It's something I'd like to add though.
I re-added setTitle in apis.nim which lets you set the windows title.
Thanks, will try it out today 👍👍
As for the statusbar, I don't need it if I can just use a textbox in it's place.
My current draw code:
proc draw*(self: Main) {.slot.} =
var node = self
rectangle "main":
with node:
cssEnable false
box 0'ux, 0'ux, window_size.width, window_size.height
# cornerRadius 0.0
strokeLine 1'ui, css"#000000"
fill css"#ffffff"
text "text":
with node:
cssEnable false
box offset.left, offset.top, window_size.width, window_size.height / 3.0
fill css"#000000"
setText(
{font: "00:00:00"},
hAlign = FontHorizontal.Center,
vAlign = FontVertical.Middle,
)
Input.new "text":
box node,
offset.left,
offset.top + window_size.height / 3.0,
window_size.width,
window_size.height / 3.0
with node:
setText(
{font: "00:00:00"},
hAlign = FontHorizontal.Center,
vAlign = FontVertical.Middle,
)
Questions:
- How do I set an
Input's font, alignment, ... as is done for atextnode? ThesetTextdoesn't seem to do anything. - How do I set an
Input's text programmatically?
Nice, glad you're able to get that far!
One note, you can use 100'pp to get a "percent of parent" width or height. Main shouldn't need a box as nodes default to 0, 0, 100'pp, 100'pp or the windows height for Mains case.
Also there's a flag to prevent window resizing for this sorta app: newAppFrame(fig, size=(500'ui, 140'ui), style = DecoratedFixedSized).
Questions:
- How do I set an
Input's font, alignment, ... as is done for atextnode? ThesetTextdoesn't seem to do anything.- How do I set an
Input's text programmatically?
Great questions, I'm not actually sure. Everything to do that is present in the underlying text api's but likely not exposed in the figuro UI api's.
Stay tuned I'll get those added! Actually your example gives a nice pattern for doing it. Hmmm, those would be needed for CSS too.
Made a new release v0.11.0 that provides APIs in Input to answer your questions. See tinput.nim:
Input.new "input":
box node, 10'ux, 10'ux, 400'ux, 100'ux
align node, Middle
justify node, Center
font node, UiFont(typefaceId: defaultTypeface, size: 20'ui)
font node, css"darkred"
fill node, css"grey"
Note you'll need to do a nimble install -d to update Sigils.
Thanks for the feedback! It really helps me find these cases of missing APIs.
My updated code:
proc draw*(self: Main) {.slot.} =
var node = self
# Time display background
rectangle "time-background":
with node:
box offset.left, offset.top, window_size.width, window_size.height / 3.0
fill css"#ffffff"
border 1'ui, css"#000000"
# Time display text
text "time":
with node:
box offset.left, offset.top, window_size.width, window_size.height / 3.0
fill css"red"
border 1'ui, css"#000000"
setText(
{defaultFont: "00:00:00"},
hAlign = FontHorizontal.Center,
vAlign = FontVertical.Middle,
)
# Test text input
Input.new "input":
with node:
box offset.left,
offset.top + (window_size.height / 3.0),
window_size.width,
window_size.height / 3.0
align Middle
justify Center
font defaultFont
font css"#000000"
fill css"#ffffff"
border 1'ui, css"#000000"
cornerRadius 0.0
setText(
{defaultFont: "00:00:00"},
hAlign = FontHorizontal.Center,
vAlign = FontVertical.Middle,
)
Observations:
- Don't know how to set the background color for
Main. - Don't know how to set the text for the
Inputwidget,setTextstill does nothing. textwidget hasfillto set the font color,Inputwidget hasfont. Why?- Text alignment for the
textandInputand any other text widget should probably be unified. - Vertical alignment doesn't seem to work,
Input'salign Middleandtext'svAlign = FontVertical.Middle, everything is still aligned to the top: - I'm using
with node:in every widget. I have no idea why I have to and what is the relathionship between a widget andnode😊🤷♂️
I'm using with node: in every widget. I have no idea why I have to and what is the relathionship between a widget and node 😊🤷♂️
A widget is just a custom node and it's children. I updated the readme recently to have a short bit on that. Let me know if there's something I can add to help clarify it.
It's sorta similar to HTML style components, which is a bit different than most desktop UI's so it's not clear the best way to describe it.
Each new widget block made with SomeWidget.new "foo": adds an implicit node variable referencing the current node/widget. I chose node rather than widget or current for the implicit variable for each node/widget. Though I'm not too partial to any naming. widget or current might be clearer. Hmmm, maybe this? It's shorter but still gives the idea of "current widget I'm working with".
It might make sense to drop the node terminology for the user facing API. To me internally a widget and node are synonymous, but to a user it's just a widget or figuro element.
Hmmm, currently with node: is required because I moved the APIs to be procs that take the node to modify. Eventually the API will be compiled and linked to to reduce compile time for big projects.
Though I'm planning to add back templates that use the implicit node variable like Fidget did.
Essentially that'd just be:
proc fill*(node: Figuro, c: Color) = ...
template fill*(c: Color) = fill(node, c)
Button.new "btn": # injects `node`
fill css"blue"
Don't know how to set the background color for Main.
It should be just setting fill self, css"ref" but looks like it's not working. I'll have to fix the renderer or just default the main widget to fill with a rectangle automatically. The reason is the root node is a bit special, partly due to the possibility of zlevels needed for menus and such. It's size is also overridden to be the window's current size.
For now just make a rectangle with size 100'pp, 100'pp which will fill it to the windows size. Put everything in that rectangle.
Text alignment for the text and Input and any other text widget should probably be unified.
Yeah good point. I kinda like the split align Middle, justify Left, and text "hello world" over passing them as arguments to setText. Plus it'd match with CSS settings a bit better than the setText proc.
Don't know how to set the text for the Input widget, setText still does nothing.
Ah I looked at your code. I was trying to override setText from the text node in Input, but looks like since you used the normal setText api it just used that one. It'll work if you use just setText(node, "text") without the fonts or aligns.
In my next release branch I've changed setText to just text with aligns in separate setters. Think I'll modify Text to work the same and keep setText as the under the cover APIs.
Note, I haven't figured out the API to "intercept" the text after the user inputs new characters. I may need to add an doTextChanged event.
You can use something like if text() != timerText: ... pattern for now though.
text widget has fill to set the font color, Input widget has font. Why?
Input has a background rectangle node and a text node. I'm not sure a good api for setting both. Though Input could just be like Text and only fill in the text and not provide a background color.
Vertical alignment doesn't seem to work, Input's align Middle and text's vAlign = FontVertical.Middle, everything is still aligned to the top:
Ah, there's an alignment issue where the text starts offset higher than it should be by about half a font height when centered. Make it taller you'll see it centers like in tinput.nim.
It's annoying but I was working on other stuff until recently and so ignored it. I'll dig into it though adding an offset to fix centered text is annoying. It's probably just a matter of adjusting centered text down by half a font height due to interplay between Pixie and Figuro's layouts.
With changes your draw would look something like this. Ohhh, I think I do like this better. Even with this: feels less clunky.
What do you think?
proc draw*(self: Main) {.slot.} =
withRootWidget(self):
assert self == this # this is just a convention the apis rely on
fill this, css"blue"
box this, 0'ux, 0'ux, 100'pp, 100'pp
# Time display background
rectangle "time-background":
with this:
box 10'ux, 10'ux, 100'pp, 33'pp
fill css"#ffffff"
border 1'ui, css"#000000"
# Time display text
text "time":
with this:
box 10'ux, 10'ux, 100'pp, 33'pp
fill css"red"
border 1'ui, css"#000000"
align FontHorizontal.Center
justify FontVertical.Middle
font defaultFont
if this.text() == "":
# set default
text("00:00:00")
# Test text input
Input.new "input":
with this:
box 10'ux, 40'ux, 100'pp, 33'pp
align Middle
justify Center
font defaultFont
font css"#000000"
fill css"#ffffff"
border 1'ui, css"#000000"
cornerRadius 0.0
text("00:00:00")
Thanks for the node explanation 👍
Don't know how to set the background color for Main.
... For now just make a rectangle with size 100'pp, 100'pp which will fill it to the windows size. Put everything in that rectangle.
That's a good workaround 👍👍
Text alignment for the text and Input and any other text widget should probably be unified.
Yeah good point. I kinda like the split align Middle, justify Left, and text "hello world" over passing them as arguments to setText. ...
Yes, agreed. I like it too.
text widget has fill to set the font color, Input widget has font. Why?
Input has a background rectangle node and a text node. I'm not sure a good api for setting both. Though Input could just be like Text and only fill in the text and not provide a background color.
I think it they should be the same, yes, whichever way you choose: background or no-background.
Note, I haven't figured out the API to "intercept" the text after the user inputs new characters. I may need to add an doTextChanged event.
👍👍
- The horizontal text alignment is off between
textandInput. cornerRadius 0.0for theInputhas no effect.
With changes your draw would look something like this. Ohhh, I think I do like this better. Even with this: feels less clunky.
I agree, seems more intuitive 👍
Sweet, the text apis are starting to feel a lot better now. Thanks!
https://github.com/elcritch/figuro/pull/82
I got your example looking like this:
Here's the code changes. How's this?
proc draw*(self: Main) {.slot.} =
withRootWidget(self):
fill this, css"blue"
box this, 0'ux, 0'ux, 100'pp, 100'pp
# Time display background
rectangle "time-background":
with this:
box 20'pp, 10'pp, 60'pp, 80'pp
fill css"#ffffff"
border 1'ui, css"#000000"
usingVerticalLayout cx"min-content", gap = 20'ui
# Time display text
Text.new "time":
with this:
size 40'pp, 50'ux
fill css"white"
foreground css"red"
border 1'ui, css"#000000"
cornerRadius 10.0
justify FontHorizontal.Center
align FontVertical.Middle
font defaultFont
if this.textChanged(""):
# set default
this.text("00:00:00")
# Test text input
Input.new "input":
with this:
size 40'pp, 50'ux
align Middle
justify Center
font defaultFont
foreground css"black"
fill css"white"
border 1'ui, css"black"
cornerRadius 10.0
if not this.textChanged(""):
# set default
echo "SET DEFAULT TEXT"
text(this, "00:00:00")
Similar to the withWidget in widget draw methods, I added a withRootWidget for "root" widgets. Pretty similar but just adds that parent rectangle I mentioned before.
You can also use usingVerticalLayout or usingHorizontalLayout in addition to using Vertical.new "verticalBox": ....
- The horizontal text alignment is off between
textandInput.cornerRadius 0.0for theInputhas no effect.
Ohhh! Figured out the offset lol. They're essentially identical, use the same text layout, etc.
Except for one thing: text input has a hidden character (rune) at the end for the selection logic. Looks like that's added to the layout though... Hmmm how to solve.
Ohhh! Figured out the offset lol. They're essentially identical, use the same text layout, etc.
Except for one thing: text input has a hidden character (rune) at the end for the selection logic. Looks like that's added to the layout though... Hmmm how to solve.
Got it! Also sovled the vertical centering on the first line for text. :)
Pretty fun seeing this coming together!
Version 0.12.0 is merged! Lots of cleanup and little fixes.
Confirming that it works (only cornerRadius 10.0 has to be changed to cornerRadius 10.0'ui, I changed it to 0.0'ui):
👍👍👍
New observations:
withRootWidget(self):is awesome 👍with this:is awesome 👍- The "Delete" key doesn't work in the
Input, only backspace for deleting characters. - Is it possible to have an indentation after
usingVerticalLayout ...? I like to also visually nest things that go together. It's not a necessity, but would be nice to have.
Sweet!
New observations:
withRootWidget(self):is awesome 👍with this:is awesome 👍
Excellent!
- The "Delete" key doesn't work in the
Input, only backspace for deleting characters.
Hmmm, bummer. Think you could file a separate issue for that? Also need to finish some alt- key combos too.
- Is it possible to have an indentation after
usingVerticalLayout ...? I like to also visually nest things that go together. It's not a necessity, but would be nice to have.
Definitely, just use:
Vertical.new "vlayout":
contentHeight this, cx"auto", gap = 20'ui
...
I did an update and get this now:
E:\Nim\figuro\shutdown-app> .\shutdown.exe
app frame
NOT Start AppTicker tid=21400 period="14 milliseconds"
DBG CSS theme into app tid=29312 numberOfCssRules=0
NOT Starting CSS Watcher tid=17888
SET DEFAULT TEXT
C:\Users\matic\.nimble\pkgs2\sigils-0.11.6-ccc177eaecd3510a887e888c4aed38796d043a42\sigils\threadBas
e.nim(219) runThread
C:\Users\matic\.nimble\pkgs2\sigils-0.11.6-ccc177eaecd3510a887e888c4aed38796d043a42\sigils\threadBas
e.nim(209) runForever
C:\Users\matic\.nimble\pkgs2\sigils-0.11.6-ccc177eaecd3510a887e888c4aed38796d043a42\sigils\threadBas
e.nim(192) poll
C:\Users\matic\.nimble\pkgs2\sigils-0.11.6-ccc177eaecd3510a887e888c4aed38796d043a42\sigils\threadBas
e.nim(183) exec
C:\Users\matic\.nimble\pkgs2\sigils-0.11.6-ccc177eaecd3510a887e888c4aed38796d043a42\sigils\core.nim(
10) callMethod
C:\Users\matic\.nimble\pkgs2\sigils-0.11.6-ccc177eaecd3510a887e888c4aed38796d043a42\sigils\core.nim(
20) callMethod
C:\Users\matic\.nimble\pkgs2\sigils-0.11.6-ccc177eaecd3510a887e888c4aed38796d043a42\sigils\slots.nim
(218) slot
E:\Nim\figuro\shutdown-app\figuro\src\figuro\runtime\utils\cssMonitor.nim(46) cssWatcher
C:\Users\matic\.nimble\pkgs2\dmon-0.3.3-9366fd975efc5621af70d979edb31fe42aa12b4b\dmon\dmon_windows.n
im(105) watch
C:\Users\matic\.choosenim\toolchains\nim-#devel\lib\system\fatal.nim(53) sysFatal
Error: unhandled exception: index out of bounds: 0..64512 notin 0..64511 [IndexDefect]
PS E:\Nim\figuro\shutdown-app> cd figuro
PS E:\Nim\figuro\shutdown-app\figuro> nimble install -d
Downloading https://github.com/elcritch/sigils using git
Downloading https://github.com/elcritch/dmon-nim using git
Tip: 37 messages have been suppressed, use --verbose to show them.
E:\Nim\nimble\source\src\nimble.nim(2556) nimble
E:\Nim\nimble\source\src\nimble.nim(2343) doAction
E:\Nim\nimble\source\src\nimble.nim(783) install
E:\Nim\nimble\source\src\nimble.nim(504) installFromDir
E:\Nim\nimble\source\src\nimble.nim(179) processFreeDependencies
E:\Nim\nimble\source\src\nimble.nim(128) processFreeDependenciesSAT
E:\Nim\nimble\source\src\nimblepkg\nimblesat.nim(696) solvePackages
E:\Nim\nimble\source\src\nimblepkg\nimblesat.nim(633) collectAllVersions
E:\Nim\nimble\source\src\nimblepkg\nimblesat.nim(627) processRequirements
E:\Nim\nimble\source\src\nimblepkg\nimblesat.nim(616) processRequirements
E:\Nim\nimble\source\src\nimblepkg\nimblesat.nim(607) getMinimalFromPreferred
E:\Nim\nimble\source\src\nimblepkg\nimblesat.nim(588) downloadMinimalPackage
E:\Nim\nimble\source\src\nimblepkg\nimblesat.nim(483) downloadPkgFromUrl
E:\Nim\nimble\source\src\nimblepkg\download.nim(636) getDownloadInfo
Error: Package macosutils@>= 0.3.2 not found.
You'll need to do a 'nimble install -d' too. I switched to DMON.
Second part of the output above is PS E:\Nim\figuro\shutdown-app\figuro> nimble install -d
Ah I missed that on my phone. Okay I'll have to look into it. The macosutils looks to have version 0.3.2 tagged.
Oh I added macosutils to the nimble directory recently. Maybe a 'nimble refresh'?
Yes, thanks, that fixed it 👍
Is there a way to override the data directory?
Can I make an Input widget readonly? Or replace it with a text widget at runtime?