From 77c7bc9b187450818ec068239a3ae836d01fdebc Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Sat, 22 Aug 2026 19:12:01 -0400 Subject: gui: a widget layer, so the next program does not write one cal9 had grown its own: borders drawn four fills at a time in two places, a button-3 menu, hit regions, buttons, text wrapping. None of that is about calendars, and a second program would have written it again, differently. It lives in gui/ui now. It is a kit and not a framework. Every call draws a thing and, where it makes sense, records where it drew it; the program keeps its own event loop and its own idea of what to redraw. Menu takes a redraw function rather than calling back into anything. The moment the library owns the loop it stops being usable by the next program, which was the problem being fixed. Body is the fix for two bugs that turned out to be one. rio draws a window's border into the client's own image and puts corner cursors in it, taking those clicks before the client sees them: the border is rio's resize handle. Filling the whole rectangle erased it, so the border vanished and so did resizing. Drawing inside Body gives both back, and the drag-to-resize cal9 had grown -- which could only ever shrink, since a window stops getting mouse events once the pointer leaves it -- was deleted rather than fixed. Field is one line of editable text with a cursor, backspace, ^U and a paste from /dev/snarf. No selection, no mouse placement, no history: a form asking for a summary and a time needs none of them and each is somewhere to be subtly wrong. Co-Authored-By: Claude Opus 5 --- gui/ui/menu.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 gui/ui/menu.go (limited to 'gui/ui/menu.go') diff --git a/gui/ui/menu.go b/gui/ui/menu.go new file mode 100644 index 0000000..7fe7ff8 --- /dev/null +++ b/gui/ui/menu.go @@ -0,0 +1,79 @@ +package ui + +import "9front/gui/draw" + +// An Item is one line of a menu. +type Item struct { + Label string + Do func() +} + +// Menu pops up at p and tracks until button 3 comes back up, the way +// page(1) and vdir(1) do it: press 3, drag, release on an item. +// +// It keeps itself on the screen, which is the detail everyone forgets +// until the menu opens near the bottom edge and half of it is missing. +// redraw is called if the menu is dismissed without a choice. +func (u *UI) Menu(items []Item, at draw.Point, mc *draw.Mousectl, redraw func()) { + if len(items) == 0 { + return + } + + lh := int32(u.F.Height) + 4 + var w int32 + for _, it := range items { + if x := u.F.Width(it.Label) + 20; x > w { + w = x + } + } + h := lh*int32(len(items)) + 4 + win := u.Win.Rect() + r := draw.Rect(at.X, at.Y, at.X+w, at.Y+h) + if r.Max.X > win.Max.X { + r = r.Add(draw.Point{X: win.Max.X - r.Max.X}) + } + if r.Max.Y > win.Max.Y { + r = r.Add(draw.Point{Y: win.Max.Y - r.Max.Y}) + } + + sel := -1 + paint := func() { + u.Fill(r, "tag") + u.Border(r, "border") + for i, it := range items { + ir := draw.Rect(r.Min.X+1, r.Min.Y+2+int32(i)*lh, + r.Max.X-1, r.Min.Y+2+int32(i+1)*lh) + if i == sel { + u.Fill(ir, "today") + } + u.Text(draw.Point{X: ir.Min.X + 8, Y: ir.Min.Y + 2}, "ink", it.Label) + } + u.D.Flush() + } + itemAt := func(p draw.Point) int { + if p.X < r.Min.X || p.X >= r.Max.X { + return -1 + } + i := int((p.Y - r.Min.Y - 2) / lh) + if i < 0 || i >= len(items) || p.Y < r.Min.Y+2 { + return -1 + } + return i + } + paint() + + for m := range mc.C { + if n := itemAt(m.Point); n != sel { + sel = n + paint() + } + if m.Buttons&4 == 0 { + if sel >= 0 { + items[sel].Do() + } else { + redraw() + } + return + } + } +} -- cgit v1.2.3