ref: 608bf02ace201d9bbe10fd061a0476da36e8d5af
parent: 179860541723141da76c2760a466c76faf6edc94
author: Philip Silva <[email protected]>
date: Wed Jul 21 17:14:49 EDT 2021
add also position absolute - partially revert 39fbbc8 for a (Use InnerNodesToBox directly everywhere) - fix body height
--- a/browser/browser.go
+++ b/browser/browser.go
@@ -353,7 +353,7 @@
var h int
w := n.Width()
if n.Data() != "body" {
- w = n.Height()
+ h = n.Height()
}
mw, err := n.CssPx("max-width")
if err != nil {
@@ -762,6 +762,71 @@
n.Attr = append(n.Attr, newAttr)
}
+func placeFunc(name string, place *duit.Place) func(self *duit.Kid, sizeAvail image.Point) {
+ return func(self *duit.Kid, sizeAvail image.Point) {
+ for i, kid := range place.Kids {
+ el := kid.UI.(*Element)
+ if i == 0 {
+ kid.UI.Layout(dui, self, sizeAvail, true)
+ kid.R = self.R
+ } else {
+ kid.UI.Layout(dui, kid, sizeAvail, true)
+ if t, err := el.n.CssPx("top"); err == nil {
+ kid.R.Min.Y += t
+ kid.R.Max.Y += t
+ }
+ if l, err := el.n.CssPx("left"); err == nil {
+ kid.R.Max.X += l
+ kid.R.Min.X += l
+ }
+ if r, err := el.n.CssPx("right"); err == nil {
+ w := kid.R.Max.X
+ kid.R.Max.X = sizeAvail.X - r
+ kid.R.Min.X = sizeAvail.X - w
+ }
+ }
+ }
+ }
+}
+
+// arrangeAbsolute positioned elements, if any
+func arrangeAbsolute(n *nodes.Node, elements ...*Element) (ael *Element, ok bool) {
+ absolutes := make([]*Element, 0, 1)
+ other := make([]*Element, 0, len(elements))
+
+ for _, el := range elements {
+ if el.n.Css("position") == "absolute" {
+ absolutes = append(absolutes, el)
+ } else {
+ other = append(other, el)
+ }
+ }
+
+ if len(absolutes) == 0 {
+ return nil, false
+ }
+
+ bg, err := dui.Display.AllocImage(image.Rect(0, 0, 10, 10), draw.ARGB32, true, 0x00000000)
+ if err != nil {
+ log.Fatalf("%v", err)
+ }
+ uis := make([]duit.UI, 0, len(other)+1)
+ na := Arrange(n, other...)
+ if na != nil {
+ uis = append(uis, na)
+ }
+ for _, a := range absolutes {
+ uis = append(uis, a)
+ }
+ pl := &duit.Place{
+ Kids: duit.NewKids(uis...),
+ Background: bg,
+ }
+ pl.Place = placeFunc(n.QueryRef(), pl)
+
+ return NewElement(pl, n), true
+}
+
func Arrange(n *nodes.Node, elements ...*Element) *Element {
if n.IsFlex() {
if n.IsFlexDirectionRow() {
@@ -771,6 +836,10 @@
}
}
+ if ael, ok := arrangeAbsolute(n, elements...); ok {
+ return ael
+ }
+
rows := make([][]*Element, 0, 10)
currentRow := make([]*Element, 0, 10)
flushCurrentRow := func() {
@@ -1135,9 +1204,22 @@
return NewElement(innerContent, n)
case "a":
- href := n.Attr("href")
- el = InnerNodesToBox(r+1, b, n)
+ var href = n.Attr("href")
+ var innerContent duit.UI
+ if nodes.IsPureTextContent(*n) {
+ innerContent = NewLabel(
+ n.ContentString(false),
+ n,
+ )
+ } else {
+ innerContent = InnerNodesToBox(r+1, b, n)
+ }
+ if innerContent == nil {
+ return nil
+ }
+ el := NewElement(innerContent, n)
el.makeLink(href)
+ return el
case "noscript":
if ExperimentalJsInsecure || !EnableNoScriptTag {
return
@@ -1172,9 +1254,10 @@
}
func InnerNodesToBox(r int, b *Browser, n *nodes.Node) *Element {
- els := make([]*Element, 0, len(n.Children))
+ items := n.CBItems()
+ els := make([]*Element, 0, len(items))
- for _, c := range n.Children {
+ for _, c := range items {
if c.IsDisplayNone() {
continue
}
@@ -1183,7 +1266,7 @@
els = append(els, ls...)
} else if nodes.IsPureTextContent(*n) {
// Handle text wrapped in unwrappable tags like p, div, ...
- ls := NewText(c.Content(false), c.Children[0])
+ ls := NewText(c.Content(false), items[0])
if len(ls) == 0 {
continue
}
@@ -1245,6 +1328,10 @@
case *duit.Edit:
case *duit.Button:
case *duit.List:
+ case *duit.Place:
+ for _, kid := range v.Kids {
+ traverseTree(r+1, kid.UI, f)
+ }
case *duit.Scroll:
case *CodeView:
default:
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -166,6 +166,69 @@
return false
}
+// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
+func (n *Node) IsContainingBlock(position string) bool {
+ if position == "absolute" {
+ return n.Css("position") == "fixed" || n.Css("position") == "absolute" ||
+ n.Css("position") == "relative" || n.Css("position") == "sticky" || n.Data() == "body"
+ }
+ return false
+}
+
+var ContainingPositions = map[string]string{
+ "absolute": "relative",
+}
+
+func (n *Node) FindNextPositions(position string) (ps []*Node) {
+ for _, c := range n.Children {
+ if c.Css("position") == ContainingPositions[position] {
+ continue
+ }
+ if c.Css("position") == position {
+ ps = append(ps, c)
+ } else {
+ ps = append(ps, c.FindNextPositions(position)...)
+ }
+ }
+ return
+}
+
+// CB returns the Containing Block.
+func (n *Node) CB() (blk *Node) {
+ if n.parent == nil || n.Data() == "body" {
+ return n
+ }
+ if n.Css("position") == "absolute" {
+ for p := n.parent; p != nil; p = p.parent {
+ if p.IsContainingBlock("absolute") {
+ return p
+ }
+ }
+ } else {
+ return n.parent
+ }
+ return nil
+}
+
+// CBItems returns items that are within this containing block
+func (n *Node) CBItems() (cbis []*Node) {
+ cbis = make([]*Node, 0, len(n.Children))
+
+ if n.IsContainingBlock("absolute") {
+ ps := n.FindNextPositions("absolute")
+ for _, p := range ps {
+ cbis = append(cbis, p)
+ }
+ }
+ for _, c := range n.Children {
+ if c.CB() == n && c.Css("position") != "absolute" {
+ cbis = append(cbis, c)
+ }
+ }
+
+ return
+}
+
// QueryRef relative to html > body
func (n *Node) QueryRef() string {
nRef, ok := n.queryRef()
--- a/nodes/nodes_test.go
+++ b/nodes/nodes_test.go
@@ -105,3 +105,114 @@
}
}
+func TestContainingBlock(t *testing.T) {
+ tests := map[string]string{
+ "body": `
+ <html>
+ <body>
+ <div>
+ <a style="position: absolute;">link</a>
+ </div>
+ </body>
+ </html>
+ `,
+ "div": `
+ <html>
+ <body>
+ <div style="position: relative;">
+ <a style="position: absolute;">link</a>
+ </div>
+ </body>
+ </html>
+ `,
+ "main": `
+ <html>
+ <body>
+ <main style="position: relative;">
+ <article>
+ <a style="position: absolute;">link</a>
+ </article>
+ </main>
+ </body>
+ </html>
+ `,
+ }
+ for cbTag, htm := range tests {
+ doc, err := html.Parse(strings.NewReader(htm))
+ if err != nil { t.Fatalf(err.Error()) }
+ nt := NewNodeTree(doc, style.Map{}, make(map[*html.Node]style.Map), nil)
+ cb := nt.Find(cbTag)
+ a := nt.Find("a")
+ if a.CB() != cb {
+ t.Fail()
+ }
+ }
+}
+
+func TestCBItems(t *testing.T) {
+ tests := map[string]map[string][]string{
+ `
+ <html>
+ <body>
+ <div>
+ <a style="position: absolute;">link</a>
+ </div>
+ </body>
+ </html>
+ `: {
+ "body": {"a", "", "div", ""},
+ "div": {"", ""},
+ "a": {"link"},
+ },
+ `
+ <html>
+ <body>
+ <div style="position: relative;">
+ <a style="position: absolute;">link</a>
+ </div>
+ </body>
+ </html>
+ `: {
+ "body": {"", "div", ""},
+ "div": {"a", "", ""},
+ "a": {"link"},
+ },
+ `
+ <html>
+ <body>
+ <main style="position: relative;">
+ <article>
+ <a style="position: absolute;">link</a>
+ </article>
+ </main>
+ </body>
+ </html>
+ `: {
+ "body": {"", "main", ""},
+ "main": {"a", "", "article", ""},
+ "article": {"", ""},
+ "a": {"link"},
+ },
+ }
+ for htm, m := range tests {
+ doc, err := html.Parse(strings.NewReader(htm))
+ if err != nil { t.Fatalf(err.Error()) }
+ nt := NewNodeTree(doc, style.Map{}, make(map[*html.Node]style.Map), nil)
+ for from, tos := range m {
+ t.Logf("from: %v", from)
+ f := nt.Find(from)
+ cbis := f.CBItems()
+ if len(cbis) != len(tos) {
+ t.Errorf("len(cbis)=%+v", cbis)
+ } else {
+ t.Logf("lengths match")
+ }
+ for i, cbi := range cbis {
+ t.Logf("%+v %v", cbi.Data(), cbi.Type())
+ if strings.TrimSpace(cbi.Data()) != tos[i] {
+ t.Fail()
+ }
+ }
+ }
+ }
+}