diff options
author | bptato <nincsnevem662@gmail.com> | 2021-11-08 22:50:44 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-11-08 22:50:44 +0100 |
commit | 71490162f8d5eab021dcc33b66d42fa158bdb461 (patch) | |
tree | ec94555b0d282e10bb607a977204f1b229f9680c /src | |
parent | cac5382d4e9f9f7536a12448dcbe3657f19dbbf4 (diff) | |
download | chawan-71490162f8d5eab021dcc33b66d42fa158bdb461.tar.gz |
Actual layout engine progress plus some bug fixes
Diffstat (limited to 'src')
-rw-r--r-- | src/io/buffer.nim | 64 | ||||
-rw-r--r-- | src/layout/box.nim | 1 | ||||
-rw-r--r-- | src/layout/layout.nim | 385 |
3 files changed, 238 insertions, 212 deletions
diff --git a/src/io/buffer.nim b/src/io/buffer.nim index b8efd730..a64dfb12 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -88,7 +88,8 @@ func generateFullOutput*(buffer: Buffer): seq[string] = # generate a sequence of instructions to replace the previous frame with the # current one. ideally we should have some mechanism in place to determine -# where we should use this and where we should just rewrite the frame +# where we should use this and where we should just rewrite the frame, though +# now that I think about it rewriting every frame might be a better option func generateSwapOutput*(buffer: Buffer): seq[DrawInstruction] = var fgcolor: CellColor var bgcolor: CellColor @@ -175,7 +176,7 @@ func generateStatusMessage*(buffer: Buffer): string = func numLines*(buffer: Buffer): int = buffer.lines.len -func lastVisibleLine*(buffer: Buffer): int = min(buffer.fromy + buffer.height, buffer.numLines - 1) +func lastVisibleLine*(buffer: Buffer): int = min(buffer.fromy + buffer.height, buffer.numLines) func width(line: seq[FlexibleCell]): int = for c in line: @@ -228,8 +229,12 @@ func getElementById*(buffer: Buffer, id: string): Element = proc findSelectedNode*(buffer: Buffer): Option[Node] = discard #TODO +proc addLine(buffer: Buffer) = + buffer.lines.add(newSeq[FlexibleCell]()) + proc clearText*(buffer: Buffer) = buffer.lines.setLen(0) + buffer.addLine() proc clearNodes*(buffer: Buffer) = buffer.nodes.setLen(0) @@ -514,7 +519,7 @@ proc setText*(buffer: Buffer, x: int, y: int, text: seq[Rune]) = buffer.lines.se proc setLine*(buffer: Buffer, x: int, y: int, line: FlexibleLine) = while buffer.lines.len <= y: - buffer.lines.add(newSeq[FlexibleCell]()) + buffer.addLine() var i = 0 var cx = 0 @@ -528,9 +533,11 @@ proc setLine*(buffer: Buffer, x: int, y: int, line: FlexibleLine) = buffer.lines[y].add(line[i]) inc i -proc setRowBox(buffer: Buffer, x: int, y: int, line: CSSRowBox) = +proc setRowBox(buffer: Buffer, line: CSSRowBox) = + let x = line.x + let y = line.y while buffer.lines.len <= y: - buffer.lines.add(newSeq[FlexibleCell]()) + buffer.addLine() var i = 0 var cx = 0 @@ -538,16 +545,42 @@ proc setRowBox(buffer: Buffer, x: int, y: int, line: CSSRowBox) = cx += buffer.lines[y][i].rune.width() inc i + let oline = buffer.lines[y][i..high(buffer.lines[y])] buffer.lines[y].setLen(i) + var j = 0 + var nx = cx + + #TODO not sure + while nx < x: + buffer.lines[y].add(FlexibleCell(rune: Rune(' '))) + inc nx + + while j < line.runes.len: + buffer.lines[y].add(FlexibleCell(rune: line.runes[j])) + nx += line.runes[j].width() + inc j + i = 0 - while i < line.width: - buffer.lines[y].add(FlexibleCell(rune: line.runes[i])) + while cx < nx and i < oline.len: + cx += oline[i].rune.width() inc i + if i < oline.len: + buffer.lines[y].add(oline[i..high(oline)]) + proc reshape*(buffer: Buffer) = buffer.display = newFixedGrid(buffer.width, buffer.height) buffer.statusmsg = newFixedGrid(buffer.width) +proc updateCursor(buffer: Buffer) = + if buffer.fromy > buffer.lastVisibleLine - 1: + buffer.fromy = 0 + buffer.cursory = buffer.lastVisibleLine - 1 + + if buffer.lines.len == 0: + buffer.cursory = 0 + return + proc clearDisplay*(buffer: Buffer) = var i = 0 while i < buffer.display.len: @@ -558,13 +591,6 @@ proc refreshDisplay*(buffer: Buffer) = var y = 0 buffer.prevdisplay = buffer.display buffer.clearDisplay() - if buffer.fromy > buffer.lastVisibleLine - 1: - buffer.fromy = 0 - buffer.cursory = buffer.lastVisibleLine - 1 - - if buffer.lines.len == 0: - buffer.cursory = 0 - return for line in buffer.lines[buffer.fromy..buffer.lastVisibleLine - 1]: var w = 0 @@ -621,10 +647,14 @@ proc renderDocument*(buffer: Buffer) = eprint "NEW BOX" for line in inline.content: eprint line - buffer.setRowBox(inline.x + line.x, inline.y + line.y, line) + buffer.setRowBox(line) + else: + eprint "BLOCK" - for child in box.children: - stack.add(child) + var i = box.children.len - 1 + while i >= 0: + stack.add(box.children[i]) + dec i proc cursorBufferPos(buffer: Buffer) = let x = max(buffer.cursorx - buffer.fromx, 0) diff --git a/src/layout/box.nim b/src/layout/box.nim index 277bbef4..a36bb424 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -14,6 +14,7 @@ type CSSBoxObj = object of RootObj x*: int y*: int + fromx*: int width*: int height*: int children*: seq[CSSBox] diff --git a/src/layout/layout.nim b/src/layout/layout.nim index 57220df1..96f27c9c 100644 --- a/src/layout/layout.nim +++ b/src/layout/layout.nim @@ -8,39 +8,61 @@ import io/buffer import io/cell import utils/twtstr -#proc generateGrids(text: Text, maxwidth: int, maxheight: int, x: int, y: int, fromx: int = x): seq[CSSRowBox] = +#type +# Frame = object +# node: Node +# maxwidth: int +# maxheight: int +# box: CSSBox +# context: FormatContext + + +#proc addText(state: var LayoutState, frame: var Frame, text: Text) = +# let maxwidth = frame.maxwidth +# let fromx = state.fromx +# let x = state.x +# if maxwidth == 0: return +# if not (frame.box of CSSInlineBox): return +# var box = CSSInlineBox(frame.box) # var r: Rune -# var rowbox: CSSRowBox # var i = 0 -# var whitespace = false +# var rowbox: CSSRowBox # if fromx > x: -# let m = fromx - x + maxwidth +# rowbox = CSSRowBox(x: state.fromx, y: state.y) +# let m = maxwidth - fromx + x # var w = 0 +# var lf = false # while i < text.data.len: # let pi = i # fastRuneAt(text.data, i, r) # let rw = r.width() # if rw + w > m: # i = pi +# inc state.y +# lf = true # break # else: # if r.isWhitespace(): -# if not whitespace: -# whitespace = true +# if not state.whitespace: +# state.whitespace = true # rowbox.runes.add(Rune(' ')) # inc rowbox.width # w += rw # else: -# if whitespace: -# whitespace = false +# if state.whitespace: +# state.whitespace = false # rowbox.runes.add(r) # inc rowbox.width # w += rw # -# result.add(rowbox) +# box.content.add(rowbox) +# if lf: +# state.fromx = 0 +# else: +# state.fromx += rowbox.width # # if i < text.data.len: -# rowbox = CSSRowBox() +# rowbox = CSSRowBox(x: state.x, y: state.y) # var w = 0 # while i < text.data.len: # let pi = i @@ -49,216 +71,189 @@ import utils/twtstr # if rw + w > maxwidth: # i = pi # w = 0 -# result.add(rowbox) -# rowbox = CSSRowBox() +# box.content.add(rowbox) +# state.fromx += rowbox.width +# inc state.y +# rowbox = CSSRowBox(x: state.x, y: state.y) # else: # rowbox.runes.add(r) # inc rowbox.width # w += rw # # if rowbox.width > 0: -# result.add(rowbox) -# -#proc generateBox(text: Text, maxwidth: int, maxheight: int, x: int, y: int, fromx: int): CSSInlineBox = -# new(result) -# result.content = text.generateGrids(maxwidth, maxheight, x, y, fromx) -# result.fromx = fromx -# result.x = x -# result.y = y -# var height = 0 -# var width = 0 -# for grid in result.content: -# inc height -# width = max(width, grid.width) -# -# height = min(height, maxheight) -# width = min(width, maxwidth) -# result.width = width -# result.height = height -# -#proc generateBox(elem: Element, maxwidth: int, maxheight: int, x: int = 0, y: int = 0, fromx: int = x): CSSBox -# -#proc generateChildBoxes(elem: Element, maxwidth: int, maxheight: int, x: int, y: int, fromx: int = 0): seq[CSSBox] = -# var cx = fromx -# var cy = y -# for child in elem.childNodes: -# case child.nodeType -# of TEXT_NODE: -# let box = Text(child).generateBox(maxwidth, maxheight, x, cy, cx) -# if box != nil: -# result.add(box) -# cy += box.height -# if box.content.len > 0: -# cx += box.content[^1].width -# of ELEMENT_NODE: -# let box = Element(child).generateBox(maxwidth, maxheight, x, cy, cx) -# if box != nil: -# result.add(box) -# else: -# discard -# -#proc generateBox(elem: Element, maxwidth: int, maxheight: int, x: int = 0, y: int = 0, fromx: int = x): CSSBox = -# if elem.cssvalues[RULE_DISPLAY].display == DISPLAY_NONE: -# return nil -# -# result = CSSBlockBox() -# result.x = x -# result.y = y -# -# var width = 0 -# var height = 0 -# for box in elem.generateChildBoxes(maxwidth, maxheight, x, y, fromx): -# result.children.add(box) -# height += box.height -# height = min(height, maxheight) -# width = max(width, box.width) -# width = min(width, maxwidth) +# box.content.add(rowbox) +# state.fromx += rowbox.width + +#proc alignBoxes*(buffer: Buffer) = +# #buffer.rootbox = buffer.document.root.genBox(buffer.width, buffer.height) +# buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: buffer.height) +# buffer.rootbox.children.add(CSSInlineBox(x: 0, y: 0, width: buffer.width, height: buffer.height)) +# var x = 0 +# var stack: seq[Frame] +# var state: LayoutState +# stack.add(Frame(node: buffer.document.root, box: buffer.rootbox, maxwidth: 80, context: CONTEXT_BLOCK)) +# while stack.len > 0: +# var frame = stack.pop() +# let node = frame.node # -# result.width = width -# result.height = height +# case frame.context +# of CONTEXT_BLOCK: +# case node.nodeType +# of TEXT_NODE: #anonymous +# discard +# of ELEMENT_NODE: #new formatting context +# let elem = Element(node) +# case elem.cssvalues[RULE_DISPLAY].display +# of DISPLAY_BLOCK: +# let parent = frame.box +# state.whitespace = false +# frame.box = CSSBlockBox(x: state.x, y: state.y, width: frame.maxwidth) +# parent.children.add(frame.box) +# frame.context = CONTEXT_BLOCK +# of DISPLAY_INLINE: +# let parent = frame.box +# frame.box = CSSInlineBox(x: state.x, y: state.y, width: frame.maxwidth) +# parent.children.add(frame.box) +# frame.context = CONTEXT_INLINE +# of DISPLAY_NONE: continue +# else: discard #TODO +# else: discard +# of CONTEXT_INLINE: +# case node.nodeType +# of TEXT_NODE: #just add to existing inline box no problem +# let text = Text(node) +# state.addText(frame, text) +# of ELEMENT_NODE: +# let elem = Element(node) +# case elem.cssvalues[RULE_DISPLAY].display +# of DISPLAY_NONE: continue +# else: +# #ok this is the difficult part (TODO) +# #NOTE we're assuming the parent isn't inline, if it is we're screwed +# #basically what we have to do is: +# #* create a new anonymous BLOCK box +# #* for every previous INLINE box in parent (BLOCK) box, do: +# #* copy INLINE box into new anonymous BLOCK box +# #* delete INLINE box +# #* create a new BLOCK box (this one) +# #* NOTE after our BLOCK there's a continuation of the last INLINE box # -#proc genBox(elem: Element, w: int, h: int, x: int = 0, y: int = 0): CSSBlockBox = -# if elem.cssvalues[RULE_DISPLAY].display == DISPLAY_NONE: -# return nil -# result = CSSBlockBox() +# eprint "?????" +# else: discard # +# var i = node.childNodes.len - 1 +# while i >= 0: +# let child = node.childNodes[i] +# stack.add(Frame(node: child, box: frame.box, maxwidth: frame.maxwidth, context: frame.context)) +# dec i + type - Frame = object - node: Node - maxwidth: int - maxheight: int + LayoutState = object x: int y: int fromx: int - box: CSSBox + whitespace: bool context: FormatContext FormatContext = enum CONTEXT_BLOCK, CONTEXT_INLINE -proc addText(frame: var Frame, text: Text) = - let maxwidth = frame.maxwidth - let fromx = frame.fromx - let x = frame.x - if maxwidth == 0: return - if not (frame.box of CSSInlineBox): return - var box = CSSInlineBox(frame.box) - var r: Rune - var rowbox = CSSRowBox(x: frame.x, y: frame.y) - var i = 0 - var whitespace = false - if fromx > x: - let m = fromx - x + maxwidth - var w = 0 - while i < text.data.len: - let pi = i - fastRuneAt(text.data, i, r) - let rw = r.width() - if rw + w > m: - i = pi - break - else: - if r.isWhitespace(): - if not whitespace: - whitespace = true - rowbox.runes.add(Rune(' ')) - inc rowbox.width - w += rw +proc addChild(parent: var CSSBox, box: CSSBox) = + if box == nil: + return + parent.height += box.height + parent.children.add(box) + +proc processNode(parent: CSSBox, node: Node): CSSBox = + case node.nodeType + of ELEMENT_NODE: + let elem = Element(node) + var box: CSSBox + case elem.cssvalues[RULE_DISPLAY].display + of DISPLAY_BLOCK: + box = CSSBlockBox(x: parent.x, y: parent.y + parent.height, width: parent.width) + of DISPLAY_INLINE: + #TODO split this into its own thing + #TODO also rethink this bc it isn't very great + #TODO like, it doesn't work + var fromx = parent.x + if parent.children.len > 0 and parent.children[^1] of CSSInlineBox: + let sib = CSSInlineBox(parent.children[^1]) + if sib.content.len > 0: + fromx = sib.content[^1].x + sib.content[^1].width else: - if whitespace: - whitespace = false - rowbox.runes.add(r) - inc rowbox.width - w += rw + eprint "???" + elif parent of CSSInlineBox: + let sib = CSSInlineBox(parent) + if sib.content.len > 0: + fromx = sib.content[^1].x + sib.content[^1].width + else: + eprint "???" + box = CSSInlineBox(x: parent.x, y: parent.y + parent.height, width: parent.width) + CSSInlineBox(box).content.add(CSSRowBox(x: fromx, y: box.y)) + of DISPLAY_NONE: + return nil + else: + return nil + + for child in elem.childNodes: + CSSBox(box).addChild(processNode(box, child)) + return box + of TEXT_NODE: + let text = Text(node) + #TODO not always anonymous + var ibox = CSSInlineBox(x: parent.x, y: parent.y + parent.height, width: parent.width) + var ws = true #TODO doesn't always start with newline + let str = text.data - box.content.add(rowbox) - frame.x += rowbox.width + if text.data.len == 0: + return - if i < text.data.len: - rowbox = CSSRowBox(x: frame.x, y: frame.y) + #TODO ok we'll have to rethink this methinks + var fromx = ibox.x + if parent.children.len > 0 and parent.children[^1] of CSSInlineBox: + let sib = CSSInlineBox(parent.children[^1]) + if sib.content.len > 0: + fromx = sib.content[^1].x + sib.content[^1].width + else: + eprint "???" + elif parent of CSSInlineBox: + let sib = CSSInlineBox(parent) + if sib.content.len > 0: + fromx = sib.content[^1].x + sib.content[^1].width + else: + eprint "???" + + var i = 0 var w = 0 + var rowi = 0 + var rowbox = CSSRowBox(x: fromx, y: ibox.y + rowi) + var r: Rune while i < text.data.len: - let pi = i fastRuneAt(text.data, i, r) - let rw = r.width() - if rw + w > maxwidth: - i = pi - w = 0 - box.content.add(rowbox) - frame.x += rowbox.width - rowbox = CSSRowBox(x: frame.x, y: frame.y) + if w + r.width() > ibox.width: + ibox.content.add(rowbox) + inc rowi + rowbox = CSSRowBox(x: ibox.x, y: ibox.y + rowi) + if r.isWhitespace(): + if ws: + continue + else: + ws = true else: - rowbox.runes.add(r) - inc rowbox.width - w += rw + ws = false + rowbox.width += r.width() + rowbox.runes.add(r) + if rowbox.runes.len > 0: + ibox.content.add(rowbox) + inc rowi - if rowbox.width > 0: - box.content.add(rowbox) - frame.x += rowbox.width + ibox.height += rowi + return ibox + else: discard + return nil proc alignBoxes*(buffer: Buffer) = - #buffer.rootbox = buffer.document.root.genBox(buffer.width, buffer.height) - buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: buffer.height) - buffer.rootbox.children.add(CSSInlineBox(x: 0, y: 0, width: buffer.width, height: buffer.height)) - var x = 0 - var stack: seq[Frame] - stack.add(Frame(node: buffer.document.root, box: buffer.rootbox, x: 0, y: 0, fromx: 0, maxwidth: 80, context: CONTEXT_BLOCK)) - while stack.len > 0: - var frame = stack.pop() - let node = frame.node - - case frame.context - of CONTEXT_BLOCK: - case node.nodeType - of TEXT_NODE: #anonymous - discard - of ELEMENT_NODE: #new formatting context - let elem = Element(node) - case elem.cssvalues[RULE_DISPLAY].display - of DISPLAY_BLOCK: - let parent = frame.box - frame.box = CSSBlockBox(x: frame.x, y: frame.y, width: frame.maxwidth) - parent.children.add(frame.box) - frame.context = CONTEXT_BLOCK - of DISPLAY_INLINE: - let parent = frame.box - frame.box = CSSInlineBox(x: frame.x, y: frame.y, width: frame.maxwidth) - parent.children.add(frame.box) - frame.context = CONTEXT_INLINE - of DISPLAY_NONE: continue - else: discard #TODO - else: discard - of CONTEXT_INLINE: - case node.nodeType - of TEXT_NODE: #just add to existing inline box no problem - let text = Text(node) - frame.addText(text) - of ELEMENT_NODE: - let elem = Element(node) - case elem.cssvalues[RULE_DISPLAY].display - of DISPLAY_NONE: continue - else: - #ok this is the difficult part (TODO) - #NOTE we're assuming the parent isn't inline, if it is we're screwed - #basically what we have to do is: - #* create a new anonymous BLOCK box - #* for every previous INLINE box in parent (BLOCK) box, do: - #* copy INLINE box into new anonymous BLOCK box - #* delete INLINE box - #* create a new BLOCK box (this one) - #* NOTE after our BLOCK there's a continuation of the last INLINE box - - eprint "?????" - else: discard - - # look ahead to figure out if inline box will have to be split - #var i = node.childNodes.len - 1 - #while i >= 0: - # let child = node.childNodes[i] - # stack.add(Frame(node: child, box: frame.box, maxwidth: frame.maxwidth, x: frame.x, y: frame.y, fromx: frame.fromx, context: frame.context)) - - var i = node.childNodes.len - 1 - while i >= 0: - let child = node.childNodes[i] - stack.add(Frame(node: child, box: frame.box, maxwidth: frame.maxwidth, x: frame.x, y: frame.y, fromx: frame.fromx, context: frame.context)) - dec i + buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: 0) + for child in buffer.document.root.childNodes: + buffer.rootbox.addChild(processNode(buffer.rootbox, child)) |