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

Setup frame 3 #54

Merged
merged 86 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
bb398be
cleanup
elcritch Nov 13, 2023
38fa529
cleanup
elcritch Nov 13, 2023
378922f
cleanup
elcritch Nov 13, 2023
3495709
set AppFrame
elcritch Nov 13, 2023
7829dd6
refactoring globals
elcritch Nov 13, 2023
6427ce2
refactoring globals
elcritch Nov 13, 2023
3f0976c
refactoring globals
elcritch Nov 13, 2023
a1ac15b
refactoring globals
elcritch Nov 13, 2023
7bac09d
refactoring globals
elcritch Nov 13, 2023
4d542bf
refactoring globals
elcritch Nov 13, 2023
6ee60e5
refactoring globals
elcritch Nov 13, 2023
bfe295b
refactoring globals
elcritch Nov 13, 2023
592f726
refactoring globals
elcritch Nov 13, 2023
372291a
refactoring globals
elcritch Nov 13, 2023
62e5dc8
refactoring globals
elcritch Nov 13, 2023
6aa6e94
refactoring globals
elcritch Nov 13, 2023
2331aac
refactoring mainApp
elcritch Nov 13, 2023
bbbce43
refactoring mainApp
elcritch Nov 13, 2023
e17b617
refactoring mainApp
elcritch Nov 13, 2023
3833cc2
refactoring mainApp
elcritch Nov 13, 2023
0e67646
refactoring mainApp
elcritch Nov 13, 2023
370f1f7
refactoring mainApp
elcritch Nov 13, 2023
e8fbda8
refactoring mainApp
elcritch Nov 13, 2023
c68c054
refactoring mainApp
elcritch Nov 13, 2023
dd47365
refactoring mainApp
elcritch Nov 13, 2023
cccbb78
refactoring mainApp
elcritch Nov 13, 2023
543252e
refactoring mainApp
elcritch Nov 13, 2023
3d46b79
refactoring mainApp
elcritch Nov 13, 2023
9fb53af
refactoring mainApp
elcritch Nov 13, 2023
76dfd9c
refactoring mainApp
elcritch Nov 13, 2023
1765eaa
refactoring appFrame
elcritch Nov 13, 2023
cccf8b3
refactoring appFrame
elcritch Nov 13, 2023
48deb6f
refactoring appFrame
elcritch Nov 13, 2023
5782595
refactoring appFrame
elcritch Nov 13, 2023
5f8c24d
refactoring appFrame
elcritch Nov 13, 2023
8c6448e
setup multi-frames
elcritch Nov 16, 2023
3286b3a
fix close
elcritch Nov 16, 2023
c3c262e
updates
elcritch Nov 27, 2023
6276cbc
updates
elcritch Nov 27, 2023
7f1074b
updates
elcritch Nov 27, 2023
6282766
updates
elcritch Nov 27, 2023
b54d371
update frame api
elcritch Nov 27, 2023
1ef8075
update frame api
elcritch Nov 27, 2023
a1ec19f
update frame api
elcritch Nov 27, 2023
b2a6021
update frame api
elcritch Nov 27, 2023
e088507
update frame api
elcritch Nov 27, 2023
2df00fa
update frame api
elcritch Nov 27, 2023
46ed988
update frame api
elcritch Nov 27, 2023
8f6925d
update frame api
elcritch Nov 27, 2023
c132ac7
update frame api
elcritch Nov 27, 2023
71b38c3
update button
elcritch Nov 27, 2023
be467b6
fixing regression in state types
elcritch Dec 15, 2023
3914f9f
fixing regression in state types
elcritch Dec 15, 2023
5187a38
fixing regression in state types
elcritch Dec 15, 2023
cd1209e
fixing regression in state types
elcritch Dec 15, 2023
35ef61f
fixing regression in state types
elcritch Dec 15, 2023
19dadf6
fixing regression in state types
elcritch Dec 15, 2023
bececfa
use type name
elcritch Dec 15, 2023
dbeb4a3
use type name
elcritch Dec 15, 2023
8f76c3d
use type name
elcritch Dec 15, 2023
2be9300
use type name
elcritch Dec 15, 2023
02d0dff
get rid of nodes
elcritch Dec 15, 2023
36d01f2
get rid of nodes
elcritch Dec 15, 2023
cb4fc6e
get rid of nodes
elcritch Dec 15, 2023
e898cf5
get rid of nodes
elcritch Dec 15, 2023
099282d
get rid of nodes
elcritch Dec 15, 2023
01e893a
get rid of nodes
elcritch Dec 15, 2023
6bc582b
just use plain var
elcritch Dec 15, 2023
b88ecd4
add parent arg
elcritch Dec 16, 2023
a10fb20
add parent arg
elcritch Dec 16, 2023
6816564
add parent arg
elcritch Dec 16, 2023
91ad6f8
add parent arg
elcritch Dec 16, 2023
9d02029
add parent arg
elcritch Dec 16, 2023
4fa9dcb
add parent arg
elcritch Dec 16, 2023
2f0c42f
add parent arg
elcritch Dec 16, 2023
53fbb48
add parent arg
elcritch Dec 16, 2023
815603d
add parent arg
elcritch Dec 16, 2023
42bb236
add parent arg
elcritch Dec 16, 2023
1d7991e
add parent arg
elcritch Dec 16, 2023
176d7fe
move to named args
elcritch Dec 16, 2023
ffd9b8a
move to named args
elcritch Dec 16, 2023
40fced4
move to named args
elcritch Dec 16, 2023
630916d
move to named args
elcritch Dec 16, 2023
dc9c65e
move to named args
elcritch Dec 16, 2023
7913785
move to named args
elcritch Dec 16, 2023
db38ee4
move to named args
elcritch Dec 16, 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
149 changes: 81 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

A GUI toolkit for Nim that is event driven while being small and fast. It tries to incorporate the best elements of both imperitive and object oriented GUI toolkits. Originally based on Fidget it now has a multi-threaded core and improved event system. All widgets are typed and can contain their own state.

*Warning*: Figuro is still in an *alpha* stage and code and apis will be in flux. Some pieces still aren't completed.

## Example

Example drawing buttons with a fading background when any of them are hovered (see below for how it works):
Expand Down Expand Up @@ -32,7 +34,7 @@ proc btnClicked*(self: Button[int],
## which we can use to check if it's a mouse click
if buttons == {MouseLeft} or buttons == {DoubleClick}:
if kind == Enter:
self.state.inc
self.state.inc()
refresh(self)

proc btnHover*(self: Main, evtKind: EventKind) {.slot.} =
Expand All @@ -43,52 +45,53 @@ proc btnHover*(self: Main, evtKind: EventKind) {.slot.} =
proc draw*(self: Main) {.slot.} =
## draw slot for Main widget called whenever an event
## triggers a node or it's parents to be refreshed
nodes(self):
node.setName "main"

# Calls the widget template `rectangle`.
# This creates a new basic widget node. Generally used to draw generic rectangles.
rectangle "body":
node.setName "main"

# Calls the widget template `rectangle` which creates a new basic widget node.
# Generally used to draw generic boxes.
# Here we need to pass the `parent` argument since there's no current `node` set
rectangle "body", parent=self:
with node:
# sets the bounding box of this node
box 10'ux, 10'ux, 600'ux, 120'ux
cornerRadius 10.0
# `fill` sets the background color. Color apis use the `chroma` library
fill whiteColor.darken(self.bkgFade.amount)

# sets up horizontal widget node with alternate syntax
horizontal "horiz":
with node:
# sets the bounding box of this node
box 10'ux, 10'ux, 600'ux, 120'ux
cornerRadius 10.0
# `fill` sets the background color. Color apis use the `chroma` library
fill whiteColor.darken(self.bkgFade.amount)

# sets up horizontal widget node with alternate syntax
Horizontal.new "horiz": # same as `horizontal "horiz":`
with node:
box 10'ux, 0'ux, 100'pp, 100'pp
# `itemWidth` is needed to set the width of items
# in the horizontal widget
itemWidth 100'ux, gap = 20'ui
layoutItems justify=CxCenter, align=CxCenter

for i in 0 .. 4:
Button[int].new("btn", captures(i)):
let btn = node
box 10'ux, 0'ux, 100'pp, 100'pp
# `itemWidth` is needed to set the width of items
# in the horizontal widget
itemWidth 100'ux, gap = 20'ui
layoutItems justify=CxCenter, align=CxCenter

for i in 0 .. 4:
buttonOf[int] "btn", captures=[i]:
# widgets with generic type like Button can use `<name>Of[T]` to set the generic type, otherwise void is used
let btn = node
with node:
size 100'ux, 100'ux
connect(doHover, self, btnHover)
connect(doClick, node, btnClicked)
if i == 0:
connect(self, update, node, btnTick)

text "text":
with node:
size 100'ux, 100'ux
connect(doHover, self, btnHover)
connect(doClick, node, btnClicked)
if i == 0:
connect(self, update, node, btnTick)

text "text":
with node:
fill blackColor
setText({font: $(btn.state)}, Center, Middle)
fill blackColor
setText({font: $(btn.state)}, Center, Middle)

proc tick*(self: Main, tick: int, time: MonoTime) {.slot.} =
## handles background "fade" when buttons are hovered
self.bkgFade.tick(self)
emit self.update()

var main = Main.new()
app.width = 720
app.height = 140
startFiguro(main)
let frame = newAppFrame(main, size=(400'ui, 140'ui))
startFiguro(frame)
```

![Click Example](tests/tclick-screenshot.png)
Expand Down Expand Up @@ -116,33 +119,44 @@ type
Main* = ref object of Figuro

proc draw*(self: Main) {.slot.} =
nodes(self):
rectangle "body":
# each widget template injects a new `node` variable
# that references the current widget
rectangle "body", parent=self:
# each widget template injects a new `node` variable
# that references the current widget

# sets the bounding box of this node
box node, 10'ux, 10'ux, 600'ux, 120'ux
# sets the bounding box of this node
box node, 10'ux, 10'ux, 600'ux, 120'ux

# set the fill color
fill node, css"00001F"
# set the fill color
fill node, css"00001F"

var main = Main.new()
app.width = 720
app.height = 140
startFiguro(main)
let frame = newAppFrame(main, size=(400'ui, 140'ui))
frame.startFiguro()
```

The `nodes` template sets up the needed vars for creating nodes. The `rectangle` widget template sets up a basic widget. Widget templates create a new node, adds it as a child to the current node, and sets up the callbacks needed for a node.
The `rectangle` widget template sets up a basic widget. Widget templates create a new node, adds it as a child to the current node, and sets up the callbacks needed for a node.

### Exporting a Widget Instance

It's possible to manually create nodes, but it's not encouraged. Although it can be handy to understand the how it works. The blue rectangle example roughly expands to:
If the last line is `result = node` then the widget template will return the widget. Due to limitations in how the templates work, only `result = node` will work and it must be the last line of the widget:

```nim
proc draw*(self: Main) {.slot.} =
var node {.inject.} = self
let vert = vertical "vert", parent=self:
size node, 400'ux, 120'ux
result = node

block:
# rectangle "body":
echo "vertical node created: ", vert
```

### Manual Nodes

It's possible to manually create nodes, but it's not encouraged. Although it can be handy to understand the how it works. The blue rectangle example above expands to:

```nim
proc draw*(self: Main) {.slot.} =
var node = self
block: # rectangle
let parent {.inject.}: Figuro = node
var node {.inject.}: `widgetType` = nil
preNode(BasicFiguro, "body", node, parent)
Expand All @@ -161,21 +175,20 @@ There's an alternative syntax that builds on a specialized `new` template. It wo

```nim
proc draw*(self: Main) {.slot.} =
nodes(self):
BasicFiguro.new "body":
with node:
box 10'ux, 10'ux, 600'ux, 120'ux
cornerRadius 10.0
fill whiteColor.darken(self.hoveredAlpha)
horizontal "horiz":
offset node, 10'ux, 0'ux
itemWidth node, cx"min-content", gap = 20'ui
for i in 0 .. 4:
Button[int].new "btn", captures(i):
with node:
size 100'ux, 100'ux
# we need to connect the nodes onHover event
connect(doHover, self, buttonHover)
BasicFiguro.new "body", parent=self:
with node:
box 10'ux, 10'ux, 600'ux, 120'ux
cornerRadius 10.0
fill whiteColor.darken(self.hoveredAlpha)
Horizontal.new "horiz":
offset node, 10'ux, 0'ux
itemWidth node, cx"min-content", gap = 20'ui
for i in 0 .. 4:
Button[int].new "btn", captures(i):
with node:
size 100'ux, 100'ux
# we need to connect the nodes onHover event
connect(doHover, self, buttonHover)
```

Currently both syntaxes are supported and can be mixed and matched.
Expand Down
14 changes: 14 additions & 0 deletions figuro/common/nodes/ui.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import std/unicode
import std/monotimes
import std/hashes
import basics
import ../../meta
import ../../inputs
Expand All @@ -26,7 +27,17 @@ type
Theme* = ref object
font*: UiFont

AppFrame* = ref object
redrawNodes*: OrderedSet[Figuro]
root*: Figuro
uxInputList*: Chan[AppInputs]
running*, focused*, minimized*, fullscreen*: bool

windowSize*: Box ## Screen size in logical coordinates.
windowRawSize*: Vec2 ## Screen coordinates

Figuro* = ref object of Agent
frame*: AppFrame
parent*: Figuro
uid*: NodeID
name*: StackString[16]
Expand Down Expand Up @@ -80,6 +91,9 @@ type
Property*[T] = ref object of Agent
value*: T

proc hash*(a: AppFrame): Hash =
a.root.hash()

proc new*[T: Figuro](tp: typedesc[T]): T =
result = T()
result.agentId = nextAgentId()
Expand Down
43 changes: 21 additions & 22 deletions figuro/exec.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ else:
export opengl

import std/os
import std/sets
import std/sharedtables

import shared, internal
import ui/[core, events]
import common/nodes/ui
Expand All @@ -28,17 +29,15 @@ when not defined(gcArc) and not defined(gcOrc) and not defined(nimdoc):
{.error: "Figuro requires --gc:arc or --gc:orc".}

var
mainApp*: MainCallback
tickMain*: MainCallback
eventMain*: MainCallback
loadMain*: MainCallback
sendRoot*: proc (nodes: sink RenderNodes) {.closure.}
runFrame*: proc(frame: AppFrame) {.nimcall.}
appFrames*: Table[AppFrame, Renderer]


const renderPeriodMs {.intdefine.} = 16
const appPeriodMs {.intdefine.} = 16

var frameTickThread, appTickThread: Thread[void]
var appThread, : Thread[MainCallback]
var appThread, : Thread[AppFrame]

proc renderTicker() {.thread.} =
while true:
Expand All @@ -48,39 +47,39 @@ proc renderTicker() {.thread.} =
if app.tickCount == app.tickCount.typeof.high:
app.tickCount = 0

proc runRenderer(renderer: Renderer) =
while app.running:
wait(uiRenderEvent)
timeIt(renderAvgTime):
renderer.render(true)

proc appTicker() {.thread.} =
while app.running:
uiAppEvent.trigger()
os.sleep(renderPeriodMs - 2)

proc runApplication(mainApp: MainCallback) {.thread.} =
proc runApplication(frame: AppFrame) {.thread.} =
{.gcsafe.}:
while app.running:
wait(uiAppEvent)
timeIt(appAvgTime):
tickMain()
eventMain()
mainApp()
runFrame(frame)
app.frameCount.inc()

proc run*(renderer: Renderer) =
proc runRenderer(renderer: Renderer) =
while app.running and renderer.frame.running:
wait(uiRenderEvent)
timeIt(renderAvgTime):
renderer.render(true)

proc setupFrame*(frame: AppFrame): Renderer =
let renderer = setupRenderer(frame)
appFrames[frame] = renderer
result = renderer

sendRoot = proc (nodes: sink RenderNodes) =
renderer.updated = true
renderer.nodes = nodes
proc run*(frame: AppFrame) =
let renderer = setupFrame(frame)

uiRenderEvent = initUiEvent()
uiAppEvent = initUiEvent()

createThread(frameTickThread, renderTicker)
createThread(appTickThread, appTicker)
createThread(appThread, runApplication, mainApp)
createThread(appThread, runApplication, frame)

proc ctrlc() {.noconv.} =
echo "Got Ctrl+C exiting!"
Expand Down
Loading