From 6c5d6cad590bf6de6238c1ebcb8dd8dbc6254d2c Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Sun, 26 Feb 2017 03:10:55 +0100 Subject: [PATCH 1/5] Changed some c -> child for consistency Added onselected methods and fields for RadioButtons --- ui.nim | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/ui.nim b/ui.nim index df8de97..12ff035 100644 --- a/ui.nim +++ b/ui.nim @@ -217,13 +217,13 @@ type impl*: ptr rawui.Tab children*: seq[Widget] -proc add*[SomeWidget: Widget](t: Tab; name: string; c: SomeWidget) = - tabAppend t.impl, name, c.impl - t.children.add c +proc add*[SomeWidget: Widget](t: Tab; name: string; child: SomeWidget) = + tabAppend t.impl, name, child.impl + t.children.add child -proc insertAt*[SomeWidget: Widget](t: Tab; name: string; at: int; c: SomeWidget) = - tabInsertAt(t.impl, name, at.uint64, c.impl) - t.children.insert(c, at) +proc insertAt*[SomeWidget: Widget](t: Tab; name: string; at: int; child: SomeWidget) = + tabInsertAt(t.impl, name, at.uint64, child.impl) + t.children.insert(child, at) proc delete*(t: Tab; index: int) = tabDelete(t.impl, index.cint) @@ -249,9 +249,9 @@ type proc title*(g: Group): string = $groupTitle(g.impl) proc `title=`*(g: Group; title: string) = groupSetTitle(g.impl, title) -proc `child=`*[SomeWidget: Widget](g: Group; c: SomeWidget) = - groupSetChild(g.impl, c.impl) - g.child = c +proc `child=`*[SomeWidget: Widget](g: Group; child: SomeWidget) = + groupSetChild(g.impl, child.impl) + g.child = child proc margined*(g: Group): bool = groupMargined(g.impl) != 0 proc `margined=`*(g: Group; x: bool) = groupSetMargined(g.impl, x.cint) @@ -306,6 +306,8 @@ type proc `value=`*(p: ProgressBar; n: int) = progressBarSetValue p.impl, n.cint +proc value*(p: Progressbar; n:int): int = progressBarValue(p.impl) + proc newProgressBar*(): ProgressBar = newFinal result result.impl = rawui.newProgressBar() @@ -370,12 +372,20 @@ proc newEditableCombobox*(onchanged: proc () = nil): EditableCombobox = type RadioButtons* = ref object of Widget impl*: ptr rawui.RadioButtons + onselected*: proc () proc add*(r: RadioButtons; text: string) = radioButtonsAppend(r.impl, text) -proc newRadioButtons*(): RadioButtons = +proc selected*(r: RadioButtons): int = radioButtonsSelected(r.impl) +proc `selected=`*(r: RadioButtons; n: int) = radioButtonsSetSelected r.impl, cint n + +voidCallback wraprbOnSelected, RadioButtons, RadioButtons, onselected + +proc newRadioButtons*(onSelected: proc() = nil): RadioButtons = newFinal result result.impl = rawui.newRadioButtons() + result.onSelected = onSelected + radioButtonsOnSelected(result.impl, wraprbOnSelected, cast[pointer](result)) # ------------------------ MultilineEntry ------------------------------ From 2a3208a4b9197d37445683832a0e7573c4a921e1 Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Sun, 26 Feb 2017 06:10:20 +0100 Subject: [PATCH 2/5] Added genui macro which transforms a DSL into a GUI, also added simple example file to show how it works --- examples/genuiusg.nim | 64 ++++++++++++++++++ ui/genui.nim | 147 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 examples/genuiusg.nim create mode 100644 ui/genui.nim diff --git a/examples/genuiusg.nim b/examples/genuiusg.nim new file mode 100644 index 0000000..a8ce3a9 --- /dev/null +++ b/examples/genuiusg.nim @@ -0,0 +1,64 @@ +import "../ui", "../ui/genui" + +proc getRadioBox():RadioButtons = + genui: + result%RadioButtons: + "Radio Button 1" + "Radio Button 2" + "Radio Button 3" + +proc main*() = + var mainwin: Window + var spinbox: Spinbox + var slider: Slider + var progressbar: ProgressBar + var rb = getRadioBox() + rb.onselected= proc()= + echo rb.selected + + proc update(value: int) = + spinbox.value = value + slider.value = value + progressBar.value = value + + mainwin = newWindow("libui Control Gallery", 640, 480, true) + mainwin.margined = true + mainwin.onClosing = (proc (): bool = return true) + + genui: + box%VerticalBox(padded = true): + HorizontalBox(padded = true)[stretchy = true]: + Group(title = "Basic Controls"): + VerticalBox(padded = true): + Button("Button") + Checkbox("Checkbox") + Entry("Entry") + HorizontalSeparator + VerticalBox(padded = true)[stretchy = true]: + Group(title = "Numbers", margined = true): + VerticalBox(padded = true): + spinbox%Spinbox(min = 0, max = 100, onchanged = update) + slider%Slider(min = 0, max = 100, onchanged = update) + progressbar%ProgressBar + Group(title = "Lists", margined = true): + VerticalBox(padded = true): + Combobox: + "Combobox Item 1" + "Combobox Item 2" + "Combobox Item 3" + EditableCombobox: + "Editable Item 1" + "Editable Item 2" + "Editable Item 3" + %rb + Tab[stretchy = true]: + HorizontalBox[name = "Page 1"] + HorizontalBox[name = "Page 2"] + HorizontalBox[name = "Page 3"] + + mainwin.setChild(box) + show(mainwin) + mainLoop() + +init() +main() \ No newline at end of file diff --git a/ui/genui.nim b/ui/genui.nim new file mode 100644 index 0000000..3b2aaba --- /dev/null +++ b/ui/genui.nim @@ -0,0 +1,147 @@ +import macros, typetraits, tables, unicode, "../ui" + +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: Sun, 26 Feb 2017 07:08:15 +0100 Subject: [PATCH 3/5] Added a margined default to Tab. I believe most tabbed containers try to keep the style between the tabs the same, this allows you to do that more easily --- ui.nim | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ui.nim b/ui.nim index 12ff035..b9672a4 100644 --- a/ui.nim +++ b/ui.nim @@ -215,16 +215,27 @@ proc newLabel*(text: string): Label = type Tab* = ref object of Widget impl*: ptr rawui.Tab + marginedDefault*: bool children*: seq[Widget] proc add*[SomeWidget: Widget](t: Tab; name: string; child: SomeWidget) = tabAppend t.impl, name, child.impl + tabSetMargined(t.impl, tabNumPages(t.impl)-1, cint(t.marginedDefault)) t.children.add child +proc add*[SomeWidget: Widget](t: Tab; name: string; child: SomeWidget; margined: bool) = + add(t,name,child) + tabSetMargined(t.impl, tabNumPages(t.impl)-1, cint(margined)) + proc insertAt*[SomeWidget: Widget](t: Tab; name: string; at: int; child: SomeWidget) = tabInsertAt(t.impl, name, at.uint64, child.impl) + tabSetMargined(t.impl, at.uint64, cint(t.marginedDefault)) t.children.insert(child, at) +proc insertAt*[SomeWidget: Widget](t: Tab; name: string; at: int; child: SomeWidget; margined: bool) = + insertAt(t,name,at,child) + tabSetMargined(t.impl, at.uint64, cint(margined)) + proc delete*(t: Tab; index: int) = tabDelete(t.impl, index.cint) t.children.delete(index) @@ -234,9 +245,10 @@ proc margined*(t: Tab; page: int): bool = tabMargined(t.impl, page.cint) != 0 proc `margined=`*(t: Tab; page: int; x: bool) = tabSetMargined(t.impl, page.cint, cint(x)) -proc newTab*(): Tab = +proc newTab*(margined = false): Tab = newFinal result result.impl = rawui.newTab() + result.marginedDefault = margined result.children = @[] # ------------- Group -------------------------------------------------- From 0950d20237cfbc74ae55c5931d4c78e2268f6924 Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Sun, 26 Feb 2017 07:09:15 +0100 Subject: [PATCH 4/5] Added macro to create UIs, along with simple file to show how it works --- examples/genuiusg.nim | 59 ++++++++++++++++++++++++++++++++++--------- ui/genui.nim | 10 ++++++-- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/examples/genuiusg.nim b/examples/genuiusg.nim index a8ce3a9..8b4007d 100644 --- a/examples/genuiusg.nim +++ b/examples/genuiusg.nim @@ -1,46 +1,75 @@ import "../ui", "../ui/genui" +# The macro is a fairly simple substitution +# It follows one of three patterns: +# (arguments, for, widget, creator)[arguments, for, add, function]: +# +# %(arguments, for, widget, creator)[arguments, for, add, function]: +# +# %[arguments, for, add, function]: +# +# "String" +# +# Both ()-arguments and []-arguments can be omitted +# If the widget has no children the : must be omitted +# Identifiers create a var statement assigning the widget to the identifier, or assign the widget to the identifier if it already exists +# Using % you can add widget created previously, it takes the same add options and children as any other widget +# The string pattern is used for widgets which have an add function for string values, such as radio-, and comboboxes. + +# This is an example of a simple function which creates a piece of UI +# You will notice it uses result% to bind the RadioButtons widget created to the result proc getRadioBox():RadioButtons = genui: result%RadioButtons: "Radio Button 1" "Radio Button 2" "Radio Button 3" - +# This is a longer example which creates the same UI as in the controllgallery2 example proc main*() = var mainwin: Window var spinbox: Spinbox var slider: Slider var progressbar: ProgressBar - var rb = getRadioBox() - rb.onselected= proc()= - echo rb.selected + # This gets the widget from the previously defined function and adds callback to it + var radioBox = getRadioBox() + radioBox.onselected= proc()= + echo radioBox.selected + + # This is another way to create a callback, it will be assigned to the widgets later proc update(value: int) = spinbox.value = value slider.value = value progressBar.value = value + # Since Window uses setChild instead of add it can't be put inside the genui macro. + # NOTE: Group uses "child=" to set it's child but has a template to make it work. + # Adding more children to a Group widget would result in only the last being shown mainwin = newWindow("libui Control Gallery", 640, 480, true) mainwin.margined = true mainwin.onClosing = (proc (): bool = return true) + # This is where the magic happens. Note that most of the parameter names are included, + # this is a stylistic choice which I find makes the code easier to read. The notable + # exception to this is for widgets which take only a string as it's fairly obvious what it's used for genui: + # This vertical box is attached to the box variable which is later used to add it to the mainWin box%VerticalBox(padded = true): HorizontalBox(padded = true)[stretchy = true]: - Group(title = "Basic Controls"): + Group("Basic Controls"): VerticalBox(padded = true): Button("Button") Checkbox("Checkbox") Entry("Entry") HorizontalSeparator VerticalBox(padded = true)[stretchy = true]: - Group(title = "Numbers", margined = true): + Group("Numbers", margined = true): VerticalBox(padded = true): + # These are the three widgets which variables was declared earlier and used in the callback spinbox%Spinbox(min = 0, max = 100, onchanged = update) slider%Slider(min = 0, max = 100, onchanged = update) progressbar%ProgressBar - Group(title = "Lists", margined = true): + Group("Lists", margined = true): VerticalBox(padded = true): Combobox: "Combobox Item 1" @@ -50,11 +79,17 @@ proc main*() = "Editable Item 1" "Editable Item 2" "Editable Item 3" - %rb - Tab[stretchy = true]: - HorizontalBox[name = "Page 1"] - HorizontalBox[name = "Page 2"] - HorizontalBox[name = "Page 3"] + # This does not create a new widget but adds in the radio box created earlier + %radioBox + # Tabs are a bit strange as their add function has two required arguments + # Here the name parameter must be included fully qualified in order to be properly added + Tab(margined = true)[stretchy = true]: + HorizontalBox[name = "Page 1"]: + Label("Welcome to page 1") + HorizontalBox[name = "Page 2"]: + Label("Welcome to page 2") + HorizontalBox[name = "Page 3"]: + Label("Welcome to page 3") mainwin.setChild(box) show(mainwin) diff --git a/ui/genui.nim b/ui/genui.nim index 3b2aaba..0097f48 100644 --- a/ui/genui.nim +++ b/ui/genui.nim @@ -36,8 +36,12 @@ macro genui*(args: varargs[untyped]): untyped = if hasAddArguments: result = parseBracketExpr(call[0]) else: - result.name = $call[0].ident - result.arguments = if hasChildren: call[1.. Date: Sun, 26 Feb 2017 07:20:21 +0100 Subject: [PATCH 5/5] Removed some debuging echo statements --- ui/genui.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/genui.nim b/ui/genui.nim index 0097f48..b0dd3b6 100644 --- a/ui/genui.nim +++ b/ui/genui.nim @@ -142,7 +142,7 @@ macro genui*(args: varargs[untyped]): untyped = addCall.add addArg result.add addCall - echo treeRepr args[0] + #echo treeRepr args[0] let parsed = parseChildren(args[0]) result = newStmtList() for widget in parsed: @@ -150,4 +150,4 @@ macro genui*(args: varargs[untyped]): untyped = let widgetCode = createWidget(w) for node in widgetCode: result.add(node) - echo result.toStrLit + #echo result.toStrLit