ref: fc060bb0db3672c1cb6cfe4ceab1227f86746945
parent: e3e41088f4011383f89599c15f3b7d4add542b29
author: Philip Silva <[email protected]>
date: Tue Jul 27 14:25:31 EDT 2021
Untangle Arrange(..) - add interface duitx.Boxable ~> less boxes needed, flow handled at dui.Layout time
--- a/browser/browser.go
+++ b/browser/browser.go
@@ -328,7 +328,7 @@
}
if n.Type() != html.TextNode {
- if box, ok := newBoxElement(ui, n); ok {
+ if box, ok := newBoxElement(n, false, ui); ok {
ui = box
}
}
@@ -339,11 +339,11 @@
}
}
-func newBoxElement(ui duit.UI, n *nodes.Node) (box *duitx.Box, ok bool) {
- if ui == nil {
+func newBoxElement(n *nodes.Node, force bool, uis ...duit.UI) (box *duitx.Box, ok bool) {
+ if len(uis) == 0 || (len(uis) == 1 && uis[0] == nil) {
return nil, false
}
- if n.IsDisplayNone() {
+ if n != nil && n.IsDisplayNone() {
return nil, false
}
@@ -352,51 +352,56 @@
var m, p duit.Space
var zs duit.Space
var h int
- w := n.Width()
- if n.Data() != "body" {
- h = n.Height()
- }
- mw, err := n.CssPx("max-width")
- if err != nil {
- log.Printf("max-width: %v", err)
- }
+ var w int
+ var mw int
- if bg, err := n.BoxBackground(); err == nil {
- i = bg
- } else {
- log.Printf("box background: %f", err)
- }
+ if n != nil {
+ w = n.Width()
+ if n.Data() != "body" {
+ h = n.Height()
+ }
+ mw, err = n.CssPx("max-width")
+ if err != nil {
+ log.Printf("max-width: %v", err)
+ }
- if p, err = n.Tlbr("padding"); err != nil {
- log.Errorf("padding: %v", err)
- }
- if m, err = n.Tlbr("margin"); err != nil {
- log.Errorf("margin: %v", err)
- }
+ if bg, err := n.BoxBackground(); err == nil {
+ i = bg
+ } else {
+ log.Printf("box background: %f", err)
+ }
- if n.Css("display") == "inline" {
- // Actually this doesn't fix the problem to the full extend
- // exploded texts' elements might still do double and triple
- // horizontal pads/margins
- w = 0
- mw = 0
- m.Top = 0
- m.Bottom = 0
- p.Top = 0
- p.Bottom = 0
- }
+ if p, err = n.Tlbr("padding"); err != nil {
+ log.Errorf("padding: %v", err)
+ }
+ if m, err = n.Tlbr("margin"); err != nil {
+ log.Errorf("margin: %v", err)
+ }
- // TODO: make sure input fields can be put into a box
- if n.Data() =="input" {
- return nil, false
+ if n.Css("display") == "inline" {
+ // Actually this doesn't fix the problem to the full extend
+ // exploded texts' elements might still do double and triple
+ // horizontal pads/margins
+ w = 0
+ mw = 0
+ m.Top = 0
+ m.Bottom = 0
+ p.Top = 0
+ p.Bottom = 0
+ }
+
+ // TODO: make sure input fields can be put into a box
+ if n.Data() =="input" {
+ return nil, false
+ }
}
- if w == 0 && h == 0 && mw == 0 && i == nil && m == zs && p == zs {
+ if w == 0 && h == 0 && mw == 0 && i == nil && m == zs && p == zs && !force {
return nil, false
}
box = &duitx.Box{
- Kids: duit.NewKids(ui),
+ Kids: duit.NewKids(uis...),
Width: w,
Height: h,
MaxWidth: mw,
@@ -404,6 +409,8 @@
Background: i,
Margin: m,
Padding: p,
+ Dir: duitFlexDir(n),
+ Disp: duitDisplay(n),
}
return box, true
@@ -614,6 +621,22 @@
return el
}
+func (el *Element) Display() duitx.Display {
+ var n *nodes.Node
+ if el != nil {
+ n = el.n
+ }
+ return duitDisplay(n)
+}
+
+func (el *Element) FlexDir() duitx.Dir {
+ var n *nodes.Node
+ if el != nil {
+ n = el.n
+ }
+ return duitFlexDir(n)
+}
+
func (el *Element) Key(dui *duit.DUI, self *duit.Kid, k rune, m draw.Mouse, orig image.Point) (r duit.Result) {
r = el.UI.Key(dui, self, k, m, orig)
@@ -829,125 +852,83 @@
}
func Arrange(n *nodes.Node, elements ...*Element) *Element {
- if n.IsFlex() {
- if n.IsFlexDirectionRow() {
- return NewElement(horizontalSeq(true, elements), n)
- } else {
- return NewElement(verticalSeq(elements), n)
- }
- }
-
if ael, ok := arrangeAbsolute(n, elements...); ok {
return ael
}
- rows := make([][]*Element, 0, 10)
- currentRow := make([]*Element, 0, 10)
- flushCurrentRow := func() {
- if len(currentRow) > 0 {
- rows = append(rows, currentRow)
- currentRow = make([]*Element, 0, 10)
- }
- }
-
- for _, e := range elements {
- isInline := e.n.IsInline() || e.n.Type() == html.TextNode
- if !isInline {
- flushCurrentRow()
- }
- currentRow = append(currentRow, e)
- if !isInline {
- flushCurrentRow()
- }
- }
- flushCurrentRow()
- if len(rows) == 0 {
+ ui := horizontalSeq(n, true, elements)
+ if ui == nil {
return nil
- } else if len(rows) == 1 {
- if len(rows[0]) == 0 {
- return nil
- } else if len(rows[0]) == 1 {
- return rows[0][0]
- }
- s := horizontalSeq(true, rows[0])
- if el, ok := s.(*Element); ok {
- return el
- }
- return NewElement(s, n)
- } else {
- seqs := make([]*Element, 0, len(rows))
- for _, row := range rows {
- seq := horizontalSeq(true, row)
- if el, ok := seq.(*Element); ok {
- seqs = append(seqs, el)
- } else {
- seqs = append(seqs, NewElement(seq, n))
- }
- }
- s := verticalSeq(seqs)
- if el, ok := s.(*Element); ok {
- return el
- }
- return NewElement(s, n)
}
+ return &Element{
+ n: n,
+ UI: ui,
+ }
}
-func horizontalSeq(wrap bool, es []*Element) duit.UI {
+func horizontalSeq(parent *nodes.Node, wrap bool, es []*Element) duit.UI {
if len(es) == 0 {
return nil
- } else if len(es) == 1 {
- return es[0]
}
- halign := make([]duit.Halign, 0, len(es))
- valign := make([]duit.Valign, 0, len(es))
+ finalUis := make([]duit.UI, 0, len(es))
+ for _, el := range es {
+ label, isLabel := el.UI.(*duit.Label)
+ if isLabel {
+ tts := strings.Split(label.Text, " ")
+ for _, t := range tts {
+ finalUis = append(finalUis, NewElement(&duit.Label{
+ Text: t,
+ Font: label.Font,
+ }, el.n))
+ }
+ } else {
+ if el != nil {
+ finalUis = append(finalUis, el)
+ }
+ }
+ }
- for i := 0; i < len(es); i++ {
- halign = append(halign, duit.HalignLeft)
- valign = append(valign, duit.ValignTop)
+ b, ok := newBoxElement(parent, true, finalUis...)
+ if !ok {
+ return nil
}
+ return b
+}
- uis := make([]duit.UI, 0, len(es))
- for _, e := range es {
- uis = append(uis, e)
+func duitDisplay(n *nodes.Node) duitx.Display {
+ if n == nil {
+ return duitx.InlineBlock
}
+ if n.Css("float") == "left" {
+ return duitx.InlineBlock
+ } else if cl := n.Css("clear"); cl == "left" || cl == "both" {
+ return duitx.Block
+ }
+ switch n.Css("display") {
+ case "inline":
+ return duitx.Inline
+ case "block":
+ return duitx.Block
+ case "flex":
+ return duitx.Flex
+ default:
+ return duitx.InlineBlock
+ }
+}
- if wrap {
- finalUis := make([]duit.UI, 0, len(uis))
- for _, ui := range uis {
- PrintTree(ui)
- el, ok := ui.(*Element)
-
- if ok {
- label, isLabel := el.UI.(*duit.Label)
- if isLabel {
- tts := strings.Split(label.Text, " ")
- for _, t := range tts {
- finalUis = append(finalUis, NewElement(&duit.Label{
- Text: t,
- Font: label.Font,
- }, el.n))
- }
- } else {
- finalUis = append(finalUis, ui)
- }
- } else {
- finalUis = append(finalUis, ui)
- }
- }
-
- return &duitx.Box{
- Kids: duit.NewKids(finalUis...),
- }
- } else {
- return &duitx.Grid{
- Columns: len(es),
- Padding: duit.NSpace(len(es), duit.SpaceXY(0, 3)),
- Halign: halign,
- Valign: valign,
- Kids: duit.NewKids(uis...),
- }
+func duitFlexDir(n *nodes.Node) duitx.Dir {
+ if n == nil {
+ return 0
}
+ switch n.Css("flex-direction") {
+ case "row":
+ return duitx.Row
+ case "column":
+ return duitx.Column
+ default:
+ return 0
+ }
}
func verticalSeq(es []*Element) duit.UI {
@@ -971,12 +952,6 @@
}
}
-func check(err error, msg string) {
- if err != nil {
- log.Fatalf("%s: %s\n", msg, err)
- }
-}
-
type Table struct {
rows []*TableRow
}
@@ -1086,7 +1061,7 @@
}
if len(rowEls) > 0 {
- seq := horizontalSeq(false, rowEls)
+ seq := horizontalSeq(nil, false, rowEls)
seqs = append(seqs, NewElement(seq, row.n))
}
}
@@ -1158,7 +1133,7 @@
return
case "input":
t := n.Attr("type")
- if t == "" || t == "text" || t == "search" || t == "password" {
+ if t == "" || t == "text" || t == "email" || t == "search" || t == "password" {
return NewInputField(n)
} else if t == "submit" {
return NewSubmitButton(b, n)
@@ -1227,21 +1202,7 @@
}
fallthrough
default:
- if !n.IsInline() {
- // Explicitly keep block elements to preserve rows from
- // getting squashed
- innerContent := InnerNodesToBox(r+1, b, n)
- if innerContent == nil {
- return nil
- }
- if innerContent.n == n {
- return innerContent
- }
- return NewElement(innerContent, n)
- } else {
- // Internal node object
- return InnerNodesToBox(r+1, b, n)
- }
+ return InnerNodesToBox(r+1, b, n)
}
} else if n.Type() == html.TextNode {
// Leaf text object
@@ -1284,7 +1245,7 @@
if len(ls) == 0 {
continue
}
- el := NewElement(horizontalSeq(true, ls), c)
+ el := NewElement(horizontalSeq(c, true, ls), c)
if el == nil {
continue
}
@@ -1296,8 +1257,6 @@
if len(els) == 0 {
return nil
- } else if len(els) == 1 {
- return els[0]
}
return Arrange(n, els...)
--- a/browser/browser_test.go
+++ b/browser/browser_test.go
@@ -17,6 +17,10 @@
"testing"
)
+var (
+ _ duitx.Boxable = &Element{}
+)
+
func init() {
debugPrintHtml = false
log.Debug = true
@@ -86,16 +90,22 @@
t.Fatalf("%+v", e)
}
}
- body := v.UI.(*duitx.Box).Kids[0]
- if d == "inline" {
- b := body.UI.(*duitx.Box)
- if len(b.Kids) != 3 {
- t.Fatalf("%v %+v", len(b.Kids), b)
+ PrintTree(v)
+ b := v.UI.(*duitx.Box)
+ if len(b.Kids) != 3 {
+ t.Fatalf("%v %+v", len(b.Kids), b)
+ }
+ for _, k := range b.Kids {
+ disp := k.UI.(duitx.Boxable).Display()
+ if d == "inline" {
+ if disp != duitx.Inline {
+ t.Fail()
+ }
+ } else {
+ if disp != duitx.Block {
+ t.Fail()
+ }
}
- } else {
- if g := body.UI.(*duitx.Grid); g.Columns != 1 || len(g.Kids) != 3 {
- t.Fatalf("%+v", g)
- }
}
}
}
@@ -362,13 +372,13 @@
// 2. Elements are 2 rows
kids, ok := explodeRow(boxed)
- if !ok || len(kids) != 1 {
+ if !ok || len(kids) != 2 {
t.Errorf("boxed: %+v, kids: %+v", boxed, kids)
}
-
- g := kids[0].UI.(*duitx.Grid)
- if g.Columns != 1 || len(g.Kids) != 2 {
- t.Fail()
+ for _, k := range kids {
+ if k.UI.(duitx.Boxable).Display() != duitx.Block {
+ t.Fail()
+ }
}
}
--- a/browser/duitx/box.go
+++ b/browser/duitx/box.go
@@ -45,6 +45,27 @@
return &Box{Kids: kids, Reverse: true}
}
+type Display int
+
+const (
+ InlineBlock = iota // default
+ Block // always start a new line
+ Inline // flow inline but ignore margin, width and height
+ Flex
+)
+
+type Dir int
+
+const (
+ Row = iota + 1
+ Column
+)
+
+type Boxable interface {
+ Display() Display
+ FlexDir() Dir
+}
+
// Box keeps elements on a line as long as they fit, then moves on to the next line.
type Box struct {
Kids []*duit.Kid // Kids and UIs in this box.
@@ -56,6 +77,8 @@
Height int // 0 means dynamic (as much as needed), -1 means full height, >0 means that exact amount of lowDPI pixels.
MaxWidth int // if >0, the max number of lowDPI pixels that will be used.
ContentBox bool // Use ContentBox (BorderBox by default)
+ Disp Display
+ Dir Dir
Background *draw.Image `json:"-"` // Background for this box, instead of default duit background.
size image.Point // of entire box, including padding but excluding margin
@@ -62,7 +85,16 @@
}
var _ duit.UI = &Box{}
+var _ Boxable = &Box{}
+func (ui *Box) Display() Display {
+ return ui.Disp
+}
+
+func (ui *Box) FlexDir() Dir {
+ return ui.Dir
+}
+
func (ui *Box) Layout(dui *duit.DUI, self *duit.Kid, sizeAvail image.Point, force bool) {
debugLayout(dui, self)
if duit.KidsLayout(dui, self, ui.Kids, force) {
@@ -81,6 +113,12 @@
bbmaxw := dui.Scale(ui.MaxWidth)
bbh := dui.Scale(ui.Height)
+ if ui.Disp == Inline {
+ bbw = 0
+ bbmaxw = 0
+ bbh = 0
+ }
+
if ui.ContentBox {
bbw += margin.Dx()+padding.Dx()
bbmaxw += margin.Dx()+padding.Dx()
@@ -124,12 +162,20 @@
k.UI.Layout(dui, k, sizeAvail.Sub(image.Pt(0, cur.Y+lineY)), true)
childSize := k.R.Size()
var kr image.Rectangle
- if nx == 0 || cur.X+childSize.X <= sizeAvail.X {
+ var shouldCol bool
+ if ui.Disp == Flex {
+ shouldCol = ui.Dir == Column
+ } else if display(k) == Block {
+ shouldCol = true
+ }
+ if (nx == 0 || cur.X+childSize.X <= sizeAvail.X) && !shouldCol {
+ // Put on same line
kr = rect(childSize).Add(cur).Add(padding.Topleft())
cur.X += childSize.X
lineY = maximum(lineY, childSize.Y)
nx += 1
} else {
+ // Put on new line
if nx > 0 {
fixValign(ui.Kids[i-nx : i])
cur.X = 0
@@ -166,6 +212,13 @@
ui.size.Y = osize.Y
}
self.R = rect(ui.size.Add(margin.Size()))
+}
+
+func display(k *duit.Kid) (d Display) {
+ if b, ok := k.UI.(Boxable); ok {
+ return b.Display()
+ }
+ return InlineBlock
}
func (ui *Box) Draw(dui *duit.DUI, self *duit.Kid, img *draw.Image, orig image.Point, m draw.Mouse, force bool) {