100 lines
2.9 KiB
Go
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)
|
|
}
|