figuro icon indicating copy to clipboard operation
figuro copied to clipboard

Porting to Figuro?

Open matkuki opened this issue 11 months ago • 66 comments

Hi @elcritch ,

I have an desktop application, currently using the IUP library, that looks like this: Image 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?

matkuki avatar Jan 22 '25 10:01 matkuki

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.

elcritch avatar Jan 23 '25 00:01 elcritch

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.

elcritch avatar Jan 23 '25 00:01 elcritch

the other is the statusbar

Actually not sure about the status bar. It's something I'd like to add though.

elcritch avatar Jan 23 '25 00:01 elcritch

I re-added setTitle in apis.nim which lets you set the windows title.

elcritch avatar Jan 23 '25 10:01 elcritch

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.

matkuki avatar Jan 23 '25 14:01 matkuki

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 a text node? The setText doesn't seem to do anything.
  • How do I set an Input's text programmatically?

matkuki avatar Feb 02 '25 19:02 matkuki

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 a text node? The setText doesn'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.

elcritch avatar Feb 03 '25 00:02 elcritch

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.

elcritch avatar Feb 03 '25 09:02 elcritch

Thanks for the feedback! It really helps me find these cases of missing APIs.

elcritch avatar Feb 03 '25 09:02 elcritch

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 Input widget, setText still does nothing.
  • text widget has fill to set the font color, Input widget has font. Why?
  • Text alignment for the text and Input and any other text widget should probably be unified.
  • Vertical alignment doesn't seem to work, Input's align Middle and text's vAlign = FontVertical.Middle, everything is still aligned to the top: Image
  • 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 😊🤷‍♂️

matkuki avatar Feb 03 '25 20:02 matkuki

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.

elcritch avatar Feb 03 '25 22:02 elcritch

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")

elcritch avatar Feb 03 '25 22:02 elcritch

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.

👍👍

Image

  • The horizontal text alignment is off between text and Input.
  • cornerRadius 0.0 for the Input has no effect.

matkuki avatar Feb 03 '25 23:02 matkuki

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 👍

matkuki avatar Feb 03 '25 23:02 matkuki

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:

Image

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")

elcritch avatar Feb 04 '25 06:02 elcritch

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.

elcritch avatar Feb 04 '25 06:02 elcritch

You can also use usingVerticalLayout or usingHorizontalLayout in addition to using Vertical.new "verticalBox": ....

elcritch avatar Feb 04 '25 06:02 elcritch

Image

  • The horizontal text alignment is off between text and Input.
  • cornerRadius 0.0 for the Input has 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.

elcritch avatar Feb 04 '25 07:02 elcritch

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. :)

elcritch avatar Feb 04 '25 09:02 elcritch

Pretty fun seeing this coming together!

elcritch avatar Feb 04 '25 09:02 elcritch

Version 0.12.0 is merged! Lots of cleanup and little fixes.

elcritch avatar Feb 04 '25 12:02 elcritch

Confirming that it works (only cornerRadius 10.0 has to be changed to cornerRadius 10.0'ui, I changed it to 0.0'ui): Image 👍👍👍

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.

matkuki avatar Feb 04 '25 13:02 matkuki

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
  ...

elcritch avatar Feb 04 '25 14:02 elcritch

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.

matkuki avatar Feb 06 '25 13:02 matkuki

You'll need to do a 'nimble install -d' too. I switched to DMON.

elcritch avatar Feb 06 '25 13:02 elcritch

Second part of the output above is PS E:\Nim\figuro\shutdown-app\figuro> nimble install -d

matkuki avatar Feb 06 '25 13:02 matkuki

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'?

elcritch avatar Feb 06 '25 13:02 elcritch

Yes, thanks, that fixed it 👍

matkuki avatar Feb 06 '25 13:02 matkuki

Is there a way to override the data directory?

matkuki avatar Feb 06 '25 19:02 matkuki

Can I make an Input widget readonly? Or replace it with a text widget at runtime?

matkuki avatar Feb 06 '25 20:02 matkuki