shithub: mycel

Download patch

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) {