Files
MujicaUI/prompt_composer.go
T
2026-10-05 15:02:31 -04:00

100 lines
2.9 KiB
Go

package mujica
import (
"log/slog"
"strings"
"github.com/egoist/mygo/ui"
)
// PromptComposerOptions configure a PromptComposer.
type PromptComposerOptions struct {
Placeholder string
// Label names the field; empty uses the built-in "Message".
Label string
// CmdEnterSends sends with Cmd+Enter (Ctrl+Enter) and makes Enter a newline.
CmdEnterSends bool
// MaxHeight caps the field in DIPs before it scrolls; zero means 200.
MaxHeight float32
// Attachments builds the row above the field, as AttachmentChips.
Attachments func()
// Tools builds the left of the bar below the field; Actions its right, as a SendButton.
Tools, Actions func()
Disabled bool
}
// promptHooks are key handlers menus over a composer add each frame.
type promptHooks struct{ list []func(ui.InputEvent) bool }
type promptHooksKey struct{}
// PromptComposer is a multi-line prompt: Enter sends and Shift+Enter breaks the line, or Cmd+Enter sends.
// Submitted never fires for blank text.
func PromptComposer(c *ui.Context, value *string, opts PromptComposerOptions) InputResult {
if value == nil {
panic("mujica: PromptComposer needs a value")
}
if opts.MaxHeight < 0 {
panic("mujica: PromptComposer MaxHeight must not be negative")
}
u := Density(c).Unit()
maxH := opts.MaxHeight
if maxH == 0 {
maxH = 200
}
label := opts.Label
if label == "" {
label = msg(c, "prompt_composer.label")
}
var r InputResult
r.Element = ui.Column(c).Gap(u * 2).MinWidth(0).Disabled(opts.Disabled)
hooks := ui.Local(r.Element, promptHooksKey{}, func() *promptHooks { return &promptHooks{} })
h := *hooks
h.list = h.list[:0]
r.Element.Children(func() {
if opts.Attachments != nil {
ui.Row(c).Gap(u * 2).Wrap().Children(opts.Attachments)
}
ta := TextAreaOptions{Label: label, Placeholder: opts.Placeholder, MaxHeight: maxH,
keys: func(ev ui.InputEvent) bool {
for _, f := range h.list {
if f(ev) {
return true
}
}
return false
}}
if !opts.CmdEnterSends {
ta.SubmitKey = ui.KeyEnter
}
area := TextArea(c, value, ta)
r.Input, r.changed = area.Input, area.Changed()
r.submitted = area.Submitted() && strings.TrimSpace(*value) != ""
if opts.Tools != nil || opts.Actions != nil {
ui.Row(c).Gap(u * 2).AlignItems(ui.Center).Children(func() {
ui.Row(c).Gap(u).AlignItems(ui.Center).Grow(1).Children(func() {
if opts.Tools != nil {
opts.Tools()
}
})
if opts.Actions != nil {
opts.Actions()
}
})
}
})
if r.submitted {
slog.Debug("mujica: prompt_composer submit", "chars", len(*value))
}
return r
}
// promptComposerHook adds a key handler that runs before the composer's editor this frame.
func promptComposerHook(composer InputResult, f func(ui.InputEvent) bool) {
if composer.Element == nil {
panic("mujica: menus need a built PromptComposer")
}
h := *ui.Local(composer.Element, promptHooksKey{}, func() *promptHooks { return &promptHooks{} })
h.list = append(h.list, f)
}