about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-12-13 11:52:13 +0100
committerbptato <nincsnevem662@gmail.com>2021-12-13 11:59:54 +0100
commite1194507b4f6240cb15c1783240f8a21d359bc16 (patch)
tree0076a000e8a00cb3605ef8b275bdcc66e9768d51 /src
parente46f0a4cb9b6a843e900dbb3abd5ce9684f47016 (diff)
downloadchawan-e1194507b4f6240cb15c1783240f8a21d359bc16.tar.gz
Support ::before, ::after pseudo elements
Diffstat (limited to 'src')
-rw-r--r--src/css/style.nim18
-rw-r--r--src/css/values.nim13
-rw-r--r--src/html/dom.nim4
-rw-r--r--src/io/buffer.nim12
-rw-r--r--src/layout/box.nim1
-rw-r--r--src/layout/engine.nim137
6 files changed, 156 insertions, 29 deletions
diff --git a/src/css/style.nim b/src/css/style.nim
index f64cdd45..258cf438 100644
--- a/src/css/style.nim
+++ b/src/css/style.nim
@@ -47,8 +47,8 @@ func pseudoSelectorMatches(elem: Element, sel: Selector): bool =
 
 func pseudoElemSelectorMatches(elem: Element, sel: Selector): SelectResult =
   case sel.elem
+  of "before": return selectres(true, PSEUDO_BEFORE)
   of "after": return selectres(true, PSEUDO_AFTER)
-  of "before": return selectres(true, PSEUDO_AFTER)
   else: return selectres(false)
 
 func selectorsMatch(elem: Element, selectors: SelectorList): SelectResult
@@ -198,10 +198,15 @@ proc applyProperty(elem: Element, decl: CSSDeclaration, pseudo: PseudoElem) =
   of PSEUDO_NONE:
     elem.cssvalues[cval.t] = cval
   of PSEUDO_BEFORE:
+    if elem.cssvalues_before == nil:
+      elem.cssvalues_before.rootProperties()
     elem.cssvalues_before[cval.t] = cval
   of PSEUDO_AFTER:
+    if elem.cssvalues_after == nil:
+      elem.cssvalues_after.rootProperties()
     elem.cssvalues_after[cval.t] = cval
   elem.cssapplied = true
+  elem.rendered = false
 
 type
   ParsedRule* = tuple[sels: seq[SelectorList], oblock: CSSSimpleBlock]
@@ -250,6 +255,8 @@ proc applyRules*(document: Document, pss: ParsedStylesheet, reset: bool = false)
     if not elem.cssapplied:
       if reset:
         elem.cssvalues.rootProperties()
+        elem.cssvalues_before = nil
+        elem.cssvalues_after = nil
       let rules_pseudo = calcRules(elem, pss)
       for pseudo in low(PseudoElem)..high(PseudoElem):
         let rules = rules_pseudo[pseudo]
@@ -356,3 +363,12 @@ proc applyStylesheets*(document: Document, uass: ParsedStylesheet, userss: Parse
   for elem in elems:
     if elem.parentElement != nil:
       elem.cssvalues.inheritProperties(elem.parentElement.cssvalues)
+      if elem.cssvalues_before != nil:
+        elem.cssvalues_before.inheritProperties(elem.cssvalues)
+      if elem.cssvalues_after != nil:
+        elem.cssvalues_after.inheritProperties(elem.cssvalues)
+
+proc refreshStyle*(elem: Element) =
+  elem.cssapplied = false
+  for child in elem.children:
+    child.refreshStyle()
diff --git a/src/css/values.nim b/src/css/values.nim
index b3a5d0d4..e68d7d15 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -41,7 +41,7 @@ type
       wordbreak*: CSSWordBreak
     of VALUE_NONE: discard
 
-  CSSComputedValues* = array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue]
+  CSSComputedValues* = ref array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue]
 
   CSSSpecifiedValue* = object of CSSComputedValue
     globalValue: CSSGlobalValueType
@@ -605,13 +605,16 @@ func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSS
 func getComputedValue*(d: CSSDeclaration, current: CSSComputedValues): CSSComputedValue =
   return getComputedValue(getSpecifiedValue(d), current)
 
+proc rootProperties*(vals: var CSSComputedValues) =
+  new(vals)
+  for prop in low(CSSPropertyType)..high(CSSPropertyType):
+    vals[prop] = getDefault(prop)
+
 proc inheritProperties*(vals: var CSSComputedValues, parent: CSSComputedValues) =
+  if vals == nil:
+    new(vals)
   for prop in low(CSSPropertyType)..high(CSSPropertyType):
     if vals[prop] == nil:
       vals[prop] = getDefault(prop)
     if inherited(prop) and parent[prop] != nil and vals[prop] == getDefault(prop):
       vals[prop] = parent[prop]
-
-proc rootProperties*(vals: var CSSComputedValues) =
-  for prop in low(CSSPropertyType)..high(CSSPropertyType):
-    vals[prop] = getDefault(prop)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 4a63b2d0..f2c98bd0 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -68,6 +68,7 @@ type
     cssvalues_after*: CSSComputedValues
     hover*: bool
     cssapplied*: bool
+    rendered*: bool
 
   HTMLElement* = ref HTMLElementObj
   HTMLElementObj = object of ElementObj
@@ -215,10 +216,11 @@ func newHtmlElement*(tagType: TagType): HTMLElement =
   of TAG_SPAN:
     result = new(HTMLSpanElement)
   else:
-    new(result)
+    result = new(HTMLElement)
 
   result.nodeType = ELEMENT_NODE
   result.tagType = tagType
+  result.cssvalues.rootProperties()
 
 func newDocument*(): Document =
   new(result)
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index 893f66a9..1033e44c 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -669,10 +669,10 @@ proc updateHover(buffer: Buffer) =
         elem = node.parentElement
         assert elem != nil
 
-      if not elem.hover:
+      if not elem.hover and not (node in buffer.prevnodes):
         elem.hover = true
         buffer.reshape = true
-        elem.cssapplied = false
+        elem.refreshStyle()
     for node in buffer.prevnodes:
       var elem: Element
       if node of Element:
@@ -683,7 +683,7 @@ proc updateHover(buffer: Buffer) =
       if elem.hover and not (node in nodes):
         elem.hover = false
         buffer.reshape = true
-        elem.cssapplied = false
+        elem.refreshStyle()
   buffer.prevnodes = nodes
 
 proc renderPlainText*(buffer: Buffer, text: string) =
@@ -900,5 +900,11 @@ proc displayPage*(attrs: TermAttributes, buffer: Buffer): bool =
   discard buffer.gotoAnchor()
   buffer.refreshDisplay()
   buffer.displayBuffer()
+  buffer.updateHover()
+  if buffer.reshape:
+    buffer.reshapeBuffer()
+    buffer.reshape = false
+    buffer.refreshDisplay()
+    buffer.displayBufferSwapOutput()
   buffer.statusMsgForBuffer()
   return inputLoop(attrs, buffer)
diff --git a/src/layout/box.nim b/src/layout/box.nim
index cfbf8506..4e31b1e1 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -19,6 +19,7 @@ type
     icontext*: InlineContext
     bcontext*: BlockContext
     cssvalues*: CSSComputedValues
+    node*: Node
 
   #TODO move fromy
   InlineContext* = ref object
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 81ae1420..277bc70d 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -280,6 +280,19 @@ func isInline(node: Node): bool =
             elem.cssvalues[PROPERTY_DISPLAY].display == DISPLAY_INLINE_BLOCK
   return false
 
+proc processComputedValueBox(state: var LayoutState, parent: CSSBox, values: CSSComputedValues): CSSBox =
+  case values[PROPERTY_DISPLAY].display
+  of DISPLAY_BLOCK:
+    #eprint "START", elem.tagType, parent.icontext.fromy
+    result = state.newBlockBox(parent, values)
+    #CSSBlockBox(result).tag = $elem.tagType
+  of DISPLAY_INLINE:
+    result = newInlineBox(parent, values)
+  of DISPLAY_NONE:
+    return nil
+  else:
+    return nil
+
 proc processElemBox(state: var LayoutState, parent: CSSBox, elem: Element): CSSBox =
   if elem.tagType == TAG_BR:
     if parent.icontext.conty:
@@ -289,17 +302,10 @@ proc processElemBox(state: var LayoutState, parent: CSSBox, elem: Element): CSSB
     else:
       inc parent.icontext.fromy
     parent.icontext.fromx = parent.x
-  case elem.cssvalues[PROPERTY_DISPLAY].display
-  of DISPLAY_BLOCK:
-    #eprint "START", elem.tagType, parent.icontext.fromy
-    result = state.newBlockBox(parent, elem.cssvalues)
-    #CSSBlockBox(result).tag = $elem.tagType
-  of DISPLAY_INLINE:
-    result = newInlineBox(parent, elem.cssvalues)
-  of DISPLAY_NONE:
-    return nil
-  else:
-    return nil
+
+  result = state.processComputedValueBox(parent, elem.cssvalues)
+  if result != nil:
+    result.node = elem
 
 proc processNodes(state: var LayoutState, parent: CSSBox, node: Node)
 
@@ -309,13 +315,30 @@ proc processNode(state: var LayoutState, parent: CSSBox, node: Node): CSSBox =
     result = state.processElemBox(parent, Element(node))
     if result == nil:
       return
+
     state.processNodes(result, node)
   of TEXT_NODE:
     let text = Text(node)
     result = state.processInlineBox(parent, text.data)
+    if result != nil:
+      result.node = node
   else: discard
 
-template processAnonBlock(state: var LayoutState, parent: CSSBox, c: Node) =
+proc processAnonComputedValues(state: var LayoutState, parent: CSSBox, c: CSSComputedValues): bool =
+  if parent.bcontext.has_blocks:
+    if c[PROPERTY_DISPLAY].display == DISPLAY_INLINE:
+      if parent.bcontext.anon_block == nil:
+        var cssvals: CSSComputedValues
+        cssvals.inheritProperties(parent.cssvalues)
+        parent.bcontext.anon_block = state.newBlockBox(parent, cssvals)
+      state.add(parent.bcontext.anon_block, state.processComputedValueBox(parent.bcontext.anon_block, c))
+      return true
+    elif parent.bcontext.anon_block != nil:
+      state.add(parent, parent.bcontext.anon_block)
+      parent.bcontext.anon_block = nil
+  return false
+
+proc processAnonBlock(state: var LayoutState, parent: CSSBox, c: Node): bool =
   if parent.bcontext.has_blocks:
     if c.isInline():
       if parent.bcontext.anon_block == nil:
@@ -323,22 +346,94 @@ template processAnonBlock(state: var LayoutState, parent: CSSBox, c: Node) =
         cssvals.inheritProperties(parent.cssvalues)
         parent.bcontext.anon_block = state.newBlockBox(parent, cssvals)
       state.add(parent.bcontext.anon_block, state.processNode(parent.bcontext.anon_block, c))
-      continue
+      return true
     elif parent.bcontext.anon_block != nil:
       state.add(parent, parent.bcontext.anon_block)
       parent.bcontext.anon_block = nil
+  return false
 
-proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
-  state.nodes.add(node)
+func needsAnonymousBlockBoxes(node: Node): bool =
+  if node.nodeType == ELEMENT_NODE:
+    let elem = Element(node)
+    if elem.cssvalues_before != nil:
+      if elem.cssvalues_before[PROPERTY_DISPLAY].display == DISPLAY_BLOCK:
+        return true
+    if elem.cssvalues_after != nil:
+      if elem.cssvalues_after[PROPERTY_DISPLAY].display == DISPLAY_BLOCK:
+        return true
 
   for c in node.childNodes:
     if c.isBlock():
-      parent.bcontext.has_blocks = true
+      return true
+
+  return false
+
+# ugh this is ugly, but it works...
+# basically this
+# * checks if there's a ::before pseudo element
+# * checks if we need to wrap things in anonymous block boxes
+# * in case we do, it adds the text to the anonymous box
+# * in case we don't, it tries to add the text to a new parent box
+# * but only if a new parent box is needed.
+proc processBeforePseudoElem(state: var LayoutState, parent: CSSBox, node: Node) =
+  if node.nodeType == ELEMENT_NODE:
+    let elem = Element(node)
+
+    if elem.cssvalues_before != nil:
+      var box: CSSBox
+      if not state.processAnonComputedValues(parent, elem.cssvalues_before):
+        box = state.processComputedValueBox(parent, elem.cssvalues_before)
+        if box != nil:
+          box.node = node
+      else:
+        box = parent.bcontext.anon_block
+
+      let text = elem.cssvalues_before[PROPERTY_CONTENT].content
+      var inline = state.processInlineBox(box, $text)
+      if inline != nil:
+        inline.node = node
+        state.add(box, inline)
+
+      if box != parent.bcontext.anon_block:
+        state.add(parent, box)
+
+# same as before except it's after
+proc processAfterPseudoElem(state: var LayoutState, parent: CSSBox, node: Node) =
+  if node.nodeType == ELEMENT_NODE:
+    let elem = Element(node)
+
+    if elem.cssvalues_after != nil:
+      var box: CSSBox
+      if not state.processAnonComputedValues(parent, elem.cssvalues_after):
+        box = state.processComputedValueBox(parent, elem.cssvalues_after)
+        if box != nil:
+          box.node = node
+      else:
+        box = parent.bcontext.anon_block
+
+      let text = elem.cssvalues_after[PROPERTY_CONTENT].content
+      var inline = state.processInlineBox(box, $text)
+      if inline != nil:
+        inline.node = node
+        state.add(box, inline)
+
+      if box != parent.bcontext.anon_block:
+        state.add(parent, box)
+
+proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
+  state.nodes.add(node)
+
+  parent.bcontext.has_blocks = node.needsAnonymousBlockBoxes()
+
+  state.processBeforePseudoElem(parent, node)
 
   for c in node.childNodes:
-    state.processAnonBlock(parent, c)
-    let box = state.processNode(parent, c)
-    state.add(parent, box)
+    let isanon = state.processAnonBlock(parent, c)
+    if not isanon:
+      let box = state.processNode(parent, c)
+      state.add(parent, box)
+
+  state.processAfterPseudoElem(parent, node)
 
   if parent.bcontext.anon_block != nil:
     state.add(parent, parent.bcontext.anon_block)
@@ -349,8 +444,12 @@ proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
 proc alignBoxes*(document: Document, width: int, height: int): CSSBox =
   var state: LayoutState
   var rootbox = CSSBlockBox(x: 0, y: 0, width: width, height: 0)
+  rootbox.cssvalues = document.root.cssvalues
   rootbox.icontext = newInlineContext(rootbox)
   rootbox.bcontext = newBlockContext()
   state.nodes.add(document.root)
   state.processNodes(rootbox, document.root)
   return rootbox
+
+proc realignBoxes*(document: Document, width, height: int) =
+  var state: LayoutState