about summary refs log tree commit diff stats
path: root/src/css/stylednode.nim
blob: 127ce41c6a2fe7588dbb19de2c18ce18e1f30f6a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import css/cssvalues
import css/selectorparser
import html/dom

# Container to hold a style and a node.
# Pseudo-elements are implemented using StyledNode objects without nodes. Input
# elements are implemented as internal "pseudo-elements."
#
# To avoid having to invalidate the entire tree on pseudo-class changes, each
# node holds a list of nodes their CSS values depend on. (This list may include
# the node itself.) In addition, nodes also store each value valid for
# dependency d. These are then used for checking the validity of StyledNodes.
#
# In other words - say we have to apply the author stylesheets of the following
# document:
#
# <style>
# div:hover { color: red; }
# :not(input:checked) + p { display: none; }
# </style>
# <div>This div turns red on hover.</div>
# <input type=checkbox>
# <p>This paragraph is only shown when the checkbox above is checked.
#
# That produces the following dependency graph (simplified):
# div -> div (hover)
# p -> input (checked)
#
# Then, to check if a node has been invalidated, we just iterate over all
# recorded dependencies of each StyledNode, and check if their registered value
# of the pseudo-class still matches that of its associated element.
#
# So in our example, for div we check if div's :hover pseudo-class has changed,
# for p we check whether input's :checked pseudo-class has changed.

type
  StyledType* = enum
    stElement, stText, stReplacement

  DependencyInfo = array[DependencyType, seq[Element]]

  StyledNode* = ref object
    parent*: StyledNode
    node*: Node
    pseudo*: PseudoElem
    case t*: StyledType
    of stText:
      discard
    of stElement:
      computed*: CSSComputedValues
      children*: seq[StyledNode]
      # All elements we depend on, for each dependency type d.
      depends*: DependencyInfo
    of stReplacement:
      # replaced elements: quotes, or (TODO) markers, images
      content*: CSSContent

template textData*(styledNode: StyledNode): string =
  CharacterData(styledNode.node).data

# For debugging
func `$`*(node: StyledNode): string =
  if node == nil:
    return "nil"
  case node.t
  of stText:
    return "#text " & node.textData
  of stElement:
    if node.node != nil:
      return $node.node
    return $node.pseudo
  of stReplacement:
    return "#replacement"

iterator branch*(node: StyledNode): StyledNode {.inline.} =
  var node = node
  while node != nil:
    yield node
    node = node.parent

iterator elementList*(node: StyledNode): StyledNode {.inline.} =
  for child in node.children:
    yield child

iterator elementList_rev*(node: StyledNode): StyledNode {.inline.} =
  for i in countdown(node.children.high, 0):
    yield node.children[i]

func findElement*(root: StyledNode; element: Element): StyledNode =
  var stack: seq[StyledNode] = @[]
  for child in root.elementList_rev:
    if child.t == stElement and child.pseudo == peNone:
      stack.add(child)
  while stack.len > 0:
    let node = stack.pop()
    if node.node == element:
      return node
    for child in node.elementList_rev:
      if child.t == stElement and child.pseudo == peNone:
        stack.add(child)

func isDomElement*(styledNode: StyledNode): bool {.inline.} =
  styledNode.t == stElement and styledNode.pseudo == peNone

# DOM-style getters, for Element interoperability...
func parentElement*(node: StyledNode): StyledNode {.inline.} =
  node.parent

proc isValid*(styledNode: StyledNode; toReset: var seq[Element]): bool =
  if styledNode.t in {stText, stReplacement}:
    return true
  if styledNode.node != nil:
    let element = Element(styledNode.node)
    if element.invalid:
      toReset.add(element)
      return false
  for d in DependencyType:
    for dep in styledNode.depends[d]:
      if d in dep.invalidDeps:
        toReset.add(dep)
        return false
  return true

proc addDependency*(styledNode: StyledNode; dep: Element; t: DependencyType) =
  if dep notin styledNode.depends[t]:
    styledNode.depends[t].add(dep)

func newStyledElement*(parent: StyledNode; element: Element): StyledNode =
  return StyledNode(t: stElement, node: element, parent: parent)

# Root
func newStyledElement*(element: Element): StyledNode =
  return StyledNode(t: stElement, node: element)

func newStyledElement*(parent: StyledNode; pseudo: PseudoElem;
    computed: CSSComputedValues): StyledNode =
  return StyledNode(
    t: stElement,
    computed: computed,
    pseudo: pseudo,
    parent: parent
  )

func newStyledText*(parent: StyledNode; text: Text): StyledNode =
  return StyledNode(t: stText, node: text, parent: parent)

func newStyledText*(text: string): StyledNode =
  return StyledNode(t: stText, node: CharacterData(data: text))

func newStyledReplacement*(parent: StyledNode; content: CSSContent;
    pseudo: PseudoElem): StyledNode =
  return StyledNode(
    t: stReplacement,
    parent: parent,
    content: content,
    pseudo: pseudo
  )