Compare commits
18 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78a47158a1 | ||
|
|
e63a7dbb21 | ||
|
|
cd78f5085f | ||
|
|
4c582d7c06 | ||
|
|
d3e1d28c89 | ||
|
|
91679f8ae6 | ||
|
|
8bdaab3f82 | ||
|
|
ebccc4e9a1 | ||
|
|
ed70a0cbfc | ||
|
|
393c336f90 | ||
|
|
e434517dd1 | ||
|
|
2b9880a9a2 | ||
|
|
f5e9db55c3 | ||
|
|
a304322286 | ||
|
|
443b24de3e | ||
|
|
41df9b769f | ||
|
|
1ad978804c | ||
|
|
23fc331626 |
6 changed files with 206 additions and 58 deletions
|
|
@ -165,6 +165,7 @@ proc updateDirtyElements(parent, current: Node, newNode: VNode) =
|
|||
#if dirtyCount <= 0: return
|
||||
|
||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||
newNode.dom = oldNode.dom
|
||||
if not equalsShallow(newNode, oldNode):
|
||||
detach(oldNode)
|
||||
let n = vnodeToDom(newNode)
|
||||
|
|
@ -175,7 +176,6 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
|||
elif newNode.kind != VNodeKind.text:
|
||||
let newLength = newNode.len
|
||||
var oldLength = oldNode.len
|
||||
let minLength = min(newLength, oldLength)
|
||||
assert oldNode.kind == newNode.kind
|
||||
when defined(simpleDiff):
|
||||
for i in 0..min(newLength, oldLength)-1:
|
||||
|
|
@ -188,36 +188,46 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
|||
detach(oldNode[i])
|
||||
current.removeChild(current.lastChild)
|
||||
else:
|
||||
var commonPrefix = 0
|
||||
while commonPrefix < minLength and
|
||||
equalsTree(newNode[commonPrefix], oldNode[commonPrefix]):
|
||||
inc commonPrefix
|
||||
var left = 0
|
||||
var rightNew = newLength - 1
|
||||
var rightOld = oldLength - 1
|
||||
|
||||
var oldPos = oldLength - 1
|
||||
var newPos = newLength - 1
|
||||
while oldPos >= commonPrefix and newPos >= commonPrefix and
|
||||
equalsTree(newNode[newPos], oldNode[oldPos]):
|
||||
dec oldPos
|
||||
dec newPos
|
||||
while left <= min(rightNew, rightOld):
|
||||
var minRight = min(rightOld, rightNew)
|
||||
|
||||
var pos = min(oldPos, newPos) + 1
|
||||
for i in commonPrefix..pos-1:
|
||||
updateElement(current, current.childNodes[i], newNode[i], oldNode[i])
|
||||
while left <= minRight and
|
||||
equalsTree(newNode[left], oldNode[left]):
|
||||
newNode[left] = oldNode[left]
|
||||
inc left
|
||||
|
||||
var nextChildPos = oldPos + 1
|
||||
while pos <= newPos:
|
||||
if nextChildPos == oldLength:
|
||||
current.appendChild(vnodeToDom(newNode[pos]))
|
||||
else:
|
||||
current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos])
|
||||
# added new Node, so old state of VDOM have one more Node
|
||||
inc oldLength
|
||||
inc pos
|
||||
inc nextChildPos
|
||||
while rightOld > left and rightNew > left and
|
||||
equalsTree(newNode[rightNew], oldNode[rightOld]):
|
||||
newNode[rightNew] = oldNode[rightOld]
|
||||
dec rightOld
|
||||
dec rightNew
|
||||
|
||||
for i in pos..oldPos:
|
||||
detach(oldNode[i])
|
||||
current.removeChild(current.childNodes[pos])
|
||||
minRight = min(rightOld, rightNew)
|
||||
if left <= minRight:
|
||||
updateElement(current, oldNode[left].dom, newNode[left], oldNode[left])
|
||||
inc left
|
||||
|
||||
if left < minRight:
|
||||
updateElement(current, oldNode[rightOld].dom, newNode[rightNew], oldNode[rightOld])
|
||||
dec rightNew
|
||||
dec rightOld
|
||||
|
||||
var posNextElem = rightOld + 1
|
||||
var isPushBack = posNextElem == oldLength
|
||||
while left <= rightNew:
|
||||
if isPushBack:
|
||||
current.appendChild(vnodeToDom(newNode[left]))
|
||||
else:
|
||||
current.insertBefore(vnodeToDom(newNode[left]), oldNode[posNextElem].dom)
|
||||
inc left
|
||||
|
||||
for i in left..rightOld:
|
||||
current.removeChild(oldNode[i].dom)
|
||||
detach(oldNode[i])
|
||||
|
||||
when false:
|
||||
var drawTimeout: Timeout
|
||||
|
|
@ -256,6 +266,8 @@ proc redraw*() =
|
|||
else:
|
||||
dodraw()
|
||||
|
||||
proc redrawForce*() = dodraw()
|
||||
|
||||
proc init(ev: Event) =
|
||||
reqFrame(dodraw)
|
||||
|
||||
|
|
@ -263,6 +275,12 @@ proc setRenderer*(renderer: proc (): VNode) =
|
|||
dorender = renderer
|
||||
window.onload = init
|
||||
|
||||
proc setRendererOnly*(renderer: proc (): VNode) =
|
||||
dorender = renderer
|
||||
|
||||
proc setOnloadOnly*() =
|
||||
window.onload = init
|
||||
|
||||
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler) =
|
||||
## Implements the foundation of Karax's event management.
|
||||
## Karax DSL transforms ``tag(onEvent = handler)`` to
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from strutils import startsWith, toLowerAscii
|
|||
|
||||
const
|
||||
StmtContext = ["kout", "inc", "echo", "dec", "!"]
|
||||
SpecialAttrs = ["id", "class", "value", "key"]
|
||||
SpecialAttrs = ["id", "class", "value", "key", "track"]
|
||||
|
||||
proc getName(n: NimNode): string =
|
||||
case n.kind
|
||||
|
|
|
|||
66
src/vdom.nim
66
src/vdom.nim
|
|
@ -102,12 +102,14 @@ type
|
|||
kind*: VNodeKind
|
||||
key*: VKey
|
||||
id*, class*, text*: cstring
|
||||
track*: int
|
||||
kids: seq[VNode]
|
||||
# even index: key, odd index: value; done this way for memory efficiency:
|
||||
attrs: seq[cstring]
|
||||
events*: seq[(EventKind, EventHandler)]
|
||||
hash*: Hash
|
||||
validHash*: bool
|
||||
when false:
|
||||
hash*: Hash
|
||||
validHash*: bool
|
||||
dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node
|
||||
## is not part of the virtual DOM anymore.
|
||||
|
||||
|
|
@ -146,7 +148,11 @@ proc eq*(a, b: VNode): bool =
|
|||
|
||||
proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
|
||||
if n.attrs.isNil:
|
||||
n.attrs = @[key, val]
|
||||
when true:
|
||||
let x = @[key, val]
|
||||
shallowCopy n.attrs, x
|
||||
else:
|
||||
n.attrs = @[key, val]
|
||||
else:
|
||||
for i in countup(0, n.attrs.len-2, 2):
|
||||
if n.attrs[i] == key:
|
||||
|
|
@ -161,6 +167,7 @@ proc getAttr*(n: VNode; key: cstring): cstring =
|
|||
|
||||
proc len*(x: VNode): int = x.kids.len
|
||||
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx]
|
||||
proc `[]=`*(x: VNode; idx: int; y: VNode) = x.kids[idx] = y
|
||||
proc add*(parent, kid: VNode) = parent.kids.add kid
|
||||
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1)
|
||||
|
||||
|
|
@ -216,32 +223,33 @@ proc toString*(n: VNode; result: var string; indent: int) =
|
|||
for i in 1..indent: result.add ' '
|
||||
result.add "\L</" & $n.kind & ">"
|
||||
|
||||
proc calcHash*(n: VNode) =
|
||||
if n.validHash: return
|
||||
n.validHash = true
|
||||
var h: Hash = ord n.kind
|
||||
if n.id != nil:
|
||||
h &= "id"
|
||||
h &= n.id
|
||||
if n.class != nil:
|
||||
h &= "class"
|
||||
h &= n.class
|
||||
if n.key >= 0:
|
||||
h &= "k"
|
||||
h &= n.key
|
||||
for k, v in attrs(n):
|
||||
h &= " "
|
||||
h &= k
|
||||
h &= "="
|
||||
h &= v
|
||||
if n.kind == VNodeKind.text or n.text != nil:
|
||||
h &= "t"
|
||||
h &= n.text
|
||||
else:
|
||||
for child in items(n):
|
||||
calcHash(child)
|
||||
h &= child.hash
|
||||
n.hash = h
|
||||
when false:
|
||||
proc calcHash*(n: VNode) =
|
||||
if n.validHash: return
|
||||
n.validHash = true
|
||||
var h: Hash = ord n.kind
|
||||
if n.id != nil:
|
||||
h &= "id"
|
||||
h &= n.id
|
||||
if n.class != nil:
|
||||
h &= "class"
|
||||
h &= n.class
|
||||
if n.key >= 0:
|
||||
h &= "k"
|
||||
h &= n.key
|
||||
for k, v in attrs(n):
|
||||
h &= " "
|
||||
h &= k
|
||||
h &= "="
|
||||
h &= v
|
||||
if n.kind == VNodeKind.text or n.text != nil:
|
||||
h &= "t"
|
||||
h &= n.text
|
||||
else:
|
||||
for child in items(n):
|
||||
calcHash(child)
|
||||
h &= child.hash
|
||||
n.hash = h
|
||||
|
||||
proc `$`*(n: VNode): cstring =
|
||||
var res = ""
|
||||
|
|
|
|||
16
uibench/index.html
Normal file
16
uibench/index.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link href="https://localvoid.github.io/uibench-base/0.1.0/styles.css" rel="stylesheet" />
|
||||
<title>UI Benchmark: React</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="ROOT"></div>
|
||||
|
||||
<script src="https://localvoid.github.io/uibench-base/0.1.0/uibench.js"></script>
|
||||
<script src="nimcache/nim_uibench.js" type="text/javascript"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
105
uibench/nim_uibench.nim
Normal file
105
uibench/nim_uibench.nim
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import dom, vdom, karax, karaxdsl, jdict, jstrutils, kajax
|
||||
|
||||
type
|
||||
HomeState {.importc.} = ref object
|
||||
TableItemState {.importc.} = ref object
|
||||
id: int
|
||||
active: bool
|
||||
props: seq[cstring]
|
||||
TableState {.importc.} = ref object
|
||||
items: seq[TableItemState]
|
||||
AnimBoxState {.importc.} = ref object
|
||||
id: int
|
||||
time: int
|
||||
AnimState {.importc.} = ref object
|
||||
items: seq[AnimBoxState]
|
||||
TreeNodeState {.importc.} = ref object
|
||||
id: int
|
||||
container: bool
|
||||
children: seq[TreeNodeState]
|
||||
TreeState {.importc.} = ref object
|
||||
root: TreeNodeState
|
||||
AppState {.importc.} = ref object
|
||||
location: cstring
|
||||
home: HomeState
|
||||
table: TableState
|
||||
anim: AnimState
|
||||
tree: TreeState
|
||||
|
||||
var appState: AppState
|
||||
|
||||
proc createTableCell(id: cstring): VNode =
|
||||
proc tableCellClick(ev: karax.Event; n: VNode) =
|
||||
kout "Clicked" & id
|
||||
result = buildHtml(td(class="TableCell", onclick=tableCellClick)):
|
||||
text id
|
||||
|
||||
proc createTableRow(item: TableItemState): VNode =
|
||||
let className = if item.active: cstring"TableRow active" else: cstring"TableRow"
|
||||
result = buildHtml(tr(class=className, `data-id` = &item.id)):
|
||||
createTableCell("#" & &item.id)
|
||||
for i in 0..<len(item.props):
|
||||
createTableCell(item.props[i])
|
||||
|
||||
proc tableCreateVNode(data: TableState): VNode =
|
||||
result = buildHtml(table(class="Table")):
|
||||
tbody:
|
||||
for child in data.items:
|
||||
createTableRow(child)
|
||||
|
||||
proc createAnimBox(item: AnimBoxState): VNode =
|
||||
let time = item.time
|
||||
let t10 = time mod 10
|
||||
let color = float(t10) / 10
|
||||
let divStyles = cstring"border-radius: " & &t10 &
|
||||
"px; background: rgba(0,0,0," & cstring($color) & ")"
|
||||
result = flatHtml(tdiv(class="AnimBox", `data-id` = &item.id, style=divStyles))
|
||||
|
||||
proc animCreateVNode(data: AnimState): VNode =
|
||||
result = buildHtml(tdiv(class="Anim")):
|
||||
for child in data.items:
|
||||
createAnimBox(child)
|
||||
|
||||
proc createTreeNode(data: TreeNodeState): VNode =
|
||||
result = buildHtml(ul(class="TreeNode")):
|
||||
for n in data.children:
|
||||
if n.container:
|
||||
createTreeNode(n)
|
||||
else:
|
||||
li(class="TreeLeaf"):
|
||||
text &n.id
|
||||
|
||||
proc treeCreateVNode(data: TreeState): VNode =
|
||||
result = buildHtml(tdiv(class="Tree")):
|
||||
createTreeNode(data.root)
|
||||
|
||||
proc update(): VNode =
|
||||
assert appState != nil
|
||||
let location = appState.location
|
||||
var children: VNode = nil
|
||||
if location == cstring"table":
|
||||
children = tableCreateVNode(appState.table)
|
||||
elif location == cstring"anim":
|
||||
children = animCreateVNode(appState.anim)
|
||||
elif location == cstring"tree":
|
||||
children = treeCreateVNode(appState.tree)
|
||||
result = buildHtml():
|
||||
tdiv(class="Main"):
|
||||
children
|
||||
|
||||
proc a(state: AppState) =
|
||||
appState = state
|
||||
redrawForce()
|
||||
|
||||
proc myToJson[T](x: T): cstring {.importcpp: "JSON.stringify(#, null, ' ')".}
|
||||
|
||||
proc b(samples: RootRef) =
|
||||
document.body.innerHTML = cstring"<pre>" & myToJson(samples) & cstring"</pre>"
|
||||
|
||||
proc init*(a: cstring, b: cstring) {.importc: "uibench.init", nodecl.}
|
||||
proc run*(a: proc(state: AppState), b: proc(samples: RootRef)) {.importc: "uibench.run", nodecl.}
|
||||
|
||||
init(cstring"Nim-karax", cstring"0.6.1")
|
||||
|
||||
setRendererOnly update
|
||||
run(a, b)
|
||||
1
uibench/nim_uibench.nims
Normal file
1
uibench/nim_uibench.nims
Normal file
|
|
@ -0,0 +1 @@
|
|||
--path: "../src"
|
||||
Loading…
Add table
Add a link
Reference in a new issue