Skip to content

Commit 6fd8cf0

Browse files
hajimehoshicodex
andcommitted
debugui: keep dropdown menus within the screen
Choose the space above or below the dropdown button and limit the menu size to the available screen area. Clamp its horizontal position and account for the UI scale so all options remain reachable by scrolling. Recompute menu bounds each update so an open dropdown follows its button when the parent window moves or scrolls. Closes #59 Co-Authored-By: Codex <noreply@openai.com>
1 parent dc05499 commit 6fd8cf0

1 file changed

Lines changed: 41 additions & 19 deletions

File tree

‎dropdown.go‎

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ func (c *Context) dropdown(selectedIndex *int, options []string, idPart string)
2929
}
3030
last := *selectedIndex
3131

32+
buttonBounds, err := c.layoutNext()
33+
if err != nil {
34+
return nil, err
35+
}
36+
3237
id := c.idStack.push(idPart)
3338
dropdownContainer := c.container(id, 0)
3439

@@ -41,6 +46,8 @@ func (c *Context) dropdown(selectedIndex *int, options []string, idPart string)
4146
dropdownContainer.open = false
4247
}
4348

49+
dropdownContainer.layout.Bounds = c.dropdownBounds(buttonBounds, len(options))
50+
4451
_ = c.wrapEventHandlerAndError(func() (EventHandler, error) {
4552
windowOptions := optionNoResize | optionNoTitle
4653

@@ -67,7 +74,7 @@ func (c *Context) dropdown(selectedIndex *int, options []string, idPart string)
6774
return nil, nil
6875
})
6976

70-
return c.widget(id, optionAlignCenter, nil, func(bounds image.Rectangle, wasFocused bool) EventHandler {
77+
e := c.widgetWithBounds(id, optionAlignCenter, buttonBounds, func(bounds image.Rectangle, wasFocused bool) EventHandler {
7178
var e EventHandler
7279

7380
dropdownContainer := c.container(id, 0)
@@ -91,27 +98,9 @@ func (c *Context) dropdown(selectedIndex *int, options []string, idPart string)
9198
dropdownContainer.open = false
9299
dropdownContainer.dropdownCloseTimer.stop()
93100
} else {
94-
wasClosedBefore := !dropdownContainer.open
95-
96101
// Open the dropdown and cancel any pending close delay
97102
dropdownContainer.open = true
98103
dropdownContainer.dropdownCloseTimer.stop()
99-
100-
if wasClosedBefore {
101-
dropdownPos := image.Pt(bounds.Min.X, bounds.Max.Y)
102-
buttonWidth := bounds.Dx()
103-
st := c.style()
104-
optionHeight := st.defaultHeight + st.spacing
105-
totalHeight := len(options)*optionHeight - st.spacing + st.padding*2
106-
107-
maxDropdownHeight := st.defaultHeight * 12 // around 10 items visible?
108-
actualHeight := min(totalHeight, maxDropdownHeight)
109-
110-
dropdownContainer.layout.Bounds = image.Rectangle{
111-
Min: dropdownPos,
112-
Max: dropdownPos.Add(image.Pt(buttonWidth, actualHeight)),
113-
}
114-
}
115104
}
116105
}
117106
if last != *selectedIndex {
@@ -120,6 +109,9 @@ func (c *Context) dropdown(selectedIndex *int, options []string, idPart string)
120109

121110
return e
122111
}, func(bounds image.Rectangle) {
112+
if !c.currentContainer().layout.BodyBounds.Overlaps(bounds) {
113+
return
114+
}
123115
c.drawWidgetFrame(id, bounds, colorButton, optionAlignCenter)
124116

125117
arrowWidth := bounds.Dy()
@@ -134,4 +126,34 @@ func (c *Context) dropdown(selectedIndex *int, options []string, idPart string)
134126
}
135127
c.drawIcon(icon, arrowBounds, c.style().colors[colorText])
136128
})
129+
return e, nil
130+
}
131+
132+
func (c *Context) dropdownBounds(buttonBounds image.Rectangle, optionCount int) image.Rectangle {
133+
st := c.style()
134+
height := min(optionCount*(st.defaultHeight+st.spacing)-st.spacing+st.padding*2, st.defaultHeight*12)
135+
width := buttonBounds.Dx()
136+
pos := image.Pt(buttonBounds.Min.X, buttonBounds.Max.Y)
137+
138+
if c.screenWidth > 0 {
139+
screenWidth := c.screenWidth / c.Scale()
140+
width = min(width, screenWidth)
141+
pos.X = clamp(pos.X, 0, screenWidth-width)
142+
}
143+
if c.screenHeight > 0 {
144+
screenHeight := c.screenHeight / c.Scale()
145+
above := clamp(buttonBounds.Min.Y, 0, screenHeight)
146+
below := screenHeight - clamp(buttonBounds.Max.Y, 0, screenHeight)
147+
if height > below && above > below {
148+
height = min(height, above)
149+
pos.Y = above - height
150+
} else {
151+
height = min(height, below)
152+
pos.Y = screenHeight - below
153+
}
154+
}
155+
return image.Rectangle{
156+
Min: pos,
157+
Max: pos.Add(image.Pt(width, height)),
158+
}
137159
}

0 commit comments

Comments
 (0)