From 71ba1c811a833bb7ddfa2b8ec9b58bb3f6498389 Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Tue, 11 Jul 2017 20:18:20 +0200 Subject: [PATCH 1/6] Implemented genui macro for NiGui --- examples/example_92_genui_macro.nim | 20 ++++ genui.md | 81 +++++++++++++++ src/genui/genui.nim | 147 ++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 examples/example_92_genui_macro.nim create mode 100644 genui.md create mode 100644 src/genui/genui.nim diff --git a/examples/example_92_genui_macro.nim b/examples/example_92_genui_macro.nim new file mode 100644 index 0000000..538d57a --- /dev/null +++ b/examples/example_92_genui_macro.nim @@ -0,0 +1,20 @@ +import nigui, genui +import tables + +var buttons = initTable[string, Button]() + +proc clickHandler(event: ClickEvent) = + echo "Clicked" + +app.init() + +genui: + Window[width = 800, height = 600, show]: + LayoutContainer(Layout_vertical): + {buttons["button_1"] = @result}Button("Hello world") + {buttons["button_2"] = @result}Button("Second button") + [width = 800, height = 800, show]{var test = @result}("Test")Window: + Button + +app.run() + diff --git a/genui.md b/genui.md new file mode 100644 index 0000000..2fec05d --- /dev/null +++ b/genui.md @@ -0,0 +1,81 @@ +# Genui +This module provides the genui macro for the NiGui toolkit. Genui is a way to specify graphical interfaces in a hierarchical way to more clearly show the structure of the interface as well as simplifying the code. Genui is currently implemented for wxWidgets, libui, and nigui. The format focuses on being a soft conversion meaning that there are few to no assumptions and most code can be seen as a 1:1 conversion. This makes it easy to look at existing examples for your framework of choice when creating interfaces in genui. Because of this the genui format differs a bit from framework to framework, but aims to bring many of the same features. What follows is the genui format as used with nigui. + +## Creating widgets +The most basic operation is to create widgets and add them together in a hierarchy. NiGui uses a very simple style of `newButton` to create a widget of type Button and `parent.add child` to add a child to a parent. In genui this translates to: + +``` +Window: + LayoutContainer: + Button + Button +``` + +The above snippet should create a window with a layout container containing two buttons. Although there is one problem, the procedure newLayoutContainer takes a parameter dictating the direction of the layout. + +## Passing initialiser parameters +In order to pass parameters to an initialiser you simply enclose them in regular "()" brackets. Genui uses brackets to denote the various things you can do, and the order of the bracketed expressions doesn't matter. So to pass a `Layout` to the `LayoutContainer` simply do: + +``` +Window: + LayoutContainer(Layout_vertical): + Button + Button +``` + +To put text on the buttons you would similarily use `Button("Hello World")`. But now we're faced with a new challenge. NiGui requires us to call a `show` procedure on our window, but the window isn't assigned to a variable we can use. + +## Calling procedures +Many configuration options in genui requires this pattern of creating a widget and assigning values to it's fields or calling it's procedures. To avoid having to assign variable names to all your widgets just for configuration genui offers a format to create so-called dot-expressions. This uses the "[]" brackets and all statements in there will have a dot and the temporary variable name prepended to them. So to call the `show` procedure and set `width` and `height` of the window we simply do: + +``` +Window[width = 800, height = 600, show]: + LayoutContainer(Layout_vertical): + Button + Button +``` + +Now our window shows up with two empty buttons one below the other. But user interfaces aren't always static so we need to be able to assign variable names to out widgets. + +## Running code +Previous version of genui (for wxwidgets and libui) used a % notation in which an identifier could be assigned to the widget for later use. The % symbol was chosen as the assignment didn't directly convert to the Nimassignment as to avoid confusion. But this format proved a bit weird, and for data structures like a list or a table you would need to create these variables simply to use them once, something genui was created to avoid. + +So this version of genui introduces a new concept. It's still a bit of a work in progress but it shows promise. By using the "{}" brackets arbitrary code can be executed. In these blocks the special symbol `@result` can be used, and will be replaced by the temporary variable name for the widget. This means that anything from simple assignment to adding to complex data structures is possible. So for example adding our two buttons to a table of buttons would be: + +``` +Window[width = 800, height = 600, show]: + LayoutContainer(Layout_vertical): + {buttons["button_1"] = @result} Button + {buttons["button_2"] = @result} Button +``` + +## A note on order +As mentioned in the section about initialisation parameters the order of the brackets doesn't matter. So if you want to place the "{}" brackets on the end of your line, or if you want to put the "()" before the Widget name doesn't matter. But as an "official" suggestion I typically use this order: + +``` +{var myButton = @result} Button("Hello World!")[onClick = clickHandler] +``` + +The exception to this would be for code snippets which can tend to push the widget name too far along the line for readability. In that case they go in the back. + +## Adding elements to a widget +Sometimes you want to add widgets to a parent to indicate some change of state in your program. In order to facilitate this genui also comes with the procedure `addElements` which takes a container and genui formatted code like this: + +``` +myExistingContainer.addElements: + Layout(Layout_vertical): + Button + Button +``` + +# Quick reference +Don't care about the details? Here is a quick reference to the genui format: + +| Bracket | Function | Example | Generates | +|---------|---------------------------|------------------------------|------------------------------------| +| `()` | Initialisation parameters | `Button("Hello World")` | `newButton("Hello World")` | +| `[]` | Dot-expressions | `Window[height = 300, show]` | `window.height = 300; window.show` | +| `{}` | Pure code insertion | `{var b = @result} Button` | `var b = newButton()` | + +`genui` creates new code, addElements creates the same code but with `add` statements for top-level widgets. + diff --git a/src/genui/genui.nim b/src/genui/genui.nim new file mode 100644 index 0000000..196918e --- /dev/null +++ b/src/genui/genui.nim @@ -0,0 +1,147 @@ +import macros, deques + +proc `[]`(s: NimNode, x: Slice[int]): seq[NimNode] = + ## slice operation for NimNodes. + var a = x.a + var L = x.b - a + 1 + newSeq(result, L) + for i in 0.. Date: Wed, 12 Jul 2017 10:42:29 +0200 Subject: [PATCH 2/6] Moved the genui code into it's proper location after the folder restructure. Also added it as an include in nigui.nim, might not be wanted but not sure how to make it require an explicit include from it's new position. --- examples/example_92_genui_macro.nim | 3 ++- src/nigui.nim | 1 + src/{ => nigui/private}/genui/genui.nim | 0 3 files changed, 3 insertions(+), 1 deletion(-) rename src/{ => nigui/private}/genui/genui.nim (100%) diff --git a/examples/example_92_genui_macro.nim b/examples/example_92_genui_macro.nim index 538d57a..b07abf5 100644 --- a/examples/example_92_genui_macro.nim +++ b/examples/example_92_genui_macro.nim @@ -1,4 +1,4 @@ -import nigui, genui +import nigui#, genui import tables var buttons = initTable[string, Button]() @@ -8,6 +8,7 @@ proc clickHandler(event: ClickEvent) = app.init() +## TODO: Write a more functional and interesting example, maybe copy the wxNim genui threads example? genui: Window[width = 800, height = 600, show]: LayoutContainer(Layout_vertical): diff --git a/src/nigui.nim b/src/nigui.nim index 4ecdebd..6e0b3ff 100755 --- a/src/nigui.nim +++ b/src/nigui.nim @@ -2252,3 +2252,4 @@ method `wrap=`(textArea: TextArea, wrap: bool) = when useWindows(): include "nigui/private/windows/platform_impl" when useGtk(): include "nigui/private/gtk3/platform_impl" +include "nigui/private/genui/genui" diff --git a/src/genui/genui.nim b/src/nigui/private/genui/genui.nim similarity index 100% rename from src/genui/genui.nim rename to src/nigui/private/genui/genui.nim From 0ddf2c68d5712ffbfc28cd4d48e020902ce4042e Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Wed, 12 Jul 2017 20:08:46 +0200 Subject: [PATCH 3/6] Added possibility to pass pureCode as a string to allow any code as a temporary workaround. --- examples/example_92_genui_macro.nim | 2 +- src/nigui/private/genui/genui.nim | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/example_92_genui_macro.nim b/examples/example_92_genui_macro.nim index b07abf5..6c9089b 100644 --- a/examples/example_92_genui_macro.nim +++ b/examples/example_92_genui_macro.nim @@ -14,7 +14,7 @@ genui: LayoutContainer(Layout_vertical): {buttons["button_1"] = @result}Button("Hello world") {buttons["button_2"] = @result}Button("Second button") - [width = 800, height = 800, show]{var test = @result}("Test")Window: + [width = 800, height = 800, show]{"var exported* = @result"}("Test")Window: Button app.run() diff --git a/src/nigui/private/genui/genui.nim b/src/nigui/private/genui/genui.nim index 196918e..71386fa 100644 --- a/src/nigui/private/genui/genui.nim +++ b/src/nigui/private/genui/genui.nim @@ -115,7 +115,10 @@ proc createWidget(widget: ParsedWidget, parent: NimNode = nil): NimNode = let callExpr = call.parseExpr discard replacePlaceholder(callExpr) result.add callExpr + if widget.pureCode != nil: + if widget.pureCode.kind == nnkStrLit: + widget.pureCode = widget.pureCode.strVal.parseExpr widget.pureCode = widget.pureCode.repr.parseExpr discard replacePlaceholder(widget.pureCode) result.add(widget.pureCode) From df5bf67b10f1a7da0a2285ca1c050cf31a6e9f88 Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Wed, 12 Jul 2017 20:19:17 +0200 Subject: [PATCH 4/6] Added mention of string literals for the {}-brackets --- genui.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/genui.md b/genui.md index 2fec05d..9f01f54 100644 --- a/genui.md +++ b/genui.md @@ -49,6 +49,16 @@ Window[width = 800, height = 600, show]: {buttons["button_2"] = @result} Button ``` +As mentioned this is still a bit of a work in progress and not all code works. As a workaround for this you can wrap codde that doesn't work in a string. So for example exported symbols (which doesn't work when used normally) could be used like this: + +``` +{"var exported* = @result"} Window[width = 800, height = 600, show]: + LayoutContainer(Layout_vertical): + {buttons["button_1"] = @result} Button + {buttons["button_2"] = @result} Button +``` + + ## A note on order As mentioned in the section about initialisation parameters the order of the brackets doesn't matter. So if you want to place the "{}" brackets on the end of your line, or if you want to put the "()" before the Widget name doesn't matter. But as an "official" suggestion I typically use this order: @@ -77,5 +87,5 @@ Don't care about the details? Here is a quick reference to the genui format: | `[]` | Dot-expressions | `Window[height = 300, show]` | `window.height = 300; window.show` | | `{}` | Pure code insertion | `{var b = @result} Button` | `var b = newButton()` | -`genui` creates new code, addElements creates the same code but with `add` statements for top-level widgets. +`genui` creates new code, addElements creates the same code but with `add` statements for top-level widgets. `{}` is still a work in progress, code that doesn't parse in it can be added as a string instead. From 8b1332ea1e08ed42eecfb54c4dd85e536127c0c9 Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Wed, 12 Jul 2017 20:32:11 +0200 Subject: [PATCH 5/6] Added mention of better workaround with parenthesis around code statements --- examples/example_92_genui_macro.nim | 2 +- genui.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/example_92_genui_macro.nim b/examples/example_92_genui_macro.nim index 6c9089b..52b8b92 100644 --- a/examples/example_92_genui_macro.nim +++ b/examples/example_92_genui_macro.nim @@ -14,7 +14,7 @@ genui: LayoutContainer(Layout_vertical): {buttons["button_1"] = @result}Button("Hello world") {buttons["button_2"] = @result}Button("Second button") - [width = 800, height = 800, show]{"var exported* = @result"}("Test")Window: + [width = 800, height = 800, show]{(var exported* = @result)}("Test")Window: Button app.run() diff --git a/genui.md b/genui.md index 9f01f54..eff1a60 100644 --- a/genui.md +++ b/genui.md @@ -49,13 +49,13 @@ Window[width = 800, height = 600, show]: {buttons["button_2"] = @result} Button ``` -As mentioned this is still a bit of a work in progress and not all code works. As a workaround for this you can wrap codde that doesn't work in a string. So for example exported symbols (which doesn't work when used normally) could be used like this: +As mentioned this is still a bit of a work in progress and not all code works, this has to do with how Nim parses curly brackets. There are two workarounds for this, the simplest is to add regular parenthesis around your code (which Nim silently ignores when converting to code). Or, should that not work either you can wrap code in a string. So converting the above code statements to these two workaround would look like this: ``` -{"var exported* = @result"} Window[width = 800, height = 600, show]: +Window[width = 800, height = 600, show]: LayoutContainer(Layout_vertical): - {buttons["button_1"] = @result} Button - {buttons["button_2"] = @result} Button + {(buttons["button_1"] = @result)} Button + {"buttons[\"button_2\"] = @result"} Button ``` From 16540c23040fde20913a2941eed8bb56e235779c Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Wed, 12 Jul 2017 20:55:45 +0200 Subject: [PATCH 6/6] Added shorthand @r for @result --- examples/example_92_genui_macro.nim | 2 +- genui.md | 6 +++--- src/nigui/private/genui/genui.nim | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/example_92_genui_macro.nim b/examples/example_92_genui_macro.nim index 52b8b92..7002421 100644 --- a/examples/example_92_genui_macro.nim +++ b/examples/example_92_genui_macro.nim @@ -12,7 +12,7 @@ app.init() genui: Window[width = 800, height = 600, show]: LayoutContainer(Layout_vertical): - {buttons["button_1"] = @result}Button("Hello world") + {buttons["button_1"] = @r}Button("Hello world") {buttons["button_2"] = @result}Button("Second button") [width = 800, height = 800, show]{(var exported* = @result)}("Test")Window: Button diff --git a/genui.md b/genui.md index eff1a60..42d3bbd 100644 --- a/genui.md +++ b/genui.md @@ -40,13 +40,13 @@ Now our window shows up with two empty buttons one below the other. But user int ## Running code Previous version of genui (for wxwidgets and libui) used a % notation in which an identifier could be assigned to the widget for later use. The % symbol was chosen as the assignment didn't directly convert to the Nimassignment as to avoid confusion. But this format proved a bit weird, and for data structures like a list or a table you would need to create these variables simply to use them once, something genui was created to avoid. -So this version of genui introduces a new concept. It's still a bit of a work in progress but it shows promise. By using the "{}" brackets arbitrary code can be executed. In these blocks the special symbol `@result` can be used, and will be replaced by the temporary variable name for the widget. This means that anything from simple assignment to adding to complex data structures is possible. So for example adding our two buttons to a table of buttons would be: +So this version of genui introduces a new concept. It's still a bit of a work in progress but it shows promise. By using the "{}" brackets arbitrary code can be executed. In these blocks the special symbol `@result` can be used, and will be replaced by the temporary variable name for the widget (a shorthand `@r` also exists as `@result` can get a bit terse). This means that anything from simple assignment to adding to complex data structures is possible. So for example adding our two buttons to a table of buttons would be: ``` Window[width = 800, height = 600, show]: LayoutContainer(Layout_vertical): {buttons["button_1"] = @result} Button - {buttons["button_2"] = @result} Button + {buttons["button_2"] = @r} Button ``` As mentioned this is still a bit of a work in progress and not all code works, this has to do with how Nim parses curly brackets. There are two workarounds for this, the simplest is to add regular parenthesis around your code (which Nim silently ignores when converting to code). Or, should that not work either you can wrap code in a string. So converting the above code statements to these two workaround would look like this: @@ -55,7 +55,7 @@ As mentioned this is still a bit of a work in progress and not all code works, t Window[width = 800, height = 600, show]: LayoutContainer(Layout_vertical): {(buttons["button_1"] = @result)} Button - {"buttons[\"button_2\"] = @result"} Button + {"buttons[\"button_2\"] = @r"} Button ``` diff --git a/src/nigui/private/genui/genui.nim b/src/nigui/private/genui/genui.nim index 71386fa..ea95a44 100644 --- a/src/nigui/private/genui/genui.nim +++ b/src/nigui/private/genui/genui.nim @@ -102,7 +102,7 @@ proc createWidget(widget: ParsedWidget, parent: NimNode = nil): NimNode = for i in 0 .. n.high: let child = n[i] if child.kind == nnkPrefix and child[0].kind == nnkIdent and child[1].kind == nnkIdent and - child[0].ident == !"@" and child[1].ident == !"result": + child[0].ident == !"@" and (child[1].ident == !"result" or child[1].ident == !"r"): n[i] = widget.generatedSym return true let done = child.replacePlaceholder()