Skip to content

Added RadioButton onselected, ProgressBar value getter, and renamed some parameters for consistency #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions examples/genuiusg.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import "../ui", "../ui/genui"

# The macro is a fairly simple substitution
# It follows one of three patterns:
# <Widget name>(arguments, for, widget, creator)[arguments, for, add, function]:
# <Children>
# <Identifier>%<Widget name>(arguments, for, widget, creator)[arguments, for, add, function]:
# <Children>
# %<Identifier>[arguments, for, add, function]:
# <Children>
# "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 %<identifier> 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

# 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("Basic Controls"):
VerticalBox(padded = true):
Button("Button")
Checkbox("Checkbox")
Entry("Entry")
HorizontalSeparator
VerticalBox(padded = true)[stretchy = 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("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"
# 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)
mainLoop()

init()
main()
44 changes: 33 additions & 11 deletions ui.nim
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,26 @@ 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; 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
tabSetMargined(t.impl, tabNumPages(t.impl)-1, cint(t.marginedDefault))
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 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)
Expand All @@ -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 --------------------------------------------------
Expand All @@ -249,9 +261,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)
Expand Down Expand Up @@ -306,6 +318,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()
Expand Down Expand Up @@ -370,12 +384,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 ------------------------------

Expand Down
153 changes: 153 additions & 0 deletions ui/genui.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
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.. <L: result[i] = s[i + a]

proc high(s: NimNode):int =
s.len-1

template add*[SomeWidget: Widget](g:Group, child:SomeWidget) =
g.`child=`child

macro genui*(args: varargs[untyped]): untyped =
type WidgetArguments = object
identifier:NimNode
name: string
addArguments: seq[NimNode]
arguments: seq[NimNode]
children: seq[WidgetArguments]
isStr:bool
isIdentified:bool

proc parseNode(node:NimNode):WidgetArguments
proc parseBracketExpr(bracketExpr:NimNode):WidgetArguments
proc parseChildren(stmtList:NimNode): seq[WidgetArguments] =
result = @[]
for child in stmtList:
result.add parseNode(child)

proc parseCall(call:NimNode):WidgetArguments =
let hasAddArguments = call[0].kind == nnkBracketExpr
let hasChildren = call[call.high].kind == nnkStmtList
if hasAddArguments:
result = parseBracketExpr(call[0])
else:
result.name = $call[0].ident
if result.arguments == nil:
result.arguments = if hasChildren: call[1..<call.high] else: call[1..call.high]
#else:
# for arg in if hasChildren: call[1..<call.high] else: call[1..call.high]:
# result.arguments.add arg
result.children = if hasChildren: parseChildren(call[call.high]) else: nil

proc parseBracketExpr(bracketExpr:NimNode):WidgetArguments =
let hasArguments = bracketExpr[0].kind == nnkCall
let hasChildren = bracketExpr[bracketExpr.high].kind == nnkStmtList
if hasArguments:
result = parseCall(bracketExpr[0])
else:
result.name = $bracketExpr[0].ident
result.addArguments = if hasChildren: bracketExpr[1..<bracketExpr.high] else: bracketExpr[1..bracketExpr.high]
result.children = if hasChildren: parseChildren(bracketExpr[bracketExpr.high]) else: nil

proc parseInfix(infix:NimNode):WidgetArguments=
result = parseNode(infix[2])
result.identifier = infix[1]
if infix[infix.high].kind == nnkStmtList:
result.children = parseChildren(infix[infix.high])

proc parseIdent(ident:NimNode):WidgetArguments=
result = WidgetArguments(
identifier: nil,
name: $ident.ident,
addArguments: nil,
arguments: nil,
children: nil,
)

proc parsePrefix(prefix:NimNode):WidgetArguments=
#assert prefix[0] == !"%", "Use % to identify"
result = parseNode(prefix[1])
result.isIdentified = true

proc parseString(str:NimNode):WidgetArguments=
result = WidgetArguments(
name: str.strVal,
isStr: true
)

proc parseNode(node:NimNode):WidgetArguments =
case node.kind:
of nnkCall:
result = parseCall(node)
of nnkBracketExpr:
result = parseBracketExpr(node)
of nnkInfix:
result = parseInfix(node)
of nnkPrefix:
result = parsePrefix(node)
of nnkIdent:
result = parseIdent(node)
of nnkStrLit:
result = parseString(node)
else:
discard

template updateOrCreate(ident: untyped, value: untyped)=
when not declared(ident):
var ident = value
else:
ident = value

proc createWidget(widget: var WidgetArguments):NimNode =
result = newStmtList()
var call:NimNode
if widget.isIdentified:
call = newIdentNode(widget.name)
else:
call = newCall("new" & widget.name)
if widget.arguments!=nil:
for arg in widget.arguments:
call.add arg
if widget.identifier==nil:
widget.identifier = genSym(nskLet)
result.add nnkLetSection.newTree(
nnkIdentDefs.newTree(
widget.identifier,
newEmptyNode(),
call
)
)
else:
result.add getAst(updateOrCreate(widget.identifier, call))

for child in widget.children:
if child.isStr:
result.add newCall("add", widget.identifier, newStrLitNode(child.name))
else:
var c = child
let childCode = createWidget(c)
for node in childCode:
result.add node
var addCall = newCall("add",widget.identifier, nnkExprEqExpr.newTree(
ident"child",
c.identifier
)
)
for addArg in c.addArguments:
addCall.add addArg
result.add addCall

#echo treeRepr args[0]
let parsed = parseChildren(args[0])
result = newStmtList()
for widget in parsed:
var w = widget
let widgetCode = createWidget(w)
for node in widgetCode:
result.add(node)
#echo result.toStrLit