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>
82 lines
2.4 KiB
Go
82 lines
2.4 KiB
Go
package input
|
|
|
|
import (
|
|
"log/slog"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/display"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
type TokenFieldOptions struct {
|
|
Placeholder string
|
|
// Width is in dots; zero means 200.
|
|
Width int
|
|
Disabled bool
|
|
}
|
|
|
|
type TokenFieldResult struct {
|
|
Element ui.Element
|
|
Changed bool
|
|
}
|
|
|
|
type tokenKey struct{}
|
|
|
|
// editorSlot keys the editor so added tags keep its focus.
|
|
type editorSlot struct{}
|
|
|
|
// TokenField shows tokens as tags; Enter or comma adds.
|
|
func TokenField(c *ui.Context, text string, tokens *[]string, o TokenFieldOptions) TokenFieldResult {
|
|
k := pixel.K(c)
|
|
e := ui.Row(c).Wrap().Width(k.D(pixel.Or("TokenField Width", o.Width, 200))).Padding(k.D(3), k.D(4)).Gap(k.D(3)).AlignItems(ui.Center)
|
|
e.Role(ui.RoleGroup).Label(text).Description(strings.Join(*tokens, ", "))
|
|
draft := ui.Local(e, tokenKey{}, func() string { return "" })
|
|
// The row stays enabled, so off is checked here.
|
|
off := o.Disabled || pixel.Current(c).Inert
|
|
res := TokenFieldResult{Element: e}
|
|
add := func(s string) {
|
|
if s = strings.TrimSpace(s); s != "" && !slices.Contains(*tokens, s) {
|
|
*tokens, res.Changed = append(*tokens, s), true
|
|
}
|
|
}
|
|
// A comma typed since the last frame cuts the draft.
|
|
if parts := strings.Split(*draft, ","); len(parts) > 1 && !off {
|
|
for _, p := range parts[:len(parts)-1] {
|
|
add(p)
|
|
}
|
|
*draft = parts[len(parts)-1]
|
|
}
|
|
if !off && pixel.Shortcut(e, 0, ui.KeyEnter) {
|
|
add(*draft)
|
|
*draft = ""
|
|
}
|
|
if !off && *draft == "" && len(*tokens) > 0 && pixel.Shortcut(e, 0, ui.KeyBackspace) {
|
|
*tokens, res.Changed = (*tokens)[:len(*tokens)-1], true
|
|
}
|
|
var ed pixel.Edit
|
|
e.Children(func() {
|
|
for i := 0; i < len(*tokens); i++ {
|
|
tok, removed := (*tokens)[i], false
|
|
ui.Box(c).Key(tok).Children(func() {
|
|
removed = display.Tag(c, tok, display.TagOptions{Removable: true, Disabled: o.Disabled}).Removed
|
|
})
|
|
if removed {
|
|
*tokens, res.Changed = slices.Delete(*tokens, i, i+1), true
|
|
i--
|
|
}
|
|
}
|
|
ui.Box(c).Key(editorSlot{}).Grow(1).MinWidth(k.D(40)).Children(func() {
|
|
ed = pixel.Editor(c, draft, pixel.EditorOptions{Label: text, Placeholder: o.Placeholder, Disabled: o.Disabled})
|
|
})
|
|
})
|
|
focus := ed.Focused
|
|
e.Draw(func(p *ui.Painter, r ui.Rect) { well(k.Canvas(p, r), pixel.Edge, focus) })
|
|
if res.Changed {
|
|
slog.Debug("8bit: tokens", "label", text, "tokens", len(*tokens))
|
|
}
|
|
return res
|
|
}
|