FormItem takes *ui.Context first (PRD 6.1); drop dead internal.Rule

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 14:10:53 -04:00
1 parent 486d2c6a86
commit 3e011fa47f
8 files changed
+22 -29

No files matched your search

+1 -1
View File
@@ -169,7 +169,7 @@ func TestFormFocusesDatePickerInput(t *testing.T) {
tt := ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
Form(c, &st, FormOptions{}, func(f *FormScope) {
FormItem(f, "debut", &day, FormItemOptions[Date]{Label: "Debut date", Required: true}, func() *ui.Element {
FormItem(c, f, "debut", &day, FormItemOptions[Date]{Label: "Debut date", Required: true}, func() *ui.Element {
r := DatePicker(c, &day, DatePickerOptions{Today: calendarToday})
input = r.Input
return r.Input
+1 -1
View File
@@ -14,7 +14,7 @@ name := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
res := mujica.Form(c, &state, mujica.FormOptions{}, func(f *mujica.FormScope) {
mujica.FormItem(f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
mujica.FormItem(c, f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
return mujica.InputGroup(c, &name, mujica.InputGroupOptions{Invalid: state.Error("name") != ""}).Input
})
})
+1 -1
View File
@@ -15,7 +15,7 @@ func ExampleForm() {
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
res := mujica.Form(c, &state, mujica.FormOptions{}, func(f *mujica.FormScope) {
mujica.FormItem(f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
mujica.FormItem(c, f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
return mujica.InputGroup(c, &name, mujica.InputGroupOptions{Invalid: state.Error("name") != ""}).Input
})
})
+5 -5
View File
@@ -110,7 +110,7 @@ func (g *gallery) formEdit(c *ui.Context, log func(string)) {
func (g *gallery) formFields(c *ui.Context, f *mujica.FormScope, log func(string)) {
s := &g.cForm
mujica.FormItem(f, "name", &s.name, mujica.FormItemOptions[string]{Label: g.t("Stage name", "艺名"), Required: true,
mujica.FormItem(c, f, "name", &s.name, mujica.FormItemOptions[string]{Label: g.t("Stage name", "艺名"), Required: true,
Description: g.t("Checked against the archive; “Oblivionis” is taken.", "将与档案核对;“Oblivionis”已被占用。"),
Validate: func(v string) string {
if len([]rune(strings.TrimSpace(v))) < 2 {
@@ -134,7 +134,7 @@ func (g *gallery) formFields(c *ui.Context, f *mujica.FormScope, log func(string
}}, func() *ui.Element {
return mujica.TextInput(c, &s.name, mujica.TextInputOptions{}).Input
})
mujica.FormItem(f, "password", &s.password, mujica.FormItemOptions[string]{Label: g.t("Password", "密码"), Required: true,
mujica.FormItem(c, f, "password", &s.password, mujica.FormItemOptions[string]{Label: g.t("Password", "密码"), Required: true,
Validate: func(v string) string {
if len([]rune(v)) < 8 {
return g.t("Use at least 8 characters.", "至少 8 个字符。")
@@ -147,14 +147,14 @@ func (g *gallery) formFields(c *ui.Context, f *mujica.FormScope, log func(string
}
return r.Input
})
mujica.FormItem(f, "debut", &s.debut, mujica.FormItemOptions[mujica.Date]{Label: g.t("Debut date", "首演日期"), Required: true}, func() *ui.Element {
mujica.FormItem(c, f, "debut", &s.debut, mujica.FormItemOptions[mujica.Date]{Label: g.t("Debut date", "首演日期"), Required: true}, func() *ui.Element {
r := mujica.DatePicker(c, &s.debut, mujica.DatePickerOptions{Clearable: true})
if r.Changed() {
log("debut " + s.debut.Format("2006-01-02"))
}
return r.Input
})
mujica.FormItem(f, "roles", &s.tags, mujica.FormItemOptions[[]string]{Label: g.t("Roles", "担当"), Required: true,
mujica.FormItem(c, f, "roles", &s.tags, mujica.FormItemOptions[[]string]{Label: g.t("Roles", "担当"), Required: true,
Description: g.t("Up to 4; press Enter after each.", "最多 4 个;每个后按 Enter。")}, func() *ui.Element {
r := mujica.TagsInput(c, &s.tags, mujica.TagsInputOptions{Max: 4, Invalid: s.st.Error("roles") != "", Placeholder: g.t("keyboard, bass…", "键盘、贝斯…")})
if r.Changed() {
@@ -162,7 +162,7 @@ func (g *gallery) formFields(c *ui.Context, f *mujica.FormScope, log func(string
}
return r.Input
})
mujica.FormItem(f, "terms", &s.terms, mujica.FormItemOptions[bool]{Label: g.t("Terms", "条款"),
mujica.FormItem(c, f, "terms", &s.terms, mujica.FormItemOptions[bool]{Label: g.t("Terms", "条款"),
Validate: func(v bool) string {
if !v {
return g.t("Accept the terms to continue.", "请先同意条款。")
+4 -4
View File
@@ -118,7 +118,7 @@ var formPages = []page{
}
formToggles(c, []string{"Labels beside fields", "Server rejects the submit"}, &d.horizontal, &d.slowServer)
res := mujica.Form(c, &d.st, mujica.FormOptions{Horizontal: d.horizontal}, func(f *mujica.FormScope) {
mujica.FormItem(f, "name", &d.name, mujica.FormItemOptions[string]{Label: "Stage name", Required: true, Initial: "Sakiko",
mujica.FormItem(c, f, "name", &d.name, mujica.FormItemOptions[string]{Label: "Stage name", Required: true, Initial: "Sakiko",
Description: "Checked against the archive; \"Oblivionis\" is taken.",
Async: func(v string, version uint64) {
log(fmt.Sprintf("async check started v%d", version))
@@ -134,7 +134,7 @@ var formPages = []page{
}}, func() *ui.Element {
return mujica.InputGroup(c, &d.name, mujica.InputGroupOptions{Invalid: d.st.Error("name") != ""}).Input
})
mujica.FormItem(f, "email", &d.email, mujica.FormItemOptions[string]{Label: "Email", Required: true,
mujica.FormItem(c, f, "email", &d.email, mujica.FormItemOptions[string]{Label: "Email", Required: true,
Validate: func(v string) string {
if !strings.Contains(v, "@") {
return "Enter an email address."
@@ -147,7 +147,7 @@ var formPages = []page{
}
return g.Input
})
mujica.FormItem(f, "tags", &d.tags, mujica.FormItemOptions[[]string]{Label: "Roles", Required: true, Initial: []string{"keyboard"}}, func() *ui.Element {
mujica.FormItem(c, f, "tags", &d.tags, mujica.FormItemOptions[[]string]{Label: "Roles", Required: true, Initial: []string{"keyboard"}}, func() *ui.Element {
return mujica.TagsInput(c, &d.tags, mujica.TagsInputOptions{Max: 4, Invalid: d.st.Error("tags") != ""}).Input
})
})
@@ -336,7 +336,7 @@ name := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
res := mujica.Form(c, &state, mujica.FormOptions{}, func(f *mujica.FormScope) {
mujica.FormItem(f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
mujica.FormItem(c, f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
return mujica.InputGroup(c, &name, mujica.InputGroupOptions{Invalid: state.Error("name") != ""}).Input
})
})
+3 -4
View File
@@ -99,7 +99,6 @@ type FormOptions struct {
// FormScope registers a Form's fields with FormItem.
type FormScope struct {
c *ui.Context
st *FormState
horizontal bool
}
@@ -140,7 +139,7 @@ func Form(c *ui.Context, st *FormState, opts FormOptions, fields func(f *FormSco
parts := FormParts{}
col := ui.Column(c).Gap(d.Unit() * 5)
parts.Element = col
scope := &FormScope{c: c, st: st, horizontal: opts.Horizontal}
scope := &FormScope{st: st, horizontal: opts.Horizontal}
col.Children(func() {
// A new key after Reset drops the controls' drafts and errors.
ui.Box(c).Key(st.generation).Children(func() {
@@ -242,8 +241,8 @@ func formReset(st *FormState) {
}
// FormItem registers field name around the focusable element control returns.
func FormItem[T any](f *FormScope, name string, value *T, opts FormItemOptions[T], control func() *ui.Element) *ui.Element {
c, st := f.c, f.st
func FormItem[T any](c *ui.Context, f *FormScope, name string, value *T, opts FormItemOptions[T], control func() *ui.Element) *ui.Element {
st := f.st
if name == "" || opts.Label == "" {
panic("mujica: FormItem needs a name and a Label")
}
+7 -7
View File
@@ -25,7 +25,7 @@ func (p *formProbe) run(c *ui.Context) {
func (p *formProbe) view(c *ui.Context) {
res := Form(c, &p.st, FormOptions{Horizontal: p.horizontal}, func(f *FormScope) {
FormItem(f, "name", &p.name, FormItemOptions[string]{Label: "Name", Required: true, Initial: "Sakiko"}, func() *ui.Element {
FormItem(c, f, "name", &p.name, FormItemOptions[string]{Label: "Name", Required: true, Initial: "Sakiko"}, func() *ui.Element {
return InputGroup(c, &p.name, InputGroupOptions{Invalid: p.st.Error("name") != ""}).Input
})
opts := FormItemOptions[string]{Label: "Email", Validate: func(v string) string {
@@ -40,10 +40,10 @@ func (p *formProbe) view(c *ui.Context) {
p.asyncValues = append(p.asyncValues, v)
}
}
FormItem(f, "email", &p.email, opts, func() *ui.Element {
FormItem(c, f, "email", &p.email, opts, func() *ui.Element {
return InputGroup(c, &p.email, InputGroupOptions{Invalid: p.st.Error("email") != ""}).Input
})
FormItem(f, "tags", &p.tags, FormItemOptions[[]string]{Label: "Tags", Required: true, Initial: []string{"rose"}}, func() *ui.Element {
FormItem(c, f, "tags", &p.tags, FormItemOptions[[]string]{Label: "Tags", Required: true, Initial: []string{"rose"}}, func() *ui.Element {
return TagsInput(c, &p.tags, TagsInputOptions{}).Input
})
})
@@ -222,7 +222,7 @@ func TestFormResetClearsControlState(t *testing.T) {
tt := ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
Form(c, &st, FormOptions{}, func(f *FormScope) {
FormItem(f, "tags", &tags, FormItemOptions[[]string]{Label: "Tags"}, func() *ui.Element {
FormItem(c, f, "tags", &tags, FormItemOptions[[]string]{Label: "Tags"}, func() *ui.Element {
return TagsInput(c, &tags, TagsInputOptions{Validate: func(s string) string {
seen = s
return "Invalid tag"
@@ -289,13 +289,13 @@ func TestFormCrossFieldRuleAtSettle(t *testing.T) {
tt := ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
res := Form(c, &st, FormOptions{}, func(f *FormScope) {
FormItem(f, "user", &user, FormItemOptions[string]{Label: "User", Async: func(_ string, v uint64) { version = v }}, func() *ui.Element {
FormItem(c, f, "user", &user, FormItemOptions[string]{Label: "User", Async: func(_ string, v uint64) { version = v }}, func() *ui.Element {
return InputGroup(c, &user, InputGroupOptions{}).Input
})
FormItem(f, "pw", &pw, FormItemOptions[string]{Label: "Password"}, func() *ui.Element {
FormItem(c, f, "pw", &pw, FormItemOptions[string]{Label: "Password"}, func() *ui.Element {
return InputGroup(c, &pw, InputGroupOptions{}).Input
})
FormItem(f, "confirm", &confirm, FormItemOptions[string]{Label: "Confirm", Validate: func(v string) string {
FormItem(c, f, "confirm", &confirm, FormItemOptions[string]{Label: "Confirm", Validate: func(v string) string {
if v != pw {
return "Passwords differ."
}
-6
View File
@@ -6,12 +6,6 @@ import (
"github.com/egoist/mygo/ui"
)
// Rule draws a pixel-aligned 1 DIP horizontal line at y.
func Rule(p *ui.Painter, x0, x1, y float32, c ui.Color) {
yy := ruleY(p, y)
p.Line(x0, yy, x1, yy, 1, c)
}
// ruleY moves y to where a 1 DIP line fills whole device pixels.
func ruleY(p *ui.Painter, y float32) float32 {
s := float64(p.Scale())