Files
8bitMyGoUI/agent/planquest.go
T
ZacharyZhang-NYandClaude Opus 5.5 03e1f91706 Upgrade MyGo to v0.5.5
Elements are now pass-scoped value handles: tests that read bounds
after a frame bind a ui.Handle. pixel tells frames apart by the build
pass's root, since the Tester clock no longer moves on its own.

Fix the regressions the upgrade brought:
- Clipboard reads in input handlers go through c.Services().
- MyGo notes an overlay's opener after building it, so AutoFocus
  inside lost it; pixel.AutoFocus focuses through a Handle, and
  archtest bans the raw call. Dialogs hand the focus back again.
- Elements around a press stay hovered: Tooltip tells a press-given
  focus by focus arriving under the pointer.
- The web host moves the Tester clock with real time, at most 500 ms
  a call, and gives components the real clock.

The gallery's source index also accepts CRLF checkouts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 01:31:08 -04:00

87 lines
2.4 KiB
Go

package agent
import (
"fmt"
"strings"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
// PlanStep is an objective; the first undone is under way.
type PlanStep struct {
Text string
Done bool
}
type PlanQuestOptions struct {
// Width is in dots; zero means 200.
Width int
}
// PlanQuest shows a plan's steps as quest objectives.
func PlanQuest(c *ui.Context, title string, steps []PlanStep, o PlanQuestOptions) ui.Element {
k := pixel.K(c)
if title == "" || len(steps) == 0 {
panic(fmt.Sprintf("agent: PlanQuest %q has %d steps", title, len(steps)))
}
w := pixel.Or("PlanQuest Width", o.Width, 200)
if need := max(64, pixel.Measure(k.Msg("quest_complete"))); w < need {
panic(fmt.Sprintf("agent: PlanQuest Width %d is under %d", w, need))
}
cur, done, h := len(steps), 0, pixel.LineHeight+4
lines, said := make([][]string, len(steps)), make([]string, len(steps))
for i, s := range steps {
if s.Text == "" {
panic(fmt.Sprintf("agent: PlanQuest step %d has no text", i))
}
lines[i] = pixel.Wrap(s.Text, w-16, 0)
h += len(lines[i]) * pixel.LineHeight
said[i] = s.Text
switch {
case s.Done:
done++
said[i] += ", " + k.Msg("done")
case cur == len(steps):
cur = i
said[i] += ", " + k.Msg("doing")
}
}
if cur == len(steps) {
h += pixel.LineHeight + 2
}
count := fmt.Sprintf("%d/%d", done, len(steps))
return ui.Box(c).Size(k.D(w), k.D(h)).Role(ui.RoleImage).Label(title).Description(count + "; " + strings.Join(said, "; ")).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
cw := pixel.MicroWidth(count)
g.Sprite(0, 2, icon.Scroll, 0, pixel.Warn, 1)
g.Text(16, 0, pixel.Wrap(title, w-20-cw, 1)[0], pixel.Ink, 1)
g.Micro(w-cw, 6, count, pixel.Ink, 1)
g.Checker(0, pixel.LineHeight+1, w, 1, pixel.Edge)
y := pixel.LineHeight + 4
for i, s := range steps {
ink := pixel.Ink
switch {
case s.Done:
ink = pixel.Faint
g.Sprite(0, y+2, icon.Check, 0, pixel.Good, 1)
case i == cur:
g.Sprite(0, y+2, icon.TriangleRight, 0, pixel.Accent, 1)
default:
g.Rect(2, y+5, 8, 8, pixel.Edge)
g.Rect(3, y+6, 6, 6, pixel.Well)
}
for j, l := range lines[i] {
g.Text(16, y+j*pixel.LineHeight, l, ink, 1)
}
y += len(lines[i]) * pixel.LineHeight
}
if cur == len(steps) {
q := k.Msg("quest_complete")
g.Text((w-pixel.Measure(q))/2, y+2, q, pixel.Good, 1)
}
})
}