Speech bubbles, a chat log that follows its end, a composer that sends on Enter, typing dots, chat and channel lists, read receipts, reactions, emoji and sticker pickers, mentions, date and unread rules, system lines, member lists, user cards, a mailbox, mail reader and letter, a notification bell, call controls, a voice meter, attachment chips, link previews and polls. pixel's editor takes EnterSends; display's Bubble takes a Fill and exports EmojiSprite. data.List names its rows, which also lets typing choose one. layout.ScrollArea and input.TextArea hold still under Inert, and an inert scroll view leaves the Tab order until it wakes.
71 lines
2.2 KiB
Go
71 lines
2.2 KiB
Go
package input
|
|
|
|
import (
|
|
"fmt"
|
|
"unicode/utf8"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
type TextAreaOptions struct {
|
|
Placeholder string
|
|
// MaxLength caps runes and shows a count.
|
|
MaxLength int
|
|
// Width is zero for 160 dots, Lines zero for 4.
|
|
Width, Lines int
|
|
Disabled bool
|
|
// EnterSends submits on Enter; Shift+Enter breaks lines.
|
|
EnterSends bool
|
|
}
|
|
|
|
type areaKey struct{}
|
|
|
|
type areaState struct {
|
|
scroll ui.ScrollState
|
|
line int
|
|
text string
|
|
// held is last frame's offset, kept while disabled.
|
|
held float32
|
|
}
|
|
|
|
type TextAreaResult = TextInputResult
|
|
|
|
// TextArea wraps lines in its own scroll, following the caret.
|
|
func TextArea(c *ui.Context, text string, value *string, o TextAreaOptions) TextAreaResult {
|
|
k := pixel.K(c)
|
|
lines := pixel.Or("TextArea Lines", o.Lines, 4)
|
|
col := ui.Column(c).Width(k.D(pixel.Or("TextArea Width", o.Width, 160))).Gap(k.D(2))
|
|
st := ui.Local(col, areaKey{}, func() areaState { return areaState{line: -1} })
|
|
// Disabled or inert, the view holds still.
|
|
if o.Disabled || pixel.Current(c).Inert {
|
|
st.scroll.Y = st.held
|
|
}
|
|
var ed pixel.Edit
|
|
col.Children(func() {
|
|
view := ui.Scroll(c).TrackScroll(&st.scroll).Height(k.D(lines*pixel.LineHeight+6)).Padding(k.D(3), k.D(4))
|
|
view.Draw(func(p *ui.Painter, r ui.Rect) {
|
|
g := k.Canvas(p, r)
|
|
g.Frame(0, 0, g.W, g.H, pixel.Sunken, pixel.Well)
|
|
}).Children(func() {
|
|
ed = pixel.Editor(c, value, pixel.EditorOptions{Multiline: true, Label: text, Placeholder: o.Placeholder, MaxLength: o.MaxLength, Disabled: o.Disabled, EnterSends: o.EnterSends})
|
|
})
|
|
// Keep the caret's line in view after it moves.
|
|
if ed.Line != st.line || *value != st.text {
|
|
top, bottom := k.D(ed.Line*pixel.LineHeight), k.D((ed.Line+1)*pixel.LineHeight)
|
|
if y := max(min(st.scroll.Y, top), bottom-k.D(lines*pixel.LineHeight)); y != st.scroll.Y {
|
|
st.scroll.Y = y
|
|
view.TrackScroll(&st.scroll)
|
|
}
|
|
st.line, st.text = ed.Line, *value
|
|
}
|
|
st.held = st.scroll.Y
|
|
if o.MaxLength > 0 {
|
|
count := fmt.Sprintf("%d/%d", utf8.RuneCountInString(*value), o.MaxLength)
|
|
ui.Row(c).Justify(ui.End).Children(func() { pixel.Text(c, count, pixel.TextOptions{Slot: pixel.Faint}) })
|
|
}
|
|
})
|
|
return result(col, ed, text)
|
|
}
|