I've got what feels like a minimal but useable widget API around Fidget. While Fidget is fantastic for one off UI's it didn't really support pre-made widgets. So I added it. :)
There's support for both stateful and non-stateful widgets. Stateful widgets are divided into either `appWidget`s or `statefulWidget`s. App widgets have explicit state and are best used for encapsulating application state. Generally these will be near the top level, with the state object stored in a top level proc or as a global. Stateful widgets support all the other UI widgets like `dropdown` which require some internal state like whether the dropdown is open or not. The main difference is that the internal state can be more transitory. Here's a basic app widget demo: import button import progressBar loadFont("IBM Plex Sans", "IBMPlexSans-Regular.ttf") proc exampleApp*( myName {.property: name.}: string, ) {.appWidget.} = ## defines a stateful app widget ## ## `exampleApp` will be transformed to also take the basic ## widget parameters: `self: ExampleApp = nil; setup: proc() = nil; post: proc() = nil` properties: ## this creates a new ref object type name using the ## capitalized proc name which is `ExampleApp` in this example. ## This will be customizable in the future. count: int value: UnitRange frame "main": setTitle(fmt"Fidget Animated Progress Example - {myName}") group "center": box 50, 0, 100.Vw - 100, 100.Vh orgBox 50, 0, 100.Vw, 100.Vw # Use progress bar widget self.value = (self.count.toFloat * 0.10) mod 1.0 progressBar(self.value) do: # this is progress bar's setup proc -- used to set box size, override colors, etc box 10.WPerc, 20, 80.WPerc, 2.Em Horizontal: # creates an horizontal spacing box box 90.WPerc - 16.Em, 100, 8.Em, 2.Em itemSpacing 0.Em # Click to make the bar increase # basic syntax just calling a proc if button(fmt"Clicked1: {self.count:4d}"): self.count.inc() # Alternate format using `Widget` macro that enables # a YAML like syntax using property labels # (see parameters on `button` widget proc) Widget button: text: fmt"Clicked2: {self.count:4d}" onClick: self.count.inc() # current limit on Widget macros is that all args # must be called as properties, no mix and match # # i.e. this doesn't work (yet): # Widget button(fmt"Clicked2: {self.count:4d}"): # onClick: self.count.inc() Vertical: # creates a vertical spacing box box 10.WPerc, 160, 8.Em, 2.Em itemSpacing 1.Em Button: # default alias of `Widget button` # only created for non-stateful widgets text: fmt"Clicked3: {self.count:4d}" setup: size 8.Em, 2.Em onClick: self.count.inc() Widget button: text: fmt"Clicked4: {self.count:4d}" setup: size 8.Em, 2.Em onClick: self.count.inc() var state = ExampleApp(count: 0, value: 0.33) const callform {.intdefine.} = 2 proc drawMain() = frame "main": when callform == 1: # we call exampleApp with a pre-made state # the `statefulWidget` always takes a `self` paramter # that that widgets state reference exampleApp("basic widgets", state) elif callform == 2: Widget exampleApp: name: "basic widgets" self: state startFidget(drawMain, uiScale=2.0) Run It's been edited down from the full version you can find at: <https://github.com/elcritch/fidget/blob/devel/tests/widgets/basicwidget.nim> There's a demo for widgets with **internal** state. This is the trickiest part. It's possible to use standard proc's and have the end user manually create the state object for widgets, but that becomes tedious pretty quickly. I also wanted to avoid a more object oriented design. The approach I've taken is to have widgets create ref object's that can be stored in the Fidget node tree. Alternatively, the ref object can be passed in as a parameter. This allows user control of the **hidden state** when desired. See progress bar widget example here: <https://github.com/elcritch/fidget/blob/160d00f6155a3aa0846e9d3e1259f157413c97b3/tests/widgets/dropdown.nim#L128> dropdown(dropItems, dropIndexes[0], dstate) dropdown(dropItems, dropIndexes[1], nil) text "desc": size 100.WPerc, 1.Em fill "#000d00" characters "linked dropdowns: " dropdown(dropItems, dropIndexes[2]) Widget dropdown: items: dropItems dropSelected: dropIndexes[2] setup: box 0, 0, 12.Em, 2.Em Run