Skip to content
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

Refactor playground to not use toListFormField + Remove unused imports from examples #131

Merged
merged 21 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0219a41
Beautify Scale example
PhilippMDoerner Oct 25, 2023
e60b67b
Remove unused import
PhilippMDoerner Oct 25, 2023
3c510f6
Remove unused imports from example
PhilippMDoerner Oct 25, 2023
0d3bb3d
Refactor playground to not require toListFormField
PhilippMDoerner Oct 25, 2023
1a8924a
Fix toFormfield signature for enums
PhilippMDoerner Oct 25, 2023
93a6eb8
Remove requirement for fieldnames to be static in playground
PhilippMDoerner Oct 25, 2023
7d2eec0
Add percent formfield
PhilippMDoerner Oct 25, 2023
a6e8c82
Add delete button to seq formfield
PhilippMDoerner Oct 25, 2023
106ed80
Move generics over to use Viewable
PhilippMDoerner Oct 26, 2023
25ede6a
Make range toFormField generic over all ranges
PhilippMDoerner Oct 26, 2023
fa85850
Make toFormField doc comments more uniform
PhilippMDoerner Oct 26, 2023
ede376e
Add index to form field label of sequences
PhilippMDoerner Oct 26, 2023
caf7295
Add forward declarations for all toFormField procs
PhilippMDoerner Oct 26, 2023
580ce73
Add toFormField proc to fully generate form for any type
PhilippMDoerner Oct 26, 2023
cdf614c
Add toFormField proc for any distinct type
PhilippMDoerner Oct 26, 2023
a6d243a
Add back removed import
PhilippMDoerner Nov 4, 2023
de3ef3a
Add Variant toFormField overload
PhilippMDoerner Nov 5, 2023
93afe60
Change name of ObjectVariant concept
PhilippMDoerner Nov 5, 2023
5fcc0e5
Fix deletion not working
PhilippMDoerner Nov 5, 2023
a5d5503
Beautify how delete button gets added
PhilippMDoerner Nov 5, 2023
3290144
Add support for ref object variants
PhilippMDoerner Nov 5, 2023
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
1 change: 0 additions & 1 deletion examples/widgets/action_bar.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import std/[sequtils]
import owlkettle, owlkettle/[dataentries, playground, adw]

viewable App:
Expand Down
1 change: 0 additions & 1 deletion examples/widgets/editable_label.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import std/[sequtils]
import owlkettle, owlkettle/[dataentries, playground, adw]

viewable App:
Expand Down
2 changes: 1 addition & 1 deletion examples/widgets/fixed.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# SOFTWARE

import std/random
import owlkettle, owlkettle/[adw, dataentries]
import owlkettle, owlkettle/[adw]

type FixedItem = ref object
name: string
Expand Down
1 change: 0 additions & 1 deletion examples/widgets/picture.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import std/[asyncfutures]
PhilippMDoerner marked this conversation as resolved.
Show resolved Hide resolved
import owlkettle, owlkettle/[playground, adw]

const APP_NAME = "Image Example"
Expand Down
43 changes: 22 additions & 21 deletions examples/widgets/scale.nim
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,30 @@ method view(app: AppState): Widget =
result = gui:
Window():
title = "Scale Example"
defaultSize = (800, 600)
defaultSize = (800, 150)
HeaderBar() {.addTitlebar.}:
insert(app.toAutoFormMenu()) {.addRight.}

Scale:
min = app.min
max = app.max
value = app.value
marks = app.marks
inverted = app.inverted
showValue = app.showValue
stepSize = app.stepSize
pageSize = app.pageSize
orient = app.orient
showFillLevel = app.showFillLevel
precision = app.precision
valuePosition = app.valuePosition
sensitive = app.sensitive
tooltip = app.tooltip
sizeRequest = app.sizeRequest

proc valueChanged(newValue: float64) =
app.value = newValue
echo "New value from Scale is ", $newValue
Box(orient = OrientY):
Scale {.expand: false.}:
min = app.min
max = app.max
value = app.value
marks = app.marks
inverted = app.inverted
showValue = app.showValue
stepSize = app.stepSize
pageSize = app.pageSize
orient = app.orient
showFillLevel = app.showFillLevel
precision = app.precision
valuePosition = app.valuePosition
sensitive = app.sensitive
tooltip = app.tooltip
sizeRequest = app.sizeRequest

proc valueChanged(newValue: float64) =
app.value = newValue
echo "New value from Scale is ", $newValue

adw.brew(gui(App()))
174 changes: 45 additions & 129 deletions owlkettle/playground.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import std/[options, times, macros, strformat, strutils, sequtils, typetraits]
import std/[options, times, macros, strformat, sugar, strutils, sequtils, typetraits]
import ./dataentries
import ./adw
import ./widgetutils
Expand All @@ -32,71 +32,72 @@ macro getField*(someType: untyped, fieldName: static string): untyped =
nnkDotExpr.newTree(someType, ident(fieldName))

# Default `toFormField` implementations
proc toFormField[T: SomeNumber](state: auto, fieldName: static string, typ: typedesc[T]): Widget =
proc toFormField(state: auto, field: ptr SomeNumber, fieldName: static string): Widget =
PhilippMDoerner marked this conversation as resolved.
Show resolved Hide resolved
## Provides a form field for all number times in SomeNumber
return gui:
ActionRow:
title = fieldName
FormulaEntry() {.addSuffix.}:
value = state.getField(fieldName).float
value = field[].float
xAlign = 1.0
maxWidth = 8
proc changed(value: float) =
state.getField(fieldName) = value.T
field[] = type(field[])(value)

proc toFormField(state: auto, fieldName: static string, typ: typedesc[string]): Widget =
proc toFormField(state: auto, field: ptr string, fieldName: static string): Widget =
## Provides a form field for string
return gui:
ActionRow:
title = fieldName
Entry() {.addSuffix.}:
text = state.getField(fieldName)
text = field[]
proc changed(text: string) =
state.getField(fieldName) = text
field[] = text

proc toFormField(state: auto, fieldName: static string, typ: typedesc[bool]): Widget =
proc toFormField(state: auto, field: ptr bool, fieldName: static string): Widget =
## Provides a form field for bool
return gui:
ActionRow:
title = fieldName
Box() {.addSuffix.}:
Switch() {.vAlign: AlignCenter, expand: false.}:
state = state.getField(fieldName)
state = field[]
proc changed(newVal: bool) =
state.getField(fieldName) = newVal
field[] = newVal

proc toFormField(state: auto, fieldName: static string, typ: typedesc[auto]): Widget =
proc toFormField(state: auto, field: ptr auto, fieldName: static string): Widget =
## Provides a dummy field as a fallback for any type without a `toFormField`.
const typeName: string = $field[].type
return gui:
ActionRow:
title = fieldName
Label():
text = fmt"Override `toFormField` for '{$typ.type}'"
text = fmt"Override `toFormField` for '{typeName}'"
tooltip = fmt"""
The type '{$typ.type}' must implement a `toFormField` proc:
The type '{typeName}' must implement a `toFormField` proc:
`toFormField(
state: auto,
field: ptr {typeName}
fieldName: static string,
typ: typedesc[{$typ.type}]
): Widget`
state: The <Widget>State
field: The field's value to assign to/get the value from
fieldName: The name of the field on `state` for which the current form field is being generated
typ: The type for which `toListFormField` shall function.

Implementing the proc will override this dummy Widget.
See the playground module for examples.
"""

proc toFormField[T: enum](state: auto, fieldName: static string, typ: typedesc[T]): Widget =
proc toFormField(state: auto, field: ptr[enum] , fieldName: static string): Widget =
## Provides a form field for enums
let options: seq[string] = T.items.toSeq().mapIt($it)
let options: seq[string] = type(field[]).items.toSeq().mapIt($it)
return gui:
ComboRow:
title = fieldName
items = options
selected = ord(state.getField(fieldName))
selected = ord(field[])
proc select(enumIndex: int) =
state.getField(fieldName) = enumIndex.T
field[] = type(field[])(enumIndex)

viewable DateDialog:
date: DateTime = now()
Expand All @@ -123,165 +124,79 @@ method view (state: DateDialogState): Widget =
proc select(date: DateTime) =
state.date = date

proc toFormField(state: auto, fieldName: static string, typ: typedesc[DateTime]): Widget =
proc toFormField(state: auto, field: ptr DateTime, fieldName: static string): Widget =
## Provides a form field for DateTime
return gui:
ActionRow:
title = fieldName
subtitle = $state.getField(fieldName).inZone(local())
subtitle = $(field[]).inZone(local())
Button {.addSuffix.}:
icon = "x-office-calendar-symbolic"
text = "Select"
proc clicked() =
let (res, dialogState) = state.open(gui(DateDialog()))
if res.kind == DialogAccept:
state.getField(fieldName) = DateDialogState(dialogState).date
field[] = DateDialogState(dialogState).date

proc toFormField(state: auto, fieldName: static string, typ: typedesc[tuple[x,y: int]]): Widget =
proc toFormField(state: auto, field: ptr tuple[x, y: int], fieldName: static string): Widget =
## Provides a form field for the tuple type of sizeRequest
let tup = field[]
return gui:
ActionRow:
title = fieldName
NumberEntry() {.addSuffix.}:
value = state.getField(fieldName)[0].float
value = tup[0].float
xAlign = 1.0
maxWidth = 8
proc changed(value: float) =
state.getField(fieldName)[0] = value.int
field[][0] = value.int

NumberEntry() {.addSuffix.}:
value = state.getField(fieldName)[1].float
value = tup[1].float
xAlign = 1.0
maxWidth = 8
proc changed(value: float) =
state.getField(fieldName)[1] = value.int
field[][1] = value.int


## Default `toListFormField` implementations
proc toListFormField[T: SomeNumber](state: auto, fieldName: static string, index: int, typ: typedesc[T]): Widget =
## Provides a form to display a single entry of any number in a list of number entries.
return gui:
ActionRow:
title = fieldName & $index

FormulaEntry() {.addSuffix.}:
value = state.getField(fieldName)[index].float
xAlign = 1.0
maxWidth = 8
proc changed(value: float) =
state.getField(fieldName)[index] = value.T

proc toListFormField(state: auto, fieldName: static string, index: int, typ: typedesc[string]): Widget =
## Provides a form to display a single entry of type `string` in a list of `string` entries.
return gui:
ActionRow:
title = fieldName
Entry() {.addSuffix.}:
text = state.getField(fieldName)[index]
proc changed(text: string) =
state.getField(fieldName)[index] = text

proc toListFormField(state: auto, fieldName: static string, index: int, typ: typedesc[bool]): Widget =
## Provides a form to display a single entry of type `bool` in a list of `bool` entries.
return gui:
ActionRow:
title = fieldName
Box() {.addSuffix.}:
Switch() {.vAlign: AlignCenter, expand: false.}:
state = state.getField(fieldName)[index]
proc changed(newVal: bool) =
state.getField(fieldName)[index] = newVal

proc toListFormField(state: auto, fieldName: static string, index: int, typ: typedesc[DateTime]): Widget =
## Provides a form to display a single entry of type `DateTime` in a list of `DateTime` entries.
return gui:
ActionRow:
title = fieldName
subtitle = $state.getField(fieldName)[index].inZone(local())
Button {.addSuffix.}:
icon = "x-office-calendar-symbolic"
text = "Select"
proc clicked() =
let (res, dialogState) = state.open(gui(DateDialog()))
if res.kind == DialogAccept:
state.getField(fieldName)[index] = DateDialogState(dialogState).date

proc toListFormField[T: enum](state: auto, fieldName: static string, index: int, typ: typedesc[T]): Widget =
## Provides a form to display a single entry of an enum in a list of enum entries.
let options: seq[string] = T.items.toSeq().mapIt($it)
return gui:
ComboRow:
title = fieldName
items = options
selected = ord(state.getField(fieldName)[index])
proc select(enumIndex: int) =
state.getField(fieldName)[index] = enumIndex.T

proc toListFormField(state: auto, fieldName: static string, index: int, typ: typedesc[auto]): Widget =
## Provides a dummy row widget for displaying an entry in a list of any type without its own `toListFormField`
return gui:
ActionRow:
title = fieldName
Label():
text = fmt"Override `toListFormField` for '{$typ.type}'"
tooltip = fmt"""
The type '{$typ.type}' must implement a `toListFormField` proc:
`toListFormField(
state: auto,
fieldName: static string,
index: int,
typ: typedesc[{$typ.type}]
): Widget`
state: The <Widget>State
fieldName: The name of the field on `state` that contains the seq for which the current form-row is being generated
index: The index on the list of entries in `state.<fieldName>`
typ: The type for which `toListFormField` shall function.

Implementing the proc will override this dummy Widget.
See the playground module for examples.
"""

proc toListFormField(state: auto, fieldName: static string, index: int, typ: typedesc[ScaleMark]): Widget =
proc toFormField(state: auto, field: ptr ScaleMark, fieldName: static string): Widget =
## Provides a form to display a single entry of type `ScaleMark` in a list of `ScaleMark` entries.
let mark: ScaleMark = state.getField(fieldName)[index]
return gui:
ActionRow:
title = fieldName & $index
title = fieldName

FormulaEntry {.addSuffix.}:
value = mark.value
value = field[].value
xAlign = 1.0
maxWidth = 8
proc changed(value: float) =
state.getField(fieldName)[index].value = value
field[].value = value

DropDown {.addSuffix.}:
items = ScalePosition.items.toSeq().mapIt($it)
selected = mark.position.int
selected = field[].position.int
proc select(enumIndex: int) =
state.getField(fieldName)[index].position = enumIndex.ScalePosition

Button {.addSuffix.}:
icon = "user-trash-symbolic"
proc clicked() =
state.getField(fieldName).delete(index)
field[].position = enumIndex.ScalePosition

proc toFormField[T](state: auto, fieldName: static string, typ: typedesc[seq[T]]): Widget =
proc toFormField[T](state: auto, field: ptr seq[T], fieldName: static string): Widget =
## Provides a form field for any field on `state` with a seq type.
## Displays a dummy widget if there is no `toListFormField` implementation for type T.
let formFields = collect(newSeq):
for index, value in field[]:
toFormField(state, field[][index].addr, fieldName)

return gui:
ExpanderRow:
title = fieldName

for index, num in state.getField(fieldName):
insert(toListFormField(state, fieldName, index, T)){.addRow.}
for index, formField in formFields:
insert(formField){.addRow.}

ListBoxRow {.addRow.}:
Button:
icon = "list-add-symbolic"
style = [ButtonFlat]
proc clicked() =
state.getField(fieldName).add(default(T))
field[].add(default(T))

proc toAutoFormMenu*[T](app: T, sizeRequest: tuple[x,y: int] = (400, 700), ignoreFields: static seq[string]): Widget =
## Provides a form for every field in a given `app` instance.
Expand All @@ -292,7 +207,8 @@ proc toAutoFormMenu*[T](app: T, sizeRequest: tuple[x,y: int] = (400, 700), ignor
var fieldWidgets: seq[Widget] = @[]
for name, value in app[].fieldPairs:
when name notin ["app", "viewed"] and name notin ignoreFields:
let fieldWidget = app.toFormField(name, value.type)
let field: ptr = value.addr
let fieldWidget = app.toFormField(field, name)
fieldWidgets.add(fieldWidget)

result = gui:
Expand Down