about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-11-08 22:50:44 +0100
committerbptato <nincsnevem662@gmail.com>2021-11-08 22:50:44 +0100
commit71490162f8d5eab021dcc33b66d42fa158bdb461 (patch)
treeec94555b0d282e10bb607a977204f1b229f9680c /src
parentcac5382d4e9f9f7536a12448dcbe3657f19dbbf4 (diff)
downloadchawan-71490162f8d5eab021dcc33b66d42fa158bdb461.tar.gz
Actual layout engine progress plus some bug fixes
Diffstat (limited to 'src')
-rw-r--r--src/io/buffer.nim64
-rw-r--r--src/layout/box.nim1
-rw-r--r--src/layout/layout.nim385
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))