diff options
author | bptato <nincsnevem662@gmail.com> | 2021-11-19 21:42:39 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-11-19 21:52:31 +0100 |
commit | 8bbff1f79920fa8175da7425f8f23ad08b97f79e (patch) | |
tree | 830abcc6918f8c96bce5538eff5384518876e378 /src | |
parent | 42aacf6bf1a52a8ebad902d8ee5adeef57f8822a (diff) | |
download | chawan-8bbff1f79920fa8175da7425f8f23ad08b97f79e.tar.gz |
User stylesheets and applyStylesheets optimizations
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config.nim | 61 | ||||
-rw-r--r-- | src/css/style.nim | 38 | ||||
-rw-r--r-- | src/html/dom.nim | 142 | ||||
-rw-r--r-- | src/io/buffer.nim | 60 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 26 |
5 files changed, 216 insertions, 111 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index d055af53..89f80fae 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -37,10 +37,12 @@ type StaticConfig = object nmap: ActionMap lemap: ActionMap + stylesheet*: string Config = object nmap*: ActionMap lemap*: ActionMap + stylesheet*: string func getConfig(s: StaticConfig): Config = return Config(nmap: s.nmap, lemap: s.lemap) @@ -105,15 +107,53 @@ func constructActionTable*(origTable: ActionMap): ActionMap = newTable[realk] = v return newTable +proc readUserStylesheet(dir: string, file: string): string = + if file.len == 0: + return "" + if file[0] == '~' or file[0] == '/': + var f: File + if f.open(expandPath(file)): + result = f.readAll() + f.close() + else: + var f: File + if f.open(dir / file): + result = f.readAll() + f.close() + proc parseConfigLine[T](line: string, config: var T) = if line.len == 0 or line[0] == '#': return - let cmd = line.split(' ') + var cmd: seq[string] + var s = "" + var quote = false + var escape = false + for c in line: + if escape: + escape = false + s &= c + continue + + if not quote and c == ' ' and s.len > 0: + cmd.add(s) + s = "" + elif c == '"': + quote = not quote + elif c == '\\' and not quote: + escape = true + else: + s &= c + if s.len > 0: + cmd.add(s) + if cmd.len == 3: if cmd[0] == "nmap": config.nmap[getRealKey(cmd[1])] = parseEnum[TwtAction]("ACTION_" & cmd[2]) elif cmd[0] == "lemap": config.lemap[getRealKey(cmd[1])] = parseEnum[TwtAction]("ACTION_" & cmd[2]) + elif cmd.len == 2: + if cmd[0] == "stylesheet": + config.stylesheet = cmd[1] proc staticReadConfig(): StaticConfig = let default = staticRead"res/config" @@ -124,25 +164,26 @@ proc staticReadConfig(): StaticConfig = result.lemap = constructActionTable(result.lemap) const defaultConfig = staticReadConfig() -var gconfig* = getConfig(defaultConfig) +var gconfig*: Config -proc readConfig(filename: string) = +proc readConfig(dir: string) = var f: File - let status = f.open(filename, fmRead) - var nmap: ActionMap - var lemap: ActionMap + let status = f.open(dir / "config", fmRead) if status: var line: TaintedString while f.readLine(line): parseConfigLine(line, gconfig) - gconfig.nmap = constructActionTable(nmap) - gconfig.lemap = constructActionTable(lemap) + gconfig.nmap = constructActionTable(gconfig.nmap) + gconfig.lemap = constructActionTable(gconfig.lemap) + gconfig.stylesheet = readUserStylesheet(dir, gconfig.stylesheet) + f.close() proc readConfig*() = + gconfig = getConfig(defaultConfig) when defined(debug): - readConfig("res" / "config") - readConfig(getConfigDir() / "twt" / "config") + readConfig(getCurrentDir() / "res") + readConfig(getConfigDir() / "twt") proc getNormalAction*(s: string): TwtAction = if gconfig.nmap.hasKey(s): diff --git a/src/css/style.nim b/src/css/style.nim index 9611f862..f018f077 100644 --- a/src/css/style.nim +++ b/src/css/style.nim @@ -12,11 +12,9 @@ type unit*: CSSUnit auto*: bool - CSSComputedValues* = array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue] - CSSColor* = tuple[r: uint8, g: uint8, b: uint8, a: uint8] - CSSComputedValue* = object of RootObj + CSSComputedValue* = ref object of RootObj t*: CSSPropertyType case v*: CSSValueType of VALUE_COLOR: @@ -37,6 +35,8 @@ type textdecoration*: CSSTextDecoration of VALUE_NONE: discard + CSSComputedValues* = array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue] + CSSSpecifiedValue* = object of CSSComputedValue globalValue: CSSGlobalValueType @@ -59,22 +59,22 @@ const PropertyNames = { "text-decoration": PROPERTY_TEXT_DECORATION, }.toTable() -const ValueTypes = { +const ValueTypes = [ PROPERTY_NONE: VALUE_NONE, PROPERTY_ALL: VALUE_NONE, PROPERTY_COLOR: VALUE_COLOR, PROPERTY_MARGIN: VALUE_LENGTH, PROPERTY_MARGIN_TOP: VALUE_LENGTH, - PROPERTY_MARGIN_BOTTOM: VALUE_LENGTH, PROPERTY_MARGIN_LEFT: VALUE_LENGTH, PROPERTY_MARGIN_RIGHT: VALUE_LENGTH, + PROPERTY_MARGIN_BOTTOM: VALUE_LENGTH, PROPERTY_FONT_STYLE: VALUE_FONT_STYLE, PROPERTY_DISPLAY: VALUE_DISPLAY, PROPERTY_CONTENT: VALUE_CONTENT, PROPERTY_WHITE_SPACE: VALUE_WHITE_SPACE, PROPERTY_FONT_WEIGHT: VALUE_INTEGER, PROPERTY_TEXT_DECORATION: VALUE_TEXT_DECORATION, -}.toTable() +] const InheritedProperties = { PROPERTY_COLOR, PROPERTY_FONT_STYLE, PROPERTY_WHITE_SPACE, @@ -365,7 +365,7 @@ func getInitialColor*(t: CSSPropertyType): CSSColor = else: return (r: 0u8, g: 0u8, b: 0u8, a: 255u8) -func getDefault(t: CSSPropertyType): CSSComputedValue = +func calcDefault(t: CSSPropertyType): CSSComputedValue = let v = valueType(t) var nv: CSSComputedValue case v @@ -377,6 +377,16 @@ func getDefault(t: CSSPropertyType): CSSComputedValue = nv = CSSComputedValue(t: t, v: v) return nv +func getDefaultTable(): array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue] = + for i in low(result)..high(result): + result[i] = calcDefault(i) + +let defaultTable = getDefaultTable() + +func getDefault(t: CSSPropertyType): CSSComputedValue = {.cast(noSideEffect).}: + assert defaultTable[t] != nil + return defaultTable[t] + func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSSComputedValue = case prop.globalValue of VALUE_INHERIT: @@ -415,13 +425,13 @@ func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSS func getComputedValue*(d: CSSDeclaration, current: CSSComputedValues): CSSComputedValue = return getComputedValue(getSpecifiedValue(d), current) -func inheritProperties*(parent: CSSComputedValues): CSSComputedValues = +proc inheritProperties*(vals: var CSSComputedValues, parent: CSSComputedValues) = for prop in low(CSSPropertyType)..high(CSSPropertyType): - if inherited(prop): - result[prop] = parent[prop] - else: - result[prop] = getDefault(prop) + if vals[prop] == nil: + vals[prop] = getDefault(prop) + if inherited(prop) and parent[prop] != nil and vals[prop] == getDefault(prop): + vals[prop] = parent[prop] -func rootProperties*(): CSSComputedValues = +proc rootProperties*(vals: var CSSComputedValues) = for prop in low(CSSPropertyType)..high(CSSPropertyType): - result[prop] = getDefault(prop) + vals[prop] = getDefault(prop) diff --git a/src/html/dom.nim b/src/html/dom.nim index a807f824..db7b9d89 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -12,9 +12,6 @@ import css/parser import css/selector import types/enums -const css = staticRead"res/default.css" -let stylesheet = parseCSS(newStringStream(css)) - type EventTarget* = ref EventTargetObj EventTargetObj = object of RootObj @@ -31,9 +28,6 @@ type parentElement*: Element ownerDocument*: Document - hidden*: bool - hover*: bool - Attr* = ref AttrObj AttrObj = object of NodeObj namespaceURI*: string @@ -80,6 +74,8 @@ type cssvalues*: CSSComputedValues cssvalues_before*: CSSComputedValues cssvalues_after*: CSSComputedValues + hover*: bool + cssapplied*: bool HTMLElement* = ref HTMLElementObj HTMLElementObj = object of ElementObj @@ -140,13 +136,6 @@ func lastElementChild(node: Node): Element = func `$`*(element: Element): string = return "Element of " & $element.tagType -#TODO -func nodeAttr*(node: Node): HtmlElement = - case node.nodeType - of TEXT_NODE: return HtmlElement(node.parentElement) - of ELEMENT_NODE: return HtmlElement(node) - else: assert(false) - func isTextNode*(node: Node): bool = return node.nodeType == TEXT_NODE @@ -194,20 +183,6 @@ func toInputType*(str: string): InputType = of "week": INPUT_WEEK else: INPUT_UNKNOWN -func toInputSize*(str: string): int = - if str.len == 0: - return 20 - for c in str: - if not c.isDigit(): - return 20 - return str.parseInt() - -#TODO -func ancestor*(htmlNode: Node, tagType: TagType): HtmlElement = - result = HtmlElement(htmlNode.parentElement) - while result != nil and result.tagType != tagType: - result = HtmlElement(result.parentElement) - func newText*(): Text = new(result) result.nodeType = TEXT_NODE @@ -260,7 +235,6 @@ func getAttrValue*(element: Element, s: string): string = #TODO case sensitivity - type SelectResult = object success: bool pseudo: PseudoElem @@ -377,7 +351,6 @@ proc querySelector*(document: Document, q: string): seq[Element] = for sel in selectors: result.add(document.selectElems(sel)) - proc applyProperty(elem: Element, decl: CSSDeclaration, pseudo: PseudoElem) = let cval = getComputedValue(decl, elem.cssvalues) case pseudo @@ -387,9 +360,14 @@ proc applyProperty(elem: Element, decl: CSSDeclaration, pseudo: PseudoElem) = elem.cssvalues_before[cval.t] = cval of PSEUDO_AFTER: elem.cssvalues_after[cval.t] = cval + elem.cssapplied = true -type ParsedRule = tuple[sels: seq[SelectorList], oblock: CSSSimpleBlock] -type ParsedStylesheet = seq[ParsedRule] +type + ParsedRule* = tuple[sels: seq[SelectorList], oblock: CSSSimpleBlock] + ParsedStylesheet* = seq[ParsedRule] + ApplyResult = object + normal: seq[tuple[e:Element,d:CSSDeclaration,p:PseudoElem]] + important: seq[tuple[e:Element,d:CSSDeclaration,p:PseudoElem]] func calcRules(elem: Element, rules: ParsedStylesheet): array[low(PseudoElem)..high(PseudoElem), seq[CSSSimpleBlock]] = @@ -405,29 +383,30 @@ func calcRules(elem: Element, rules: ParsedStylesheet): tosorts[i].sort((x, y) => cmp(x.s,y.s)) result[i] = tosorts[i].map((x) => x.b) -proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element,d:CSSDeclaration,p:PseudoElem]] = +proc applyRules*(document: Document, pss: ParsedStylesheet, reset: bool = false): ApplyResult = var stack: seq[Element] stack.add(document.head) stack.add(document.body) - document.root.cssvalues = rootProperties() + document.root.cssvalues.rootProperties() - let parsed = rules.value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) while stack.len > 0: let elem = stack.pop() - elem.cssvalues = inheritProperties(elem.parentElement.cssvalues) - let rules_pseudo = calcRules(elem, parsed) - for pseudo in low(PseudoElem)..high(PseudoElem): - let rules = rules_pseudo[pseudo] - for rule in rules: - let decls = parseCSSListOfDeclarations(rule.value) - for item in decls: - if item of CSSDeclaration: - let decl = CSSDeclaration(item) - if decl.important: - result.add((elem, decl, pseudo)) - else: - elem.applyProperty(decl, pseudo) + if not elem.cssapplied: + if reset: + elem.cssvalues.rootProperties() + let rules_pseudo = calcRules(elem, pss) + for pseudo in low(PseudoElem)..high(PseudoElem): + let rules = rules_pseudo[pseudo] + for rule in rules: + let decls = parseCSSListOfDeclarations(rule.value) + for item in decls: + if item of CSSDeclaration: + let decl = CSSDeclaration(item) + if decl.important: + result.important.add((elem, decl, pseudo)) + else: + result.normal.add((elem, decl, pseudo)) var i = elem.children.len - 1 while i >= 0: @@ -435,7 +414,7 @@ proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element, stack.add(child) dec i -proc applyAuthorRules*(document: Document): seq[tuple[e:Element,d:CSSDeclaration,p:PseudoElem]] = +proc applyAuthorRules*(document: Document): ApplyResult = var stack: seq[Element] var embedded_rules: seq[ParsedStylesheet] @@ -448,7 +427,6 @@ proc applyAuthorRules*(document: Document): seq[tuple[e:Element,d:CSSDeclaration if ct.nodeType == TEXT_NODE: rules_head &= Text(ct).data - stack.setLen(0) stack.add(document.body) @@ -469,20 +447,22 @@ proc applyAuthorRules*(document: Document): seq[tuple[e:Element,d:CSSDeclaration if rules_local.len > 0: let parsed = parseCSS(newStringStream(rules_local)).value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) embedded_rules.add(parsed) - let this_rules = embedded_rules.concat() - let rules_pseudo = calcRules(elem, this_rules) - - for pseudo in low(PseudoElem)..high(PseudoElem): - let rules = rules_pseudo[pseudo] - for rule in rules: - let decls = parseCSSListOfDeclarations(rule.value) - for item in decls: - if item of CSSDeclaration: - let decl = CSSDeclaration(item) - if decl.important: - result.add((elem, decl, pseudo)) - else: - elem.applyProperty(decl, pseudo) + + if not elem.cssapplied: + let this_rules = embedded_rules.concat() + let rules_pseudo = calcRules(elem, this_rules) + + for pseudo in low(PseudoElem)..high(PseudoElem): + let rules = rules_pseudo[pseudo] + for rule in rules: + let decls = parseCSSListOfDeclarations(rule.value) + for item in decls: + if item of CSSDeclaration: + let decl = CSSDeclaration(item) + if decl.important: + result.important.add((elem, decl, pseudo)) + else: + result.normal.add((elem, decl, pseudo)) var i = elem.children.len - 1 while i >= 0: @@ -493,11 +473,37 @@ proc applyAuthorRules*(document: Document): seq[tuple[e:Element,d:CSSDeclaration if rules_local.len > 0: discard embedded_rules.pop() -proc applyStylesheets*(document: Document) = - let important_ua = document.applyRules(stylesheet) - let important_author = document.applyAuthorRules() - for rule in important_author: +proc applyStylesheets*(document: Document, uass: ParsedStylesheet, userss: ParsedStylesheet) = + let ua = document.applyRules(uass, true) + let user = document.applyRules(userss) + let author = document.applyAuthorRules() + var elems: seq[Element] + + for rule in ua.normal: + if not rule.e.cssapplied: + elems.add(rule.e) + rule.e.applyProperty(rule.d, rule.p) + for rule in user.normal: + if not rule.e.cssapplied: + elems.add(rule.e) + rule.e.applyProperty(rule.d, rule.p) + for rule in author.normal: + if not rule.e.cssapplied: + elems.add(rule.e) + rule.e.applyProperty(rule.d, rule.p) + + for rule in author.important: + if not rule.e.cssapplied: + elems.add(rule.e) + rule.e.applyProperty(rule.d, rule.p) + for rule in user.important: + if not rule.e.cssapplied: + elems.add(rule.e) rule.e.applyProperty(rule.d, rule.p) - for rule in important_ua: + for rule in ua.important: + if not rule.e.cssapplied: + elems.add(rule.e) rule.e.applyProperty(rule.d, rule.p) + for elem in elems: + elem.cssvalues.inheritProperties(elem.parentElement.cssvalues) diff --git a/src/io/buffer.nim b/src/io/buffer.nim index f38d1b24..6b72a401 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -2,17 +2,22 @@ import terminal import uri import strutils import unicode +import streams +import sequtils +import sugar import types/enums import css/style +import css/parser +import css/selector import utils/twtstr import html/dom import layout/box +import layout/engine import config/config import io/term import io/lineedit import io/cell -import layout/engine type Buffer* = ref BufferObj @@ -32,7 +37,6 @@ type fromy*: int attrs*: TermAttributes document*: Document - displaycontrols*: bool redraw*: bool reshape*: bool location*: Uri @@ -611,28 +615,35 @@ proc updateCursor(buffer: Buffer) = if buffer.lines.len == 0: buffer.cursory = 0 -#TODO this works, but needs rethinking: -#* reshape is called every time the cursor moves onto or off a line box, which -# practically means we're re-interpreting all style-sheets AND re-applying all -# rules way too often -#* reshape also calls redraw so the entire window gets re-painted too which -# looks pretty bad (tick) -#* and finally it re-arranges all CSS boxes too, which is a rather -# resource-intensive operation -#overall the second point is the easiest to solve, then the first and finally -#the last (there's only so much you can do in a flow layout, especially with -#the current layout engine) +#TODO this works, but reshape rearranges all CSS boxes which is a *very* +#resource-intensive operation, and a significant restructuring of the layout +#engine is needed to avoid this proc updateHover(buffer: Buffer) = let nodes = buffer.currentCell().nodes if nodes != buffer.prevnodes: for node in nodes: - if not node.hover: - node.hover = true + var elem: Element + if node of Element: + elem = Element(node) + else: + elem = node.parentElement + assert elem != nil + + if not elem.hover: + elem.hover = true buffer.reshape = true + elem.cssapplied = false for node in buffer.prevnodes: - if node.hover and not (node in nodes): - node.hover = false + var elem: Element + if node of Element: + elem = Element(node) + else: + elem = node.parentElement + assert elem != nil + if elem.hover and not (node in nodes): + elem.hover = false buffer.reshape = true + elem.cssapplied = false buffer.prevnodes = nodes proc renderPlainText*(buffer: Buffer, text: string) = @@ -667,9 +678,21 @@ proc renderPlainText*(buffer: Buffer, text: string) = buffer.lines.addCell(r) buffer.updateCursor() + +const css = staticRead"res/default.css" +let ua_stylesheet = parseCSS(newStringStream(css)).value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) + +#TODO refactor +var ss_init = false +var user_stylesheet: ParsedStylesheet proc renderDocument*(buffer: Buffer) = buffer.clearText() - buffer.document.applyStylesheets() + + if not ss_init: + user_stylesheet = parseCSS(newStringStream(gconfig.stylesheet)).value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) + ss_init = true + + buffer.document.applyStylesheets(ua_stylesheet, user_stylesheet) buffer.rootbox = buffer.document.alignBoxes(buffer.width, buffer.height) if buffer.rootbox == nil: return @@ -755,7 +778,6 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = else: feedNext = false - let c = getch() s &= c let action = getNormalAction(s) diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 6291df36..d2a06165 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -4,6 +4,10 @@ import unicode import tables import json import bitops +import os + +when defined(posix): + import posix func ansiStyle*(str: string, style: Style): seq[string] = result &= ansiStyleCode(style) @@ -176,6 +180,28 @@ func skipBlanks*(buf: string, at: int): int = while result < buf.len and buf[result].isWhitespace(): inc result +proc expandPath*(path: string): string = + if path.len == 0: + return path + result = path + var i = 0 + if path[0] == '~': + if path.len == 1: + result = getHomeDir() + elif path[1] == '/': + result = getHomeDir() / path.substr(2) + inc i + else: + when defined(posix): + i = 1 + var usr = "" + while path[i] != '/': + usr &= path[i] + inc i + let p = getpwnam(usr) + if p != nil: + result = $p.pw_dir / path.substr(i) + template CSI*(s: varargs[string, `$`]): string = var r = "\e[" var first = true |