diff options
author | bptato <nincsnevem662@gmail.com> | 2021-12-13 11:52:13 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-12-13 11:59:54 +0100 |
commit | e1194507b4f6240cb15c1783240f8a21d359bc16 (patch) | |
tree | 0076a000e8a00cb3605ef8b275bdcc66e9768d51 /src | |
parent | e46f0a4cb9b6a843e900dbb3abd5ce9684f47016 (diff) | |
download | chawan-e1194507b4f6240cb15c1783240f8a21d359bc16.tar.gz |
Support ::before, ::after pseudo elements
Diffstat (limited to 'src')
-rw-r--r-- | src/css/style.nim | 18 | ||||
-rw-r--r-- | src/css/values.nim | 13 | ||||
-rw-r--r-- | src/html/dom.nim | 4 | ||||
-rw-r--r-- | src/io/buffer.nim | 12 | ||||
-rw-r--r-- | src/layout/box.nim | 1 | ||||
-rw-r--r-- | src/layout/engine.nim | 137 |
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 |