From 71f08f380b233ad415ab6954924c395a85ffc577 Mon Sep 17 00:00:00 2001 From: bptato Date: Sun, 3 Oct 2021 12:20:39 +0200 Subject: Add RowBox structure --- src/css/box.nim | 7 ++- src/html/renderer.nim | 111 --------------------------------------------- src/io/buffer.nim | 28 +++++++++++- src/layout/layout.nim | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.nim | 2 +- 5 files changed, 155 insertions(+), 114 deletions(-) delete mode 100644 src/html/renderer.nim create mode 100644 src/layout/layout.nim diff --git a/src/css/box.nim b/src/css/box.nim index e0777878..457cccb0 100644 --- a/src/css/box.nim +++ b/src/css/box.nim @@ -18,10 +18,15 @@ type marginEdge*: CSSRect children*: seq[CSSBox] + CSSRowBox* = object + width*: int + height*: int + runes*: seq[Rune] + CSSInlineBox* = ref CSSInlineBoxObj CSSInlineBoxObj = object of CSSBox fromx*: int - content*: FlexibleGrid + content*: seq[CSSRowBox] CSSBlockBox* = ref CSSBlockBoxObj CSSBlockBoxObj = object of CSSBox diff --git a/src/html/renderer.nim b/src/html/renderer.nim deleted file mode 100644 index 9e947534..00000000 --- a/src/html/renderer.nim +++ /dev/null @@ -1,111 +0,0 @@ -import unicode - -import types/enums -import html/dom -import css/box -import css/style -import io/buffer -import io/cell -import utils/twtstr - -# basically these are the "line boxes". though honestly this is an awful -# way to model them... but it's fine for now, I guess. TODO -# no it's actually not fine number one priority is making it work TODO TODO TODO -proc generateGrids(text: Text, maxwidth: int, maxheight: int, x: int, y: int, fromx: int = x): FlexibleGrid = - var r: Rune - var rowgrid: FlexibleLine - var i = 0 - 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: - rowgrid.add(FlexibleCell(rune: r)) - w += rw - result.add(rowgrid) - - if i < text.data.len: - rowgrid.setLen(0) - var w = 0 - 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 - result.add(rowgrid) - rowgrid.setLen(0) - else: - rowgrid.add(FlexibleCell(rune: r)) - w += rw - - if rowgrid.len > 0: - result.add(rowgrid) - -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.innerEdge.x1 = x - result.innerEdge.y1 = y - var height = 0 - var width = 0 - for grid in result.content: - inc height - width = max(width, grid.len) - - height = min(height, maxheight) - width = min(width, maxwidth) - result.innerEdge.x2 = x + width - result.innerEdge.y2 = y + 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.h - 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.innerEdge.x1 = x - result.innerEdge.y1 = y - - var width = 0 - var height = 0 - for box in elem.generateChildBoxes(maxwidth, maxheight, x, y, fromx): - result.children.add(box) - height += box.h - height = min(height, maxheight) - width = max(width, box.w) - width = min(width, maxwidth) - - result.innerEdge.x2 = x + width - result.innerEdge.y2 = y + height - -proc alignBoxes*(buffer: Buffer) = - buffer.rootbox = buffer.document.root.generateBox(buffer.width, buffer.height) diff --git a/src/io/buffer.nim b/src/io/buffer.nim index cfea36e3..66ed6aaf 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -191,6 +191,8 @@ func cellWidthOverlap*(buffer: Buffer, x: int, y: int): int = func currentCellWidth*(buffer: Buffer): int = buffer.cellWidthOverlap(buffer.cursorx - buffer.fromx, buffer.cursory - buffer.fromy) func currentLineWidth*(buffer: Buffer): int = + if buffer.cursory > buffer.lines.len: + return 0 return buffer.lines[buffer.cursory].width() func maxScreenWidth*(buffer: Buffer): int = @@ -526,6 +528,22 @@ 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) = + while buffer.lines.len <= y: + buffer.lines.add(newSeq[FlexibleCell]()) + + var i = 0 + var cx = 0 + while cx < x and i < buffer.lines[y].len: + cx += buffer.lines[y][i].rune.width() + inc i + + buffer.lines[y].setLen(i) + i = 0 + while i < line.width: + buffer.lines[y].add(FlexibleCell(rune: line.runes[i])) + inc i + proc reshape*(buffer: Buffer) = buffer.display = newFixedGrid(buffer.width, buffer.height) buffer.statusmsg = newFixedGrid(buffer.width) @@ -543,6 +561,11 @@ proc refreshDisplay*(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 + for line in buffer.lines[buffer.fromy..buffer.lastVisibleLine - 1]: var w = 0 var i = 0 @@ -599,10 +622,13 @@ proc renderDocument*(buffer: Buffer) = if i == 0: x = inline.fromx var y = box.innerEdge.y1 + i - buffer.setLine(x, y, line) + + buffer.setRowBox(x, y, line) for child in box.children: stack.add(child) + + eprint "lines", buffer.lines.len proc cursorBufferPos(buffer: Buffer) = let x = max(buffer.cursorx - buffer.fromx, 0) diff --git a/src/layout/layout.nim b/src/layout/layout.nim new file mode 100644 index 00000000..043c0e08 --- /dev/null +++ b/src/layout/layout.nim @@ -0,0 +1,121 @@ +import unicode + +import types/enums +import html/dom +import css/box +import css/style +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] = + var r: Rune + var rowbox: CSSRowBox + 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 + else: + if whitespace: + whitespace = false + rowbox.runes.add(r) + inc rowbox.width + w += rw + + result.add(rowbox) + + if i < text.data.len: + rowbox = CSSRowBox() + var w = 0 + 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 + result.add(rowbox) + rowbox = CSSRowBox() + 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.innerEdge.x1 = x + result.innerEdge.y1 = 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.innerEdge.x2 = x + width + result.innerEdge.y2 = y + 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.h + 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.innerEdge.x1 = x + result.innerEdge.y1 = y + + var width = 0 + var height = 0 + for box in elem.generateChildBoxes(maxwidth, maxheight, x, y, fromx): + result.children.add(box) + height += box.h + height = min(height, maxheight) + width = max(width, box.w) + width = min(width, maxwidth) + + result.innerEdge.x2 = x + width + result.innerEdge.y2 = y + height + +proc alignBoxes*(buffer: Buffer) = + buffer.rootbox = buffer.document.root.generateBox(buffer.width, buffer.height) diff --git a/src/main.nim b/src/main.nim index b1345ce2..9661ba10 100644 --- a/src/main.nim +++ b/src/main.nim @@ -5,7 +5,7 @@ import streams import html/parser import html/dom -import html/renderer +import layout/layout import io/buffer import io/term import config/config -- cgit 1.4.1-2-gfad0