Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,15 +368,29 @@ func (c *Context) bringToFront(cnt *container) {
}

func (c *Context) hoveringRootContainer() *container {
p := c.pointingPosition()
return c.rootContainerAt(c.pointingPosition())
}

// rootContainerAt returns the topmost open root container whose hit area contains p, or nil if there is none.
func (c *Context) rootContainerAt(p image.Point) *container {
for i := len(c.rootContainers) - 1; i >= 0; i-- {
cnt := c.rootContainers[i]
if !cnt.open {
continue
}
if p.In(cnt.layout.Bounds) {
if p.In(c.rootContainerHitBounds(cnt)) {
return cnt
}
}
return nil
}

// rootContainerHitBounds returns the area of a root container that receives pointing input.
// A collapsed container shows only its title bar, so only the title bar receives input.
func (c *Context) rootContainerHitBounds(cnt *container) image.Rectangle {
bounds := cnt.layout.Bounds
if cnt.collapsed {
bounds.Max.Y = min(bounds.Max.Y, bounds.Min.Y+c.style().titleHeight)
}
return bounds
}
6 changes: 1 addition & 5 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,7 @@ func (c *Context) update(f func(ctx *Context) error) (inputCapturingState InputC
// Check whether the cursor is on any of the root containers.
pt := c.pointingPosition()
for _, cnt := range c.rootContainers {
bounds := cnt.layout.Bounds
if cnt.collapsed {
bounds.Max.Y = cnt.layout.BodyBounds.Min.Y
}
if pt.In(bounds) {
if pt.In(c.rootContainerHitBounds(cnt)) {
inputCapturingState |= InputCapturingStateHover
}
}
Expand Down
55 changes: 55 additions & 0 deletions debugui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,58 @@ func TestUnusedContainer(t *testing.T) {
t.Errorf("got: %v, want: %v", got, want)
}
}

func TestCollapsedWindowDoesNotCoverWindowsBelow(t *testing.T) {
const (
bottom = 0
top = 1
)
var d debugui.DebugUI
update := func() {
t.Helper()
if _, err := d.Update(func(ctx *debugui.Context) error {
ctx.Window("Bottom", image.Rect(50, 70, 260, 180), func(layout debugui.ContainerLayout) {})
ctx.Window("Top", image.Rect(0, 0, 320, 240), func(layout debugui.ContainerLayout) {})
return nil
}); err != nil {
t.Fatal(err)
}
}
update()

// The title bar of Top is 24 pixels tall by default.
onTopTitleBar := image.Pt(10, 10)
onBottomBelowTopTitleBar := image.Pt(100, 100)
onTopBodyOnly := image.Pt(10, 200)
outside := image.Pt(400, 400)

testCases := []struct {
name string
pt image.Point
want int
setup func()
}{
{name: "expanded title bar", pt: onTopTitleBar, want: top},
{name: "expanded body over bottom", pt: onBottomBelowTopTitleBar, want: top},
{name: "expanded body only", pt: onTopBodyOnly, want: top},
{name: "outside", pt: outside, want: -1},
{name: "collapsed title bar", pt: onTopTitleBar, want: top, setup: func() { d.SetRootContainerCollapsed(top, true) }},
{name: "collapsed title bar bottom edge", pt: image.Pt(10, 23), want: top},
{name: "collapsed just below title bar", pt: image.Pt(10, 24), want: -1},
{name: "collapsed body over bottom", pt: onBottomBelowTopTitleBar, want: bottom},
{name: "collapsed body only", pt: onTopBodyOnly, want: -1},
{name: "collapsed and moved, old title bar", pt: onTopTitleBar, want: -1, setup: func() { d.MoveRootContainer(top, image.Pt(0, 150)) }},
{name: "collapsed and moved, new title bar", pt: image.Pt(10, 160), want: top},
{name: "collapsed and moved, over bottom", pt: onBottomBelowTopTitleBar, want: bottom},
{name: "expanded again", pt: image.Pt(10, 200), want: top, setup: func() { d.SetRootContainerCollapsed(top, false) }},
}
for _, tc := range testCases {
if tc.setup != nil {
tc.setup()
update()
}
if got := d.RootContainerIndexAt(tc.pt); got != tc.want {
t.Errorf("%s: RootContainerIndexAt(%v): got: %d, want: %d", tc.name, tc.pt, got, tc.want)
}
}
}
22 changes: 22 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package debugui

import "image"

func IDPartFromCaller() string {
pc := caller()
return idPartFromCaller(pc)
Expand Down Expand Up @@ -89,3 +91,23 @@ func TextIndexFromX(str string, x int) int {
func TextWidth(str string) int {
return textWidth(str)
}

// RootContainerIndexAt returns the z-order index of the topmost root container hit at p, or -1.
func (d *DebugUI) RootContainerIndexAt(p image.Point) int {
cnt := d.ctx.rootContainerAt(p)
for i, c := range d.ctx.rootContainers {
if c == cnt {
return i
}
}
return -1
}

func (d *DebugUI) SetRootContainerCollapsed(index int, collapsed bool) {
d.ctx.rootContainers[index].collapsed = collapsed
}

func (d *DebugUI) MoveRootContainer(index int, delta image.Point) {
cnt := d.ctx.rootContainers[index]
cnt.layout.Bounds = cnt.layout.Bounds.Add(delta)
}