about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-01-22 23:33:36 +0100
committerbptato <nincsnevem662@gmail.com>2022-01-22 23:33:36 +0100
commit6ff61c5ad2ad2af36195b83582ed98be57b93f18 (patch)
tree899799dfea6cc07433aaf77985f2aefc9964b74a /src
parentc73367b5e940c184247908a22cd4a495de4a0fde (diff)
downloadchawan-6ff61c5ad2ad2af36195b83582ed98be57b93f18.tar.gz
Avoid unnecessary rendering on hover change etc
Diffstat (limited to 'src')
-rw-r--r--src/css/cascade.nim28
-rw-r--r--src/css/values.nim41
-rw-r--r--src/layout/box.nim2
-rw-r--r--src/layout/engine.nim56
-rw-r--r--src/types/color.nim3
5 files changed, 98 insertions, 32 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 8ce999e0..223a3ec5 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -27,14 +27,13 @@ proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) =
 
   case pseudo
   of PSEUDO_NONE:
-    elem.rendered = not elem.css.applyValue(parent, d)
+    elem.css.applyValue(parent, d)
   of PSEUDO_BEFORE, PSEUDO_AFTER:
     if elem.pseudo[pseudo] == nil:
       elem.pseudo[pseudo] = elem.css.inheritProperties()
-    elem.rendered = not elem.pseudo[pseudo].applyValue(parent, d)
+    elem.pseudo[pseudo].applyValue(parent, d)
 
   elem.cssapplied = true
-  elem.rendered = false
 
 func applies(mq: MediaQuery): bool =
   case mq.t
@@ -108,6 +107,23 @@ proc applyImportant(ares: var ApplyResult, decls: seq[CSSDeclaration]) =
     if decl.important:
       ares.important.add(decl)
 
+proc checkRendered(element: Element, prev: CSSSpecifiedValues, ppseudo: array[PSEUDO_BEFORE..PSEUDO_AFTER, CSSSpecifiedValues]) =
+  if element.rendered:
+    for p in PSEUDO_BEFORE..PSEUDO_AFTER:
+      if ppseudo[p] != element.pseudo[p] and ppseudo[p] == nil:
+        element.rendered = false
+        return
+    for t in CSSPropertyType:
+      if not element.css[t].equals(prev[t]):
+        element.rendered = false
+        return
+    for p in PSEUDO_BEFORE..PSEUDO_AFTER:
+      if ppseudo[p] != nil:
+        for t in CSSPropertyType:
+          if not element.pseudo[p][t].equals(ppseudo[p][t]):
+            element.rendered = false
+            return
+
 proc applyRules(element: Element, ua, user, author: RuleList, pseudo: PseudoElem) =
   var ares: ApplyResult
 
@@ -175,11 +191,13 @@ proc applyRules*(document: Document, ua, user: CSSStylesheet) =
       embedded_rules.add(rules_local)
 
     if not elem.cssapplied:
+      let prev = elem.css
+      let ppseudo = elem.pseudo
       if elem.parentElement != nil:
         elem.css = elem.parentElement.css.inheritProperties()
       else:
         elem.css = rootProperties()
-      for pseudo in [PSEUDO_BEFORE, PSEUDO_AFTER]:
+      for pseudo in PSEUDO_BEFORE..PSEUDO_AFTER:
         elem.pseudo[pseudo] = nil
 
       let uarules = calcRules(elem, ua)
@@ -190,6 +208,8 @@ proc applyRules*(document: Document, ua, user: CSSStylesheet) =
       for pseudo in PseudoElem:
         elem.applyRules(uarules, userrules, authorrules, pseudo)
 
+      elem.checkRendered(prev, ppseudo)
+
     for i in countdown(elem.children.high, 0):
       stack.add(elem.children[i])
 
diff --git a/src/css/values.nim b/src/css/values.nim
index 7da9a248..638dc2be 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -691,7 +691,26 @@ func getSpecifiedValue(d: CSSDeclaration, parent: CSSSpecifiedValues): tuple[a:C
 
   return (val, cssGlobal(d))
 
-proc applyValue(vals, parent: CSSSpecifiedValues, t: CSSPropertyType, val: CSSSpecifiedValue, global: CSSGlobalValueType): bool =
+func equals*(a, b: CSSSpecifiedValue): bool =
+  if a == b:
+    return true
+  if a == nil or b == nil:
+    return false
+  case valueType(a.t)
+  of VALUE_COLOR: return a.color == b.color
+  of VALUE_LENGTH: return a.length == b.length
+  of VALUE_FONT_STYLE: return a.fontstyle == b.fontstyle
+  of VALUE_DISPLAY: return a.display == b.display
+  of VALUE_CONTENT: return a.content == b.content
+  of VALUE_WHITESPACE: return a.whitespace == b.whitespace
+  of VALUE_INTEGER: return a.integer == a.integer
+  of VALUE_TEXT_DECORATION: return a.textdecoration == b.textdecoration
+  of VALUE_WORD_BREAK: return a.wordbreak == b.wordbreak
+  of VALUE_LIST_STYLE_TYPE: return a.liststyletype == b.liststyletype
+  of VALUE_NONE: return true
+  return false
+
+proc applyValue(vals, parent: CSSSpecifiedValues, t: CSSPropertyType, val: CSSSpecifiedValue, global: CSSGlobalValueType) =
   let oval = vals[t]
   case global
   of VALUE_INHERIT, VALUE_UNSET:
@@ -705,16 +724,8 @@ proc applyValue(vals, parent: CSSSpecifiedValues, t: CSSPropertyType, val: CSSSp
     vals[t] = getDefault(t) #TODO
   of VALUE_NOGLOBAL:
     vals[t] = val
-  return oval != vals[t]
 
-proc applyShorthand(vals, parent: CSSSpecifiedValues, left, right, top, bottom: CSSSpecifiedValue, global: CSSGlobalValueType): bool =
-  result = result or vals.applyValue(parent, left.t, left, global)
-  result = result or vals.applyValue(parent, right.t, right, global)
-  result = result or vals.applyValue(parent, top.t, top, global)
-  result = result or vals.applyValue(parent, bottom.t, bottom, global)
-
-# Returns true if anything has changed. (TODO: not always...)
-proc applyValue*(vals, parent: CSSSpecifiedValues, d: CSSDeclaration): bool =
+proc applyValue*(vals, parent: CSSSpecifiedValues, d: CSSDeclaration) =
   let vv = getSpecifiedValue(d, parent)
   let val = vv.a
   let oval = vals[val.t]
@@ -723,21 +734,23 @@ proc applyValue*(vals, parent: CSSSpecifiedValues, d: CSSDeclaration): bool =
     let global = cssGlobal(d)
     if global != VALUE_NOGLOBAL:
       for t in CSSPropertyType:
-        result = result or vals.applyValue(parent, t, nil, global)
+        vals.applyValue(parent, t, nil, global)
   of PROPERTY_MARGIN:
     let left = CSSSpecifiedValue(t: PROPERTY_MARGIN_LEFT, v: VALUE_LENGTH, length: val.length)
     let right = CSSSpecifiedValue(t: PROPERTY_MARGIN_RIGHT, v: VALUE_LENGTH, length: val.length)
     let top = CSSSpecifiedValue(t: PROPERTY_MARGIN_TOP, v: VALUE_LENGTH, length: val.length)
     let bottom = CSSSpecifiedValue(t: PROPERTY_MARGIN_BOTTOM, v: VALUE_LENGTH, length: val.length)
-    return vals.applyShorthand(parent, left, right, top, bottom, vv.b)
+    for val in [left, right, top, bottom]:
+      vals.applyValue(parent, val.t, val, vv.b)
   of PROPERTY_PADDING:
     let left = CSSSpecifiedValue(t: PROPERTY_PADDING_LEFT, v: VALUE_LENGTH, length: val.length)
     let right = CSSSpecifiedValue(t: PROPERTY_PADDING_RIGHT, v: VALUE_LENGTH, length: val.length)
     let top = CSSSpecifiedValue(t: PROPERTY_PADDING_TOP, v: VALUE_LENGTH, length: val.length)
     let bottom = CSSSpecifiedValue(t: PROPERTY_PADDING_BOTTOM, v: VALUE_LENGTH, length: val.length)
-    return vals.applyShorthand(parent, left, right, top, bottom, vv.b)
+    for val in [left, right, top, bottom]:
+      vals.applyValue(parent, val.t, val, vv.b)
   else:
-    return vals.applyValue(parent, val.t, vv.a, vv.b)
+    vals.applyValue(parent, val.t, vv.a, vv.b)
 
 func inheritProperties*(parent: CSSSpecifiedValues): CSSSpecifiedValues =
   new(result)
diff --git a/src/layout/box.nim b/src/layout/box.nim
index 67cb9be9..a49bde7f 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -17,6 +17,7 @@ type
     inlinelayout*: bool
     specified*: CSSSpecifiedValues
     node*: Node
+    element*: Element
 
   InlineAtom* = ref object of RootObj
     relx*: int
@@ -59,6 +60,7 @@ type
 
     compwidth*: int
     compheight*: Option[int]
+    done*: bool
 
   RowBox* = object
     x*: int
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 0063ac05..a0046f36 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -165,7 +165,6 @@ proc newBlockContext_common(parent: BlockContext, box: CSSBox): BlockContext {.i
 
 proc newBlockContext(parent: BlockContext, box: BlockBox): BlockContext =
   result = newBlockContext_common(parent, box)
-  parent.nested.add(result)
 
 proc newInlineBlockContext(parent: BlockContext, box: InlineBlockBox): BlockContext =
   newBlockContext_common(parent, box)
@@ -176,7 +175,6 @@ proc newBlockContext(parent: BlockContext): BlockContext =
   result.specified = parent.specified.inheritProperties()
   result.viewport = parent.viewport
   result.computedDimensions(parent.compwidth, parent.compheight)
-  parent.nested.add(result)
 
 # Anonymous block box (root).
 proc newBlockContext(viewport: Viewport): BlockContext =
@@ -306,13 +304,14 @@ proc alignBlocks(bctx: BlockContext, blocks: seq[CSSBox]) =
       let gctx = newBlockContext(bctx)
       gctx.alignInlines(blockgroup)
       blockgroup.setLen(0)
+      bctx.nested.add(gctx)
 
   for child in blocks:
     case child.t
     of DISPLAY_BLOCK, DISPLAY_LIST_ITEM:
       let child = BlockBox(child)
       flush_group()
-      child.bctx = newBlockContext(bctx, child)
+      bctx.nested.add(child.bctx)
       alignBlock(child)
     of DISPLAY_INLINE:
       if child.inlinelayout:
@@ -327,6 +326,9 @@ proc alignBlocks(bctx: BlockContext, blocks: seq[CSSBox]) =
   flush_group()
 
 proc alignBlock(box: BlockBox) =
+  if box.bctx.done:
+    return
+  box.bctx.done = true
   if box.node != nil:
     box.bctx.viewport.nodes.add(box.node)
   if box.inlinelayout:
@@ -378,11 +380,28 @@ proc getPseudoBox(specified: CSSSpecifiedValues): CSSBox =
     box.children.add(content)
   return box
 
-proc generateBox(elem: Element, viewport: Viewport, first = false): CSSBox =
+proc generateBox(elem: Element, viewport: Viewport, bctx: BlockContext = nil): CSSBox =
+  elem.rendered = true
   if viewport.map[elem.uid] != nil:
+    let box = viewport.map[elem.uid]
+    var bctx = bctx
+    if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM}:
+      let box = BlockBox(box)
+      if bctx == nil:
+        box.bctx = viewport.newBlockContext()
+      else:
+        box.bctx = bctx.newBlockContext(box)
+      bctx = box.bctx
+
+    var i = 0
+    while i < box.children.len:
+      let child = box.children[i]
+      if child.element != nil:
+        box.children[i] = generateBox(child.element, viewport, bctx)
+      inc i
     return viewport.map[elem.uid]
 
-  let box = if not first:
+  let box = if bctx != nil:
     getBox(elem.css)
   else:
     getBlockBox(elem.css)
@@ -391,6 +410,16 @@ proc generateBox(elem: Element, viewport: Viewport, first = false): CSSBox =
     return nil
 
   box.node = elem
+  box.element = elem
+
+  var bctx = bctx
+  if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM}:
+    let box = BlockBox(box)
+    if bctx == nil:
+      box.bctx = viewport.newBlockContext()
+    else:
+      box.bctx = bctx.newBlockContext(box)
+    bctx = box.bctx
 
   var ibox: InlineBox
   template add_ibox() =
@@ -429,13 +458,13 @@ proc generateBox(elem: Element, viewport: Viewport, first = false): CSSBox =
         ibox = box.getTextBox()
         ibox.newline = true
 
-      let cbox = elem.generateBox(viewport)
+      let cbox = elem.generateBox(viewport, bctx)
       if cbox != nil:
         add_ibox()
         add_box(cbox)
     of TEXT_NODE:
       let text = Text(child)
-      # Don't generate empty anonymous inline blocks.
+      # Don't generate empty anonymous inline blocks between block boxes
       if box.specified{"display"} == DISPLAY_INLINE or
           box.children.len > 0 and box.children[^1].specified{"display"} == DISPLAY_INLINE or
           box.specified{"white-space"} in {WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP} or
@@ -459,12 +488,11 @@ proc generateBox(elem: Element, viewport: Viewport, first = false): CSSBox =
 proc renderLayout*(viewport: var Viewport, document: Document) =
   if viewport.root == nil or document.all_elements.len != viewport.map.len:
     viewport.map = newSeq[CSSBox](document.all_elements.len)
-    viewport.root = BlockBox(document.root.generateBox(viewport, true))
   else:
-    for uid in 0..viewport.map.high:
-      if not document.all_elements[uid].rendered:
-        viewport.map[uid] = nil
-    viewport.root = BlockBox(document.root.generateBox(viewport))
-
-  viewport.root.bctx = viewport.newBlockContext()
+    var i = 0
+    while i < viewport.map.len:
+      if not document.all_elements[i].rendered:
+        viewport.map[i] = nil
+      inc i
+  viewport.root = BlockBox(document.root.generateBox(viewport))
   alignBlock(viewport.root)
diff --git a/src/types/color.nim b/src/types/color.nim
index 2690d35d..588e59d3 100644
--- a/src/types/color.nim
+++ b/src/types/color.nim
@@ -34,6 +34,9 @@ func a*(c: RGBAColor): int =
 func rgb*(r, g, b: int): RGBColor =
   return RGBColor((r shl 16) or (g shl 8) or b)
 
+func `==`*(a, b: RGBAColor): bool =
+  return int(a) == int(b)
+
 
 func r*(c: RGBColor): int =
   return int(c) shr 16 and 0xff