Refactor: results expose Element as a named field
Every component result type now names its element Element instead of embedding *ui.Element. The eleven element-plus-changed results (AccordionView, CarouselView, CollapsibleView, ImageViewerView, PaginationResult, StepsResult, SidebarResult, TabsResult, RatingParts, FileDropZoneParts, TransferParts) fold into ChoiceResult. Fixes four Gallery demos where .Width(...).Changed() read the element's flag instead of the component's: Accordion, MultiSelect, Cascader, TreeSelect. TestDataAccordionDemoLogsChange covers the Accordion.
This commit is contained in:
73 files changed
+163
-245
No files matched your search
+3
-12
@@ -28,17 +28,8 @@ type AccordionOptions struct {
|
||||
KeepOne bool
|
||||
}
|
||||
|
||||
// AccordionView is a built Accordion and what happened to it in this frame.
|
||||
type AccordionView struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports that a section opened or closed.
|
||||
func (v AccordionView) Changed() bool { return v.changed }
|
||||
|
||||
// Accordion shows sections whose open IDs live in *open, which the caller controls.
|
||||
func Accordion(c *ui.Context, open *[]string, items []AccordionItem, opts AccordionOptions) AccordionView {
|
||||
func Accordion(c *ui.Context, open *[]string, items []AccordionItem, opts AccordionOptions) ChoiceResult {
|
||||
seen := map[string]bool{}
|
||||
for _, it := range items {
|
||||
if it.ID == "" || seen[it.ID] || it.Content == nil {
|
||||
@@ -49,10 +40,10 @@ func Accordion(c *ui.Context, open *[]string, items []AccordionItem, opts Accord
|
||||
k := Tokens(c)
|
||||
d := Density(c)
|
||||
u := d.Unit()
|
||||
var v AccordionView
|
||||
var v ChoiceResult
|
||||
v.Element = ui.Column(c).Background(k.Surface).Border(theme.BorderWidth, k.Border).Radius(theme.CardRadius)
|
||||
focusTo := ui.Local(v.Element, "focus", func() int { return -1 })
|
||||
v.Children(func() {
|
||||
v.Element.Children(func() {
|
||||
for i, it := range items {
|
||||
ui.Column(c).Key(it.ID).Children(func() {
|
||||
if i > 0 {
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ type AppShellOptions struct {
|
||||
|
||||
// AppShellResult is an AppShell and its event in this frame.
|
||||
type AppShellResult struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
// Toggled reports the sidebar opened or collapsed.
|
||||
Toggled bool
|
||||
}
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ type BreadcrumbOptions struct {
|
||||
|
||||
// BreadcrumbResult is the built Breadcrumb.
|
||||
type BreadcrumbResult struct {
|
||||
*ui.Element
|
||||
chosen int
|
||||
Element *ui.Element
|
||||
chosen int
|
||||
}
|
||||
|
||||
// Chosen returns the index of the item the user activated in this frame.
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ type ButtonGroupOptions struct {
|
||||
|
||||
// ButtonGroupResult is the group element and the item clicked this frame.
|
||||
type ButtonGroupResult struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
clicked int
|
||||
}
|
||||
|
||||
|
||||
+8
-17
@@ -24,15 +24,6 @@ type CarouselOptions struct {
|
||||
Loop bool
|
||||
}
|
||||
|
||||
// CarouselView is a built Carousel and what happened to it in this frame.
|
||||
type CarouselView struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports a new slide.
|
||||
func (v CarouselView) Changed() bool { return v.changed }
|
||||
|
||||
type carouselState struct {
|
||||
paused bool
|
||||
next time.Time
|
||||
@@ -41,7 +32,7 @@ type carouselState struct {
|
||||
}
|
||||
|
||||
// Carousel shows slide *index of n with previous, next and position controls.
|
||||
func Carousel(c *ui.Context, index *int, n int, opts CarouselOptions, slide func(i int)) CarouselView {
|
||||
func Carousel(c *ui.Context, index *int, n int, opts CarouselOptions, slide func(i int)) ChoiceResult {
|
||||
if n < 0 || opts.Interval < 0 {
|
||||
panic("mujica: Carousel with negative count or Interval")
|
||||
}
|
||||
@@ -51,7 +42,7 @@ func Carousel(c *ui.Context, index *int, n int, opts CarouselOptions, slide func
|
||||
}
|
||||
k := Tokens(c)
|
||||
u := Density(c).Unit()
|
||||
var v CarouselView
|
||||
var v ChoiceResult
|
||||
v.Element = ui.Column(c).Gap(u * 2).Label(opts.Label)
|
||||
st := ui.Local(v.Element, "state", func() carouselState { return carouselState{} })
|
||||
if n > 0 && (*index < 0 || *index >= n) {
|
||||
@@ -75,7 +66,7 @@ func Carousel(c *ui.Context, index *int, n int, opts CarouselOptions, slide func
|
||||
slog.Debug("mujica: carousel slide", "index", i)
|
||||
}
|
||||
now := c.Now()
|
||||
playing := opts.Autoplay && n > 1 && !st.paused && !v.Hovered() && !v.FocusWithin()
|
||||
playing := opts.Autoplay && n > 1 && !st.paused && !v.Element.Hovered() && !v.Element.FocusWithin()
|
||||
if !playing {
|
||||
st.next = time.Time{}
|
||||
} else if st.next.IsZero() {
|
||||
@@ -89,16 +80,16 @@ func Carousel(c *ui.Context, index *int, n int, opts CarouselOptions, slide func
|
||||
}
|
||||
switch {
|
||||
case st.dotFocus:
|
||||
case v.Shortcut(0, ui.KeyLeft):
|
||||
case v.Element.Shortcut(0, ui.KeyLeft):
|
||||
goTo(*index - 1)
|
||||
case v.Shortcut(0, ui.KeyRight):
|
||||
case v.Element.Shortcut(0, ui.KeyRight):
|
||||
goTo(*index + 1)
|
||||
case v.Shortcut(0, ui.KeyHome):
|
||||
case v.Element.Shortcut(0, ui.KeyHome):
|
||||
goTo(0)
|
||||
case v.Shortcut(0, ui.KeyEnd):
|
||||
case v.Element.Shortcut(0, ui.KeyEnd):
|
||||
goTo(n - 1)
|
||||
}
|
||||
v.Children(func() {
|
||||
v.Element.Children(func() {
|
||||
ui.Row(c).Grow(1).MinHeight(120).AlignItems(ui.Center).Gap(u * 2).Children(func() {
|
||||
prev := Button(c, "", ButtonOptions{Variant: Ghost, Icon: icons.Must("chevron-left"), Label: msg(c, "carousel.prev"), Disabled: n < 2 || !wrap && *index == 0})
|
||||
ui.Column(c).Grow(1).MinWidth(0).FillHeight().Clip().Background(k.Surface).Border(theme.BorderWidth, k.Border).Radius(theme.CardRadius).
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ func (h *carouselHarness) view(c *ui.Context) {
|
||||
v := Carousel(c, &h.index, 4, h.opts, func(i int) {
|
||||
ui.Text(c, fmt.Sprintf("Window %d", i+1)).Padding(20)
|
||||
})
|
||||
v.Grow(1)
|
||||
v.Element.Grow(1)
|
||||
if v.Changed() {
|
||||
h.changes++
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func TestCarouselSnapshot(t *testing.T) {
|
||||
ui.Column(c).Grow(1).Center().Children(func() {
|
||||
ui.Text(c, fmt.Sprintf("Rose window %d", n+1)).FontSize(FontSize(c, 18))
|
||||
})
|
||||
}).Grow(1)
|
||||
}).Element.Grow(1)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func TestCarouselEmptyAutoplayKeys(t *testing.T) {
|
||||
index := 0
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
Use(c, Settings{})
|
||||
Carousel(c, &index, 0, CarouselOptions{Autoplay: true}, func(int) {}).Size(300, 200)
|
||||
Carousel(c, &index, 0, CarouselOptions{Autoplay: true}, func(int) {}).Element.Size(300, 200)
|
||||
}, 320, 240)
|
||||
for range 4 {
|
||||
tt.Key(0, ui.KeyTab)
|
||||
|
||||
+3
-12
@@ -19,25 +19,16 @@ type CollapsibleOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// CollapsibleView is a built Collapsible and what happened to it in this frame.
|
||||
type CollapsibleView struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports that the trigger opened or closed the content.
|
||||
func (v CollapsibleView) Changed() bool { return v.changed }
|
||||
|
||||
// Collapsible shows content below a trigger while *open; closed content is not built.
|
||||
func Collapsible(c *ui.Context, open *bool, opts CollapsibleOptions, content func()) CollapsibleView {
|
||||
func Collapsible(c *ui.Context, open *bool, opts CollapsibleOptions, content func()) ChoiceResult {
|
||||
if opts.Title == "" && opts.Trigger == nil {
|
||||
panic("mujica: Collapsible needs Title or Trigger")
|
||||
}
|
||||
k := Tokens(c)
|
||||
u := Density(c).Unit()
|
||||
var v CollapsibleView
|
||||
var v ChoiceResult
|
||||
v.Element = ui.Column(c).Shrink(0)
|
||||
v.Children(func() {
|
||||
v.Element.Children(func() {
|
||||
was := *open
|
||||
p := ui.CollapsibleBase(c, open)
|
||||
tr := p.Trigger
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ type CommandPaletteOptions struct {
|
||||
|
||||
// CommandPaletteResult is the built CommandPalette; Element is nil while closed.
|
||||
type CommandPaletteResult struct {
|
||||
*ui.Element
|
||||
chosen string
|
||||
Element *ui.Element
|
||||
chosen string
|
||||
}
|
||||
|
||||
// Chosen returns the ID of the command run in this frame; running closes the palette.
|
||||
|
||||
+2
-2
@@ -85,7 +85,7 @@ func (s *DataTableState[K]) Rows() int { return len(s.cache.view) }
|
||||
|
||||
// DataTableView is a built DataTable and what happened to it in this frame.
|
||||
type DataTableView struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
changed, submitted, sorted, retry bool
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func DataTable[R any, K comparable](c *ui.Context, s *DataTableState[K], rows []
|
||||
before := s.Sort
|
||||
var v DataTableView
|
||||
v.Element = ui.Column(c).MinHeight(0).Gap(d.Unit() * 2)
|
||||
v.Children(func() {
|
||||
v.Element.Children(func() {
|
||||
if len(rows) > 0 {
|
||||
ui.Row(c).Justify(ui.End).Children(func() {
|
||||
tableColumnMenu(c, dataTableMenu(opts), &s.Sort, &s.Layout, &s.Hidden)
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ func (h *tableHarness) view(c *ui.Context) {
|
||||
cols.Columns[i].Text = func(r relic) string { h.seen[r.Name]++; return text(r) }
|
||||
}
|
||||
v := DataTable(c, &h.st, h.rows, cols)
|
||||
v.Size(360, 300)
|
||||
v.Element.Size(360, 300)
|
||||
if v.Changed() {
|
||||
h.changes++
|
||||
}
|
||||
@@ -347,7 +347,7 @@ func TestDataTableSnapshot(t *testing.T) {
|
||||
return h
|
||||
})
|
||||
ui.Column(c).Padding(16).Children(func() {
|
||||
DataTable(c, &(*h).st, (*h).rows, (*h).opts).Size(600, 260)
|
||||
DataTable(c, &(*h).st, (*h).rows, (*h).opts).Element.Size(600, 260)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ func (d Date) within(lo, hi Date) bool {
|
||||
|
||||
// DateTimeResult is a date or time component and what happened to it this frame.
|
||||
type DateTimeResult struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
// Input is the text field inside, for labels and focus; nil without one.
|
||||
Input *ui.Element
|
||||
changed bool
|
||||
|
||||
+5
-5
@@ -31,8 +31,8 @@ type DescriptionListOptions struct {
|
||||
|
||||
// DescriptionListView is a built DescriptionList and what happened to it in this frame.
|
||||
type DescriptionListView struct {
|
||||
*ui.Element
|
||||
copied int
|
||||
Element *ui.Element
|
||||
copied int
|
||||
}
|
||||
|
||||
// Copied returns the index of the item whose value was copied in this frame.
|
||||
@@ -69,15 +69,15 @@ func DescriptionList(c *ui.Context, items []DescriptionItem, opts DescriptionLis
|
||||
}
|
||||
return max(1, min(most, int(math.Floor(float64((w+gap)/(minW+gap))))))
|
||||
}
|
||||
cols := fit(v.Bounds().W)
|
||||
v.Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
cols := fit(v.Element.Bounds().W)
|
||||
v.Element.Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
if fit(r.W) != cols {
|
||||
// The new width needs other columns: build again.
|
||||
c.Invalidate()
|
||||
}
|
||||
})
|
||||
now := c.Now()
|
||||
v.Columns(cols).Children(func() {
|
||||
v.Element.Columns(cols).Children(func() {
|
||||
for i, it := range items {
|
||||
cell := ui.Column(c).Gap(u).MinWidth(0)
|
||||
if it.Span < 0 || it.Span > cols {
|
||||
|
||||
@@ -29,7 +29,7 @@ mujica.AppShell(c, &collapsed, mujica.AppShellOptions{
|
||||
| `SidebarWidth` | `float32` | 240 | 展开时侧栏宽度(DIP);负数 panic。 |
|
||||
| `ContentScroll` | `*ui.ScrollState` | nil | 保存主内容滚动位置;nil 时按位置保留。 |
|
||||
|
||||
返回 `AppShellResult`,内嵌 `*ui.Element`。
|
||||
返回 `AppShellResult`,`Element` 字段是组件元素。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ split := mujica.SplitPaneState{Size: 160}
|
||||
mujica.SplitPane(c, &split, mujica.SplitPaneOptions{MinFirst: 120, Collapsible: mujica.SplitPaneFirst},
|
||||
func() { ui.Text(c, "Files") },
|
||||
func() { ui.Text(c, "Editor") },
|
||||
).Fill()
|
||||
).Element.Fill()
|
||||
```
|
||||
|
||||
签名:`func SplitPane(c *ui.Context, state *SplitPaneState, opts SplitPaneOptions, first, second func()) SplitPaneResult`
|
||||
@@ -33,7 +33,7 @@ mujica.SplitPane(c, &split, mujica.SplitPaneOptions{MinFirst: 120, Collapsible:
|
||||
| `MinFirst`, `MinSecond` | `float32` | 10 单位(默认 40 DIP) | 两栏最小尺寸;负数 panic。 |
|
||||
| `Collapsible` | `SplitPaneSide` | `SplitPaneNeither` | 可折叠的栏:`SplitPaneFirst` 或 `SplitPaneSecond`。 |
|
||||
|
||||
返回 `SplitPaneResult`,内嵌 `*ui.Element`。
|
||||
返回 `SplitPaneResult`,`Element` 字段是组件元素。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ if r.Submitted() {
|
||||
|
||||
签名:`func TextInput(c *ui.Context, value *string, opts TextInputOptions) InputResult`
|
||||
|
||||
`InputResult` 内嵌外层 `*ui.Element`,`Input` 字段是内部编辑元素,`Changed()`、`Submitted()` 报告事件。
|
||||
`InputResult` 的 `Element` 字段是外层元素,`Input` 字段是内部编辑元素,`Changed()`、`Submitted()` 报告事件。
|
||||
|
||||
## 参数
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 事件
|
||||
|
||||
`RatingParts.Changed()` 用户设置了新值;同值点击不触发。
|
||||
`ChoiceResult.Changed()` 用户设置了新值;同值点击不触发。
|
||||
|
||||
## 键盘操作
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 事件
|
||||
|
||||
`FileDropZoneParts.Changed()` 添加或移除了文件。被拒绝的条目只显示在组件内,可单独关闭。
|
||||
`ChoiceResult.Changed()` 添加或移除了文件。被拒绝的条目只显示在组件内,可单独关闭。
|
||||
|
||||
## 键盘操作
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 事件
|
||||
|
||||
`TransferParts.Changed()` 有条目移动。
|
||||
`ChoiceResult.Changed()` 有条目移动。
|
||||
|
||||
## 键盘操作
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func Calendar(c *ui.Context, value *Date, opts CalendarOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func Calendar(c *ui.Context, value *Date, opts CalendarOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -21,7 +21,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func DatePicker(c *ui.Context, value *Date, opts DatePickerOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func DatePicker(c *ui.Context, value *Date, opts DatePickerOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -20,7 +20,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func DateRangePicker(c *ui.Context, value *DateRange, opts DateRangePickerOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func DateRangePicker(c *ui.Context, value *DateRange, opts DateRangePickerOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -20,7 +20,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func TimePicker(c *ui.Context, value *TimeOfDay, opts TimePickerOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func TimePicker(c *ui.Context, value *TimeOfDay, opts TimePickerOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -20,7 +20,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func TimeRangePicker(c *ui.Context, value *TimeRange, opts TimeRangePickerOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func TimeRangePicker(c *ui.Context, value *TimeRange, opts TimeRangePickerOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -24,7 +24,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func DateTimePicker(c *ui.Context, value *time.Time, opts DateTimePickerOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func DateTimePicker(c *ui.Context, value *time.Time, opts DateTimePickerOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -20,7 +20,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func MonthPicker(c *ui.Context, value *YearMonth, opts MonthPickerOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func MonthPicker(c *ui.Context, value *YearMonth, opts MonthPickerOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -20,7 +20,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func YearPicker(c *ui.Context, value *int, opts YearPickerOptions) DateTimeResult`,返回值嵌入 `*ui.Element`。
|
||||
`func YearPicker(c *ui.Context, value *int, opts YearPickerOptions) DateTimeResult`,返回值的 `Element` 字段是组件元素。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -14,7 +14,7 @@ mujica.Tabs(c, &tab, []mujica.Tab{
|
||||
}, mujica.TabsOptions{Label: "Settings"})
|
||||
```
|
||||
|
||||
签名:`func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) TabsResult`
|
||||
签名:`func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) ChoiceResult`
|
||||
|
||||
## 参数
|
||||
|
||||
@@ -36,7 +36,7 @@ mujica.Tabs(c, &tab, []mujica.Tab{
|
||||
| `Vertical` | `bool` | false | 标签竖排在左侧,面板在右侧。 |
|
||||
| `Label` | `string` | "" | 标签列表的无障碍名称。 |
|
||||
|
||||
返回 `TabsResult`,内嵌包含标签与面板的 `*ui.Element`。
|
||||
返回 `ChoiceResult`,`Element` 字段是包含标签与面板的元素。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ if i, ok := mujica.Breadcrumb(c, path, mujica.BreadcrumbOptions{}).Chosen(); ok
|
||||
| `MaxItemWidth` | `float32` | 0(即 160 DIP) | 单项文字最大宽度,超出以省略号截断。 |
|
||||
| `Label` | `string` | 内置“路径导航” | 整组的无障碍名称。 |
|
||||
|
||||
返回 `BreadcrumbResult`,内嵌 `*ui.Element`。
|
||||
返回 `BreadcrumbResult`,`Element` 字段是组件元素。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ if mujica.Pagination(c, &page, &size, mujica.PaginationOptions{Total: 420, PageS
|
||||
}
|
||||
```
|
||||
|
||||
签名:`func Pagination(c *ui.Context, page, pageSize *int, opts PaginationOptions) PaginationResult`
|
||||
签名:`func Pagination(c *ui.Context, page, pageSize *int, opts PaginationOptions) ChoiceResult`
|
||||
|
||||
## 参数
|
||||
|
||||
@@ -28,7 +28,7 @@ if mujica.Pagination(c, &page, &size, mujica.PaginationOptions{Total: 420, PageS
|
||||
| `Siblings` | `int` | 0(即 1) | 当前页两侧显示的页码数;为负 panic。 |
|
||||
| `Disabled` | `bool` | false | 整体禁用。 |
|
||||
|
||||
返回 `PaginationResult`,内嵌 `*ui.Element`。
|
||||
返回 `ChoiceResult`,`Element` 字段是组件元素。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ mujica.Steps(c, ¤t, []mujica.Step{
|
||||
}, mujica.StepsOptions{Label: "Sign-up"})
|
||||
```
|
||||
|
||||
签名:`func Steps(c *ui.Context, current *int, steps []Step, opts StepsOptions) StepsResult`
|
||||
签名:`func Steps(c *ui.Context, current *int, steps []Step, opts StepsOptions) ChoiceResult`
|
||||
|
||||
## 参数
|
||||
|
||||
@@ -36,7 +36,7 @@ mujica.Steps(c, ¤t, []mujica.Step{
|
||||
| `Vertical` | `bool` | false | 纵向排列,连接线在标记下方。 |
|
||||
| `Label` | `string` | "" | 整组的无障碍名称。 |
|
||||
|
||||
返回 `StepsResult`,内嵌 `*ui.Element`。
|
||||
返回 `ChoiceResult`,`Element` 字段是组件元素。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ mujica.Sidebar(c, &selected, []mujica.SidebarSection{
|
||||
}, mujica.SidebarOptions{})
|
||||
```
|
||||
|
||||
签名:`func Sidebar(c *ui.Context, selected *string, sections []SidebarSection, opts SidebarOptions) SidebarResult`
|
||||
签名:`func Sidebar(c *ui.Context, selected *string, sections []SidebarSection, opts SidebarOptions) ChoiceResult`
|
||||
|
||||
## 参数
|
||||
|
||||
@@ -45,7 +45,7 @@ mujica.Sidebar(c, &selected, []mujica.SidebarSection{
|
||||
| `Compact` | `bool` | false | 图标栏:隐藏文字与组标题,组之间以旧金细线分隔,名称作为悬停提示。 |
|
||||
| `Width` | `float32` | 0(240,紧凑时 56) | 宽度,DIP。高度填满父容器。 |
|
||||
|
||||
返回 `SidebarResult`,内嵌 `*ui.Element`。展开折叠状态由组件保存。
|
||||
返回 `ChoiceResult`,`Element` 字段是组件元素。展开折叠状态由组件保存。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ if id, ok := res.Chosen(); ok && id == "wrap" {
|
||||
| --- | --- | --- | --- |
|
||||
| `Label` | `string` | 内置“菜单栏” | 菜单栏的无障碍名称。 |
|
||||
|
||||
返回 `MenubarResult`,内嵌菜单栏 `*ui.Element`。
|
||||
返回 `MenubarResult`,`Element` 字段是菜单栏元素。
|
||||
|
||||
## 状态
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ view := func(c *ui.Context) {
|
||||
Key: func(i int) string { return names[i] },
|
||||
Label: func(i int) string { return names[i] },
|
||||
}, func(i int) { ui.Text(c, names[i]) })
|
||||
v.Size(240, 200)
|
||||
v.Element.Size(240, 200)
|
||||
if v.Changed() {
|
||||
key, _ := state.Selected()
|
||||
fmt.Println("chose", key)
|
||||
@@ -26,7 +26,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func List[K comparable](c *ui.Context, s *ListState[K], n int, opts ListOptions[K], item func(i int)) ListView`。`ListView` 嵌入 `*ui.Element`;`ListState` 由调用方持有,零值表示未选择。`state.Selected()` 返回所选键,多选时 `state.Selection` 保存全部键;`state.ScrollTo(i)` 滚动到第 i 项。
|
||||
`func List[K comparable](c *ui.Context, s *ListState[K], n int, opts ListOptions[K], item func(i int)) ListView`。`ListView` 的 `Element` 字段是列表元素;`ListState` 由调用方持有,零值表示未选择。`state.Selected()` 返回所选键,多选时 `state.Selection` 保存全部键;`state.ScrollTo(i)` 滚动到第 i 项。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -27,7 +27,7 @@ opts := mujica.DataTableOptions[relic, int]{
|
||||
}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.DataTable(c, &state, rows, opts).Size(360, 240)
|
||||
mujica.DataTable(c, &state, rows, opts).Element.Size(360, 240)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ view := func(c *ui.Context) {
|
||||
Roots: []string{"Nave", "Sacristy"},
|
||||
Children: func(n string) []string { return kids[n] },
|
||||
Label: func(n string) string { return n },
|
||||
}).Size(280, 240)
|
||||
}).Element.Size(280, 240)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
```
|
||||
|
||||
@@ -22,7 +22,7 @@ view := func(c *ui.Context) {
|
||||
{ID: "size", Title: "Size", Width: 80, Align: ui.End,
|
||||
Cell: func(n string) { ui.Text(c, strconv.Itoa(size[n])) }},
|
||||
},
|
||||
}).Size(360, 240)
|
||||
}).Element.Size(360, 240)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func Accordion(c *ui.Context, open *[]string, items []AccordionItem, opts AccordionOptions) AccordionView`。`*open` 是展开区段的 ID 列表;`AccordionItem` 字段:`ID`(必填、唯一)、`Title`、`Content`(必填)、`Disabled`。
|
||||
`func Accordion(c *ui.Context, open *[]string, items []AccordionItem, opts AccordionOptions) ChoiceResult`。`*open` 是展开区段的 ID 列表;`AccordionItem` 字段:`ID`(必填、唯一)、`Title`、`Content`(必填)、`Disabled`。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -33,7 +33,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 事件
|
||||
|
||||
`AccordionView.Changed()`:某区段因用户操作而展开或关闭,`*open` 已更新。外部直接修改 `*open` 不触发事件。
|
||||
`ChoiceResult.Changed()`:某区段因用户操作而展开或关闭,`*open` 已更新。外部直接修改 `*open` 不触发事件。
|
||||
|
||||
## 键盘操作
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func Collapsible(c *ui.Context, open *bool, opts CollapsibleOptions, content func()) CollapsibleView`。
|
||||
`func Collapsible(c *ui.Context, open *bool, opts CollapsibleOptions, content func()) ChoiceResult`。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -33,7 +33,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 事件
|
||||
|
||||
`CollapsibleView.Changed()`:用户点击或按键切换了展开状态。
|
||||
`ChoiceResult.Changed()`:用户点击或按键切换了展开状态。
|
||||
|
||||
## 键盘操作
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 参数
|
||||
|
||||
`func ImageViewer(c *ui.Context, open *bool, index *int, images []ViewerImage) ImageViewerView`。`ViewerImage` 字段:`Source`(`*ui.Bitmap` 或 `*ui.SVG`,必填)、`Alt`(说明文字与无障碍名称)。关闭时返回的 `Element` 为 nil。
|
||||
`func ImageViewer(c *ui.Context, open *bool, index *int, images []ViewerImage) ChoiceResult`。`ViewerImage` 字段:`Source`(`*ui.Bitmap` 或 `*ui.SVG`,必填)、`Alt`(说明文字与无障碍名称)。关闭时返回的 `Element` 为 nil。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -32,7 +32,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 事件
|
||||
|
||||
`ImageViewerView.Changed()`:切换了图片,`*index` 已更新。关闭通过 `*open` 变为 false 观察。
|
||||
`ChoiceResult.Changed()`:切换了图片,`*index` 已更新。关闭通过 `*open` 变为 false 观察。
|
||||
|
||||
## 键盘操作
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.Carousel(c, &index, 3, mujica.CarouselOptions{Label: "Windows", Autoplay: true}, func(i int) {
|
||||
ui.Text(c, fmt.Sprintf("Rose window %d", i+1)).Padding(24)
|
||||
}).Size(360, 220)
|
||||
}).Element.Size(360, 220)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
`func Carousel(c *ui.Context, index *int, n int, opts CarouselOptions, slide func(i int)) CarouselView`。
|
||||
`func Carousel(c *ui.Context, index *int, n int, opts CarouselOptions, slide func(i int)) ChoiceResult`。
|
||||
|
||||
| 字段 | 类型 | 默认值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -34,7 +34,7 @@ ui.Render(view, 400, 300, 1)
|
||||
|
||||
## 事件
|
||||
|
||||
`CarouselView.Changed()`:当前项变化,`*index` 已更新;同时向读屏软件播报“第 N 项,共 M 项”。
|
||||
`ChoiceResult.Changed()`:当前项变化,`*index` 已更新;同时向读屏软件播报“第 N 项,共 M 项”。
|
||||
|
||||
## 键盘操作
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func ExampleList() {
|
||||
Key: func(i int) string { return names[i] },
|
||||
Label: func(i int) string { return names[i] },
|
||||
}, func(i int) { ui.Text(c, names[i]) })
|
||||
v.Size(240, 200)
|
||||
v.Element.Size(240, 200)
|
||||
if v.Changed() {
|
||||
key, _ := state.Selected()
|
||||
fmt.Println("chose", key)
|
||||
@@ -61,7 +61,7 @@ func ExampleDataTable() {
|
||||
}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.DataTable(c, &state, rows, opts).Size(360, 240)
|
||||
mujica.DataTable(c, &state, rows, opts).Element.Size(360, 240)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func ExampleTree() {
|
||||
Roots: []string{"Nave", "Sacristy"},
|
||||
Children: func(n string) []string { return kids[n] },
|
||||
Label: func(n string) string { return n },
|
||||
}).Size(280, 240)
|
||||
}).Element.Size(280, 240)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func ExampleTreeTable() {
|
||||
{ID: "size", Title: "Size", Width: 80, Align: ui.End,
|
||||
Cell: func(n string) { ui.Text(c, strconv.Itoa(size[n])) }},
|
||||
},
|
||||
}).Size(360, 240)
|
||||
}).Element.Size(360, 240)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
}
|
||||
@@ -176,7 +176,7 @@ func ExampleCarousel() {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.Carousel(c, &index, 3, mujica.CarouselOptions{Label: "Windows", Autoplay: true}, func(i int) {
|
||||
ui.Text(c, fmt.Sprintf("Rose window %d", i+1)).Padding(24)
|
||||
}).Size(360, 220)
|
||||
}).Element.Size(360, 220)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func ExampleSplitPane() {
|
||||
mujica.SplitPane(c, &split, mujica.SplitPaneOptions{MinFirst: 120, Collapsible: mujica.SplitPaneFirst},
|
||||
func() { ui.Text(c, "Files") },
|
||||
func() { ui.Text(c, "Editor") },
|
||||
).Fill()
|
||||
).Element.Fill()
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ func (g *gallery) filesView(c *ui.Context, log func(string)) {
|
||||
v := mujica.DataTable(c, &s.table, s.entries, mujica.DataTableOptions[fileEntry, int]{
|
||||
Columns: cols, Key: func(f fileEntry) int { return f.ID }, Multiple: true, Version: s.version,
|
||||
})
|
||||
v.FillWidth().Height(430)
|
||||
v.Element.FillWidth().Height(430)
|
||||
mujica.ContextMenu(c, v.Element, mujica.ContextMenuOptions{}, func(m *mujica.PopupMenu) { g.fileMenu(m, log, targets) })
|
||||
if v.Changed() {
|
||||
log(fmt.Sprintf("selection %d checked", s.table.Selection.Len()))
|
||||
@@ -174,7 +174,7 @@ func (g *gallery) filesView(c *ui.Context, log func(string)) {
|
||||
{ID: "shown", Text: fmt.Sprintf(g.t("%d shown", "显示 %d 个"), shown), Secondary: true},
|
||||
},
|
||||
Right: []mujica.StatusItem{{ID: "selected", Text: fmt.Sprintf(g.t("%d selected", "已选 %d 个"), s.table.Selection.Len())}},
|
||||
}).Radius(theme.CardRadius).Border(theme.BorderWidth, k.Border)
|
||||
}).Element.Radius(theme.CardRadius).Border(theme.BorderWidth, k.Border)
|
||||
g.fileAdders(c, log)
|
||||
g.renameDialog(c, log)
|
||||
g.deleteDialog(c, log)
|
||||
|
||||
@@ -330,7 +330,8 @@ func multiSelectDemo(c *ui.Context, log func(string)) {
|
||||
}
|
||||
choiceLabeled(c, "Fruits", func() {
|
||||
r := mujica.MultiSelect(c, &s.values, choiceFruits, mujica.MultiSelectOptions{Max: limit, MaxTags: 2, Disabled: s.disable, Label: "Fruits"})
|
||||
if r.Width(320).Changed() {
|
||||
r.Element.Width(320)
|
||||
if r.Changed() {
|
||||
log(fmt.Sprintf("Fruits: %v", s.values))
|
||||
}
|
||||
})
|
||||
@@ -435,7 +436,9 @@ func cascaderDemo(c *ui.Context, log func(string)) {
|
||||
}, func() {
|
||||
choiceLabeled(c, "City (children load on demand)", func() {
|
||||
opts := mujica.CascaderOptions{Level: level, Load: load, AnyLevel: s.anyLevel, Disabled: s.ds, Label: "City"}
|
||||
if mujica.Cascader(c, &s.path, opts).Width(320).Changed() {
|
||||
r := mujica.Cascader(c, &s.path, opts)
|
||||
r.Element.Width(320)
|
||||
if r.Changed() {
|
||||
log(fmt.Sprintf("path: %v", s.path))
|
||||
}
|
||||
})
|
||||
@@ -471,7 +474,9 @@ func treeSelectDemo(c *ui.Context, log func(string)) {
|
||||
}
|
||||
choiceLabeled(c, "Roles", func() {
|
||||
opts := mujica.TreeSelectOptions{Multiple: s.multiple, Rule: rule, MaxTags: 2, Disabled: s.disabled, Label: "Roles"}
|
||||
if mujica.TreeSelect(c, &s.sel, choiceRoles, opts).Width(320).Changed() {
|
||||
r := mujica.TreeSelect(c, &s.sel, choiceRoles, opts)
|
||||
r.Element.Width(320)
|
||||
if r.Changed() {
|
||||
log(fmt.Sprintf("Roles: %v", s.sel))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -58,7 +58,7 @@ func dataListDemo(c *ui.Context, log func(string)) {
|
||||
ui.Text(c, "Office").TextColor(mujica.Tokens(c).TextMuted)
|
||||
}
|
||||
})
|
||||
v.Size(480, 320)
|
||||
v.Element.Size(480, 320)
|
||||
if v.Changed() {
|
||||
k, _ := s.list.Selected()
|
||||
log(fmt.Sprintf("selected %d, %d chosen", k, s.list.Selection.Len()))
|
||||
@@ -138,7 +138,7 @@ func dataTableDemo(c *ui.Context, log func(string)) {
|
||||
v := mujica.DataTable(c, &s.table, rows, mujica.DataTableOptions[dataRelic, int]{
|
||||
Columns: cols, Key: func(r dataRelic) int { return r.ID }, Multiple: s.multiple, Status: st,
|
||||
})
|
||||
v.Size(620, 360)
|
||||
v.Element.Size(620, 360)
|
||||
ui.Text(c, fmt.Sprintf("%d of %d rows shown", s.table.Rows(), len(rows))).TextColor(k.TextMuted)
|
||||
if v.SortChanged() {
|
||||
log(fmt.Sprintf("sort %s descending=%v", s.table.Sort.Column, s.table.Sort.Descending))
|
||||
@@ -224,7 +224,7 @@ func dataTreeDemo(c *ui.Context, log func(string)) {
|
||||
ui.Checkbox(c, &s.multiple, "Multiple")
|
||||
})
|
||||
v := mujica.Tree(c, ts, opts)
|
||||
v.Size(420, 340)
|
||||
v.Element.Size(420, 340)
|
||||
if v.Changed() {
|
||||
n, _ := ts.Selected()
|
||||
log(fmt.Sprintf("selected %s, %d chosen", n, ts.Selection.Len()))
|
||||
@@ -290,7 +290,7 @@ func dataTreeTableDemo(c *ui.Context, log func(string)) {
|
||||
{ID: "kids", Title: "Rooms", Width: 80, Align: ui.End, Cell: func(n string) { ui.Text(c, strconv.Itoa(len(s.lazy.kids[n]))) }},
|
||||
},
|
||||
})
|
||||
v.Size(520, 320)
|
||||
v.Element.Size(520, 320)
|
||||
if v.SortChanged() {
|
||||
log(fmt.Sprintf("sort %s descending=%v", s.table.Sort.Column, s.table.Sort.Descending))
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func dataAccordionDemo(c *ui.Context, log func(string)) {
|
||||
log("closed all from outside")
|
||||
}
|
||||
})
|
||||
if mujica.Accordion(c, &s.open, []mujica.AccordionItem{
|
||||
r := mujica.Accordion(c, &s.open, []mujica.AccordionItem{
|
||||
{ID: "choir", Title: "Choir", Content: func() { ui.Text(c, "Twelve voices, two at the altar.") }},
|
||||
{ID: "nave", Title: "Nave", Content: func() { ui.Text(c, "Pews of black oak, polished for the feast.") }},
|
||||
{ID: "crypt", Title: "Crypt (disabled)", Disabled: true, Content: func() {}},
|
||||
@@ -67,7 +67,9 @@ func dataAccordionDemo(c *ui.Context, log func(string)) {
|
||||
log("ring the bells")
|
||||
}
|
||||
}},
|
||||
}, mujica.AccordionOptions{Multiple: s.multiple, KeepOne: s.keepOne}).Width(420).Changed() {
|
||||
}, mujica.AccordionOptions{Multiple: s.multiple, KeepOne: s.keepOne})
|
||||
r.Element.Width(420)
|
||||
if r.Changed() {
|
||||
log(fmt.Sprintf("open %v", s.open))
|
||||
}
|
||||
})
|
||||
@@ -235,7 +237,7 @@ func dataCarouselDemo(c *ui.Context, log func(string)) {
|
||||
v := mujica.Carousel(c, &s.index, len(dataPictures), mujica.CarouselOptions{Label: "Windows", Autoplay: s.autoplay, Loop: s.loop, Interval: d}, func(i int) {
|
||||
ui.Image(c, dataPictures[i].Source).Grow(1).Label(dataPictures[i].Alt)
|
||||
})
|
||||
v.Size(520, 300)
|
||||
v.Element.Size(520, 300)
|
||||
if v.Changed() {
|
||||
log(fmt.Sprintf("slide %d", s.index+1))
|
||||
}
|
||||
|
||||
@@ -47,3 +47,13 @@ func TestDataDemoButtonsLog(t *testing.T) {
|
||||
t.Fatalf("Light candles not logged: %q", *events)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataAccordionDemoLogsChange(t *testing.T) {
|
||||
tt, events := dataDemo(t, "074")
|
||||
if err := tt.Click("Bells"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !slices.Contains(*events, "open [bells]") {
|
||||
t.Fatalf("Accordion change not logged: %q", *events)
|
||||
}
|
||||
}
|
||||
@@ -180,9 +180,9 @@ if r.Submitted() {
|
||||
inputSection(c, "States", func() {
|
||||
ui.Row(c).Gap(12).Children(func() {
|
||||
e, ro, off := "1789-13-01", "Read only", "Disabled"
|
||||
mujica.TextInput(c, &e, mujica.TextInputOptions{Label: "Error state", Error: "No such date"}).Grow(1)
|
||||
mujica.TextInput(c, &ro, mujica.TextInputOptions{Label: "Read-only state", ReadOnly: true}).Grow(1)
|
||||
mujica.TextInput(c, &off, mujica.TextInputOptions{Label: "Disabled state", Disabled: true}).Grow(1)
|
||||
mujica.TextInput(c, &e, mujica.TextInputOptions{Label: "Error state", Error: "No such date"}).Element.Grow(1)
|
||||
mujica.TextInput(c, &ro, mujica.TextInputOptions{Label: "Read-only state", ReadOnly: true}).Element.Grow(1)
|
||||
mujica.TextInput(c, &off, mujica.TextInputOptions{Label: "Disabled state", Disabled: true}).Element.Grow(1)
|
||||
})
|
||||
})
|
||||
}},
|
||||
@@ -244,8 +244,8 @@ if r.Submitted() {
|
||||
ui.Row(c).Gap(12).AlignItems(ui.Start).Children(func() {
|
||||
a, b, e := 4.0, 2.0, 7.5
|
||||
mujica.NumberInput(c, &a, mujica.NumberInputOptions{Min: 1, Max: 8, StepperOnly: true, Label: "Stepper state"})
|
||||
mujica.NumberInput(c, &b, mujica.NumberInputOptions{Label: "Disabled state", Disabled: true}).Width(160)
|
||||
mujica.NumberInput(c, &e, mujica.NumberInputOptions{Step: 0.5, Label: "Error state", Error: "Must be whole"}).Width(160)
|
||||
mujica.NumberInput(c, &b, mujica.NumberInputOptions{Label: "Disabled state", Disabled: true}).Element.Width(160)
|
||||
mujica.NumberInput(c, &e, mujica.NumberInputOptions{Step: 0.5, Label: "Error state", Error: "Must be whole"}).Element.Width(160)
|
||||
})
|
||||
})
|
||||
}},
|
||||
|
||||
@@ -417,7 +417,7 @@ func layoutSplitPaneDemo(c *ui.Context, log func(string)) {
|
||||
Collapsible: sides[s.collapsible]},
|
||||
pane("Files", "Drag the divider, or focus it and use the arrows.", k.Surface),
|
||||
pane("Editor", "Enter on the divider folds the collapsible pane.", k.Background))
|
||||
r.Fill()
|
||||
r.Element.Fill()
|
||||
if r.Resized {
|
||||
log(fmt.Sprintf("resized to %.0f", s.split.Size))
|
||||
}
|
||||
|
||||
+3
-12
@@ -37,15 +37,6 @@ type FileDropZoneOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// FileDropZoneParts are the parts of a FileDropZone.
|
||||
type FileDropZoneParts struct {
|
||||
Element *ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports whether files were added or removed this frame.
|
||||
func (p FileDropZoneParts) Changed() bool { return p.changed }
|
||||
|
||||
// fileDropZoneReject is a file that failed a check, shown until dismissed.
|
||||
type fileDropZoneReject struct {
|
||||
path, reason string
|
||||
@@ -66,7 +57,7 @@ type fileDropZoneState struct {
|
||||
}
|
||||
|
||||
// FileDropZone checks dropped or browsed files and lists them; it never reads or uploads them.
|
||||
func FileDropZone(c *ui.Context, files *[]DroppedFile, opts FileDropZoneOptions) FileDropZoneParts {
|
||||
func FileDropZone(c *ui.Context, files *[]DroppedFile, opts FileDropZoneOptions) ChoiceResult {
|
||||
if opts.MaxFiles < 0 || opts.MaxSize < 0 {
|
||||
panic("mujica: FileDropZone limits must not be negative")
|
||||
}
|
||||
@@ -77,7 +68,7 @@ func FileDropZone(c *ui.Context, files *[]DroppedFile, opts FileDropZoneOptions)
|
||||
}
|
||||
k := Tokens(c)
|
||||
d := Density(c)
|
||||
parts := FileDropZoneParts{}
|
||||
parts := ChoiceResult{}
|
||||
col := ui.Column(c).Gap(d.Unit() * 2)
|
||||
parts.Element = col
|
||||
st := ui.Local(col, "zone", func() fileDropZoneState { return fileDropZoneState{} })
|
||||
@@ -213,7 +204,7 @@ func fileDropZoneCheck(c *ui.Context, st *fileDropZoneState) {
|
||||
}
|
||||
|
||||
// fileDropZoneList shows accepted, checking and rejected files.
|
||||
func fileDropZoneList(c *ui.Context, st *fileDropZoneState, files *[]DroppedFile, parts *FileDropZoneParts, disabled bool) {
|
||||
func fileDropZoneList(c *ui.Context, st *fileDropZoneState, files *[]DroppedFile, parts *ChoiceResult, disabled bool) {
|
||||
k := Tokens(c)
|
||||
d := Density(c)
|
||||
row := func(key string, icon string, color ui.Color, name, detail string, remove func()) {
|
||||
|
||||
+3
-12
@@ -20,15 +20,6 @@ type ViewerImage struct {
|
||||
Alt string
|
||||
}
|
||||
|
||||
// ImageViewerView is a built ImageViewer; Element is nil while closed.
|
||||
type ImageViewerView struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports that another image was chosen.
|
||||
func (v ImageViewerView) Changed() bool { return v.changed }
|
||||
|
||||
// Zoom limits, as multiples of the fitted size.
|
||||
const (
|
||||
viewerMinZoom = 0.25
|
||||
@@ -42,9 +33,9 @@ type viewerState struct {
|
||||
zoom, x, y float32
|
||||
}
|
||||
|
||||
// ImageViewer shows images[*index] over the window while *open, with zoom, pan and switching.
|
||||
func ImageViewer(c *ui.Context, open *bool, index *int, images []ViewerImage) ImageViewerView {
|
||||
var v ImageViewerView
|
||||
// ImageViewer shows images[*index] over the window while *open, with zoom, pan and switching; Element is nil while closed.
|
||||
func ImageViewer(c *ui.Context, open *bool, index *int, images []ViewerImage) ChoiceResult {
|
||||
var v ChoiceResult
|
||||
if !*open {
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func (s *ListState[K]) ScrollTo(i int) {
|
||||
|
||||
// ListView is a built List and what happened to it in this frame.
|
||||
type ListView struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
changed, submitted, retry bool
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func List[K comparable](c *ui.Context, s *ListState[K], n int, opts ListOptions[
|
||||
k := Tokens(c)
|
||||
var v ListView
|
||||
v.Element = ui.Column(c).MinHeight(0)
|
||||
v.Children(func() {
|
||||
v.Element.Children(func() {
|
||||
if n == 0 {
|
||||
v.retry = dataPlaceholder(c, opts.Status, opts.Error, opts.Empty, msg(c, "list.empty"))
|
||||
return
|
||||
|
||||
+3
-3
@@ -40,7 +40,7 @@ func (h *listHarness) view(c *ui.Context) {
|
||||
h.built++
|
||||
ui.Text(c, h.opts.Label(i)).SingleLine()
|
||||
})
|
||||
v.Size(h.listSize[0], h.listSize[1])
|
||||
v.Element.Size(h.listSize[0], h.listSize[1])
|
||||
if v.Changed() {
|
||||
h.changes++
|
||||
}
|
||||
@@ -207,13 +207,13 @@ func TestListSnapshot(t *testing.T) {
|
||||
g := ui.Local(c.Root(), "g", func() *ListState[int] { return &ListState[int]{started: true, cursor: 4} })
|
||||
opts := ListOptions[int]{Key: func(i int) int { return i }, Label: func(i int) string { return names[i] }, Multiple: true}
|
||||
ui.Row(c).Padding(16).Gap(16).Children(func() {
|
||||
List(c, *st, len(names), opts, func(i int) { ui.Text(c, names[i]) }).Size(220, 228)
|
||||
List(c, *st, len(names), opts, func(i int) { ui.Text(c, names[i]) }).Element.Size(220, 228)
|
||||
gopts := opts
|
||||
gopts.Grid, gopts.Multiple, gopts.ItemWidth, gopts.ItemHeight = true, false, 100, 64
|
||||
List(c, *g, len(names), gopts, func(i int) {
|
||||
ui.Text(c, names[i]).FontWeight(600)
|
||||
ui.Text(c, "Office").TextColor(Tokens(c).TextMuted)
|
||||
}).Size(360, 228)
|
||||
}).Element.Size(360, 228)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -56,8 +56,8 @@ type MenubarOptions struct {
|
||||
|
||||
// MenubarResult is the built Menubar.
|
||||
type MenubarResult struct {
|
||||
*ui.Element
|
||||
chosen string
|
||||
Element *ui.Element
|
||||
chosen string
|
||||
}
|
||||
|
||||
// Chosen returns the ID of the item the user chose in this frame.
|
||||
|
||||
+3
-12
@@ -27,17 +27,8 @@ type PaginationOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// PaginationResult is the built Pagination.
|
||||
type PaginationResult struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports a new page or page size in this frame, from the user or a correction.
|
||||
func (r PaginationResult) Changed() bool { return r.changed }
|
||||
|
||||
// Pagination pages *page (from 1) through Total records, clamping it when Total shrinks.
|
||||
func Pagination(c *ui.Context, page, pageSize *int, opts PaginationOptions) PaginationResult {
|
||||
func Pagination(c *ui.Context, page, pageSize *int, opts PaginationOptions) ChoiceResult {
|
||||
if opts.Total < 0 {
|
||||
panic("mujica: Pagination Total is negative")
|
||||
}
|
||||
@@ -58,7 +49,7 @@ func Pagination(c *ui.Context, page, pageSize *int, opts PaginationOptions) Pagi
|
||||
d := Density(c)
|
||||
u := d.Unit()
|
||||
row := ui.Row(c).AlignItems(ui.Center).Gap(u * 3).Wrap().Role(ui.RoleGroup).Label(msg(c, "pagination.label")).Disabled(opts.Disabled)
|
||||
res := PaginationResult{Element: row}
|
||||
res := ChoiceResult{Element: row}
|
||||
pages := paginationPages(opts.Total, *pageSize)
|
||||
if p := max(1, min(*page, pages)); p != *page {
|
||||
slog.Debug("mujica: pagination correct", "from", *page, "to", p, "pages", pages)
|
||||
@@ -166,7 +157,7 @@ func paginationButton(c *ui.Context, text string, icon *ui.SVG, name string, on,
|
||||
return b
|
||||
}
|
||||
|
||||
func paginationSizes(c *ui.Context, page, pageSize *int, sizes []int, res *PaginationResult) {
|
||||
func paginationSizes(c *ui.Context, page, pageSize *int, sizes []int, res *ChoiceResult) {
|
||||
k := Tokens(c)
|
||||
d := Density(c)
|
||||
u := d.Unit()
|
||||
|
||||
@@ -25,17 +25,8 @@ type RatingOptions struct {
|
||||
ShowValue bool
|
||||
}
|
||||
|
||||
// RatingParts are the parts of a Rating.
|
||||
type RatingParts struct {
|
||||
Element *ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports whether the user set a new value this frame.
|
||||
func (p RatingParts) Changed() bool { return p.changed }
|
||||
|
||||
// Rating shows *value as stars and lets the pointer and arrows set it in steps.
|
||||
func Rating(c *ui.Context, value *float64, opts RatingOptions) RatingParts {
|
||||
func Rating(c *ui.Context, value *float64, opts RatingOptions) ChoiceResult {
|
||||
n, step := opts.Max, opts.Step
|
||||
if n == 0 {
|
||||
n = 5
|
||||
@@ -54,7 +45,7 @@ func Rating(c *ui.Context, value *float64, opts RatingOptions) RatingParts {
|
||||
if label == "" {
|
||||
label = msg(c, "rating.label")
|
||||
}
|
||||
parts := RatingParts{}
|
||||
parts := ChoiceResult{}
|
||||
set := func(v float64) {
|
||||
v = math.Max(0, math.Min(float64(n), math.Round(v/step)*step))
|
||||
if v != *value {
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ func TestRatingSnapshot(t *testing.T) {
|
||||
|
||||
func TestRatingHitAreaCompact(t *testing.T) {
|
||||
v := 2.0
|
||||
var r RatingParts
|
||||
var r ChoiceResult
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
Use(c, Settings{Density: theme.Compact})
|
||||
ui.Column(c).AlignItems(ui.Start).Children(func() {
|
||||
|
||||
+2
-11
@@ -37,17 +37,8 @@ type SidebarOptions struct {
|
||||
Width float32
|
||||
}
|
||||
|
||||
// SidebarResult is the built Sidebar.
|
||||
type SidebarResult struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports a new choice in this frame.
|
||||
func (r SidebarResult) Changed() bool { return r.changed }
|
||||
|
||||
// Sidebar is a source list choosing an item ID into *selected, built on MyGo's Sidebar.
|
||||
func Sidebar(c *ui.Context, selected *string, sections []SidebarSection, opts SidebarOptions) SidebarResult {
|
||||
func Sidebar(c *ui.Context, selected *string, sections []SidebarSection, opts SidebarOptions) ChoiceResult {
|
||||
k := Tokens(c)
|
||||
width := opts.Width
|
||||
if width == 0 {
|
||||
@@ -58,7 +49,7 @@ func Sidebar(c *ui.Context, selected *string, sections []SidebarSection, opts Si
|
||||
}
|
||||
wrap := ui.Column(c).Width(width).FillHeight().AlignItems(ui.Stretch).Shrink(0)
|
||||
closed := ui.Local(wrap, "closed", func() map[string]*bool { return map[string]*bool{} })
|
||||
res := SidebarResult{Element: wrap}
|
||||
res := ChoiceResult{Element: wrap}
|
||||
prev := *selected
|
||||
wrap.Children(func() {
|
||||
// MyGo draws icons in Accent: swap in readable state colors for the sidebar.
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ func TestSidebarCompact(t *testing.T) {
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
Use(c, Settings{})
|
||||
ui.Row(c).FillHeight().Children(func() {
|
||||
w = Sidebar(c, &sel, sidebarSample(), SidebarOptions{Compact: true}).Bounds().W
|
||||
w = Sidebar(c, &sel, sidebarSample(), SidebarOptions{Compact: true}).Element.Bounds().W
|
||||
})
|
||||
}, 400, 500)
|
||||
tt.Frame()
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ func TestSliderSnapshot(t *testing.T) {
|
||||
ui.Column(c).Padding(20).Gap(8).Width(380).Children(func() {
|
||||
Slider(c, &a, SliderOptions{Step: 5})
|
||||
s := Slider(c, &b, SliderOptions{Step: 25, Marks: marks, ShowValue: true, Format: func(v float64) string { return fmt.Sprintf("%.0f%%", v) }})
|
||||
s.Focus()
|
||||
s.Element.Focus()
|
||||
Slider(c, &d, SliderOptions{Disabled: true})
|
||||
})
|
||||
})
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ type SplitPaneOptions struct {
|
||||
|
||||
// SplitPaneResult is a SplitPane and its events in this frame.
|
||||
type SplitPaneResult struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
Resized bool
|
||||
Toggled bool
|
||||
}
|
||||
|
||||
+5
-5
@@ -14,7 +14,7 @@ func splitPaneTester(st *SplitPaneState, opts SplitPaneOptions, p *splitPaneProb
|
||||
return ui.NewTester(func(c *ui.Context) {
|
||||
Use(c, Settings{})
|
||||
r := SplitPane(c, st, opts, func() { ui.Text(c, "Files") }, func() { ui.Text(c, "Editor") })
|
||||
r.Fill()
|
||||
r.Element.Fill()
|
||||
if r.Resized {
|
||||
p.resized++
|
||||
}
|
||||
@@ -136,7 +136,7 @@ func TestSplitPaneCollapseSecondVertical(t *testing.T) {
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
Use(c, Settings{})
|
||||
SplitPane(c, st, SplitPaneOptions{Vertical: true, MinSecond: 60, Collapsible: SplitPaneSecond},
|
||||
func() { ui.Text(c, "Editor") }, func() { ui.Text(c, "Console") }).Fill()
|
||||
func() { ui.Text(c, "Editor") }, func() { ui.Text(c, "Console") }).Element.Fill()
|
||||
}, 300, 201)
|
||||
tt.Key(0, ui.KeyTab)
|
||||
tt.Key(0, ui.KeyDown)
|
||||
@@ -184,7 +184,7 @@ func TestSplitPaneSnapshot(t *testing.T) {
|
||||
}
|
||||
ui.Column(c).Padding(16).Children(func() {
|
||||
SplitPane(c, st, SplitPaneOptions{}, pane("Files", k.Surface), pane("Editor", k.Background)).
|
||||
Height(128).Border(1, k.Border).Radius(4).Clip()
|
||||
Element.Height(128).Border(1, k.Border).Radius(4).Clip()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func TestSplitPaneSizesInsideBorder(t *testing.T) {
|
||||
Use(c, Settings{})
|
||||
SplitPane(c, st, SplitPaneOptions{MinSecond: 120}, func() { ui.Text(c, "Files") }, func() {
|
||||
second = ui.Box(c).Fill().Absolute().Bounds()
|
||||
}).Fill().Border(1, ui.RGB(0, 0, 0))
|
||||
}).Element.Fill().Border(1, ui.RGB(0, 0, 0))
|
||||
}, 401, 200)
|
||||
tt.Key(0, ui.KeyTab)
|
||||
tt.Key(0, ui.KeyEnd)
|
||||
@@ -216,7 +216,7 @@ func TestSplitPaneClampsOnResizeAlone(t *testing.T) {
|
||||
st := &SplitPaneState{Size: 299}
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
Use(c, Settings{})
|
||||
SplitPane(c, st, SplitPaneOptions{}, func() { ui.Text(c, "Files") }, func() { ui.Text(c, "Editor") }).Fill()
|
||||
SplitPane(c, st, SplitPaneOptions{}, func() { ui.Text(c, "Files") }, func() { ui.Text(c, "Editor") }).Element.Fill()
|
||||
}, 400, 200)
|
||||
tt.SetSize(250, 200)
|
||||
// 250 less the divider and the 40 DIP second minimum.
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ type StatusBarOptions struct {
|
||||
|
||||
// StatusBarResult is a StatusBar and its event in this frame.
|
||||
type StatusBarResult struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
// Clicked is the ID of the action item clicked, or empty.
|
||||
Clicked string
|
||||
}
|
||||
|
||||
@@ -29,15 +29,6 @@ type StepsOptions struct {
|
||||
Label string
|
||||
}
|
||||
|
||||
// StepsResult is the built Steps.
|
||||
type StepsResult struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports that the user chose a clickable step in this frame.
|
||||
func (r StepsResult) Changed() bool { return r.changed }
|
||||
|
||||
type stepsState int
|
||||
|
||||
const (
|
||||
@@ -48,7 +39,7 @@ const (
|
||||
)
|
||||
|
||||
// Steps shows a process whose step *current is under way: earlier steps are done.
|
||||
func Steps(c *ui.Context, current *int, steps []Step, opts StepsOptions) StepsResult {
|
||||
func Steps(c *ui.Context, current *int, steps []Step, opts StepsOptions) ChoiceResult {
|
||||
if len(steps) == 0 {
|
||||
panic("mujica: Steps needs at least one step")
|
||||
}
|
||||
@@ -63,7 +54,7 @@ func Steps(c *ui.Context, current *int, steps []Step, opts StepsOptions) StepsRe
|
||||
if opts.Label != "" {
|
||||
root.Label(opts.Label)
|
||||
}
|
||||
res := StepsResult{Element: root}
|
||||
res := ChoiceResult{Element: root}
|
||||
root.Children(func() {
|
||||
for i, s := range steps {
|
||||
if s.Title == "" {
|
||||
|
||||
@@ -27,17 +27,8 @@ type TabsOptions struct {
|
||||
Label string
|
||||
}
|
||||
|
||||
// TabsResult is the built Tabs: the element holding tabs and panel.
|
||||
type TabsResult struct {
|
||||
*ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports a new choice made by the user in this frame.
|
||||
func (r TabsResult) Changed() bool { return r.changed }
|
||||
|
||||
// Tabs shows tabs choosing *selected and the chosen tab's panel. Disabled tabs are skipped by the arrows.
|
||||
func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) TabsResult {
|
||||
func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) ChoiceResult {
|
||||
if len(tabs) == 0 {
|
||||
panic("mujica: Tabs needs at least one tab")
|
||||
}
|
||||
@@ -66,7 +57,7 @@ func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) TabsResult
|
||||
if opts.Vertical {
|
||||
root.Row()
|
||||
}
|
||||
res := TabsResult{Element: root}
|
||||
res := ChoiceResult{Element: root}
|
||||
root.Children(func() {
|
||||
var strip *ui.Element
|
||||
if opts.Vertical {
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ func TestTabsOverflowScrolls(t *testing.T) {
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
Use(c, Settings{})
|
||||
ui.Column(c).Width(240).Children(func() {
|
||||
w = Tabs(c, &sel, tabs, TabsOptions{}).Bounds().W
|
||||
w = Tabs(c, &sel, tabs, TabsOptions{}).Element.Bounds().W
|
||||
})
|
||||
}, 400, 200)
|
||||
tt.Key(0, ui.KeyTab)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// InputResult is a value input's outer element and its events.
|
||||
type InputResult struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
// Input is the text field inside, for focus and keys.
|
||||
Input *ui.Element
|
||||
changed bool
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// ChoiceResult is a choice control's element and whether its value changed this frame.
|
||||
type ChoiceResult struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
|
||||
+2
-11
@@ -29,22 +29,13 @@ type TransferOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// TransferParts are the parts of a Transfer.
|
||||
type TransferParts struct {
|
||||
Element *ui.Element
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Changed reports whether items moved this frame.
|
||||
func (p TransferParts) Changed() bool { return p.changed }
|
||||
|
||||
type transferState struct {
|
||||
checked map[string]bool
|
||||
query [2]string
|
||||
}
|
||||
|
||||
// Transfer moves items between a source list and *target, the chosen IDs in order.
|
||||
func Transfer(c *ui.Context, target *[]string, items []TransferItem, opts TransferOptions) TransferParts {
|
||||
func Transfer(c *ui.Context, target *[]string, items []TransferItem, opts TransferOptions) ChoiceResult {
|
||||
byID := make(map[string]TransferItem, len(items))
|
||||
for _, it := range items {
|
||||
if _, dup := byID[it.ID]; dup || it.ID == "" {
|
||||
@@ -58,7 +49,7 @@ func Transfer(c *ui.Context, target *[]string, items []TransferItem, opts Transf
|
||||
}
|
||||
}
|
||||
d := Density(c)
|
||||
parts := TransferParts{}
|
||||
parts := ChoiceResult{}
|
||||
row := ui.Row(c).Gap(d.Unit() * 3).AlignItems(ui.Center).Disabled(opts.Disabled)
|
||||
parts.Element = row
|
||||
st := ui.Local(row, "transfer", func() transferState { return transferState{checked: map[string]bool{}} })
|
||||
|
||||
@@ -160,7 +160,7 @@ func (t *treeCore[K]) itemStatus(c *ui.Context, item K, status func(K) DataStatu
|
||||
|
||||
// TreeView is a built Tree and what happened to it in this frame.
|
||||
type TreeView struct {
|
||||
*ui.Element
|
||||
Element *ui.Element
|
||||
changed, submitted, retry bool
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ func Tree[K comparable](c *ui.Context, s *TreeState[K], opts TreeOptions[K]) Tre
|
||||
t.begin(opts.Label, &s.Selection, opts.Multiple)
|
||||
var v TreeView
|
||||
v.Element = ui.Column(c).MinHeight(0)
|
||||
v.Children(func() {
|
||||
v.Element.Children(func() {
|
||||
if len(opts.Roots) == 0 {
|
||||
t.clear(&s.Selection)
|
||||
v.retry = dataPlaceholder(c, opts.Status, opts.Error, opts.Empty, msg(c, "list.empty"))
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ func TreeTable[K comparable](c *ui.Context, s *TreeTableState[K], opts TreeTable
|
||||
before := s.Sort
|
||||
var v DataTableView
|
||||
v.Element = ui.Column(c).MinHeight(0).Gap(d.Unit() * 2)
|
||||
v.Children(func() {
|
||||
v.Element.Children(func() {
|
||||
if len(opts.Roots) == 0 {
|
||||
t.clear(&s.Selection)
|
||||
v.retry = dataPlaceholder(c, opts.Status, opts.Error, opts.Empty, msg(c, "list.empty"))
|
||||
|
||||
+4
-4
@@ -50,7 +50,7 @@ func (h *ttHarness) view(c *ui.Context) {
|
||||
h.c = c
|
||||
ui.Column(c).Padding(10).Children(func() {
|
||||
v := TreeTable(c, &h.st, h.opts)
|
||||
v.Size(360, 320)
|
||||
v.Element.Size(360, 320)
|
||||
if v.SortChanged() {
|
||||
h.sorts++
|
||||
}
|
||||
@@ -154,7 +154,7 @@ func TestTreeTableLargeIsVirtualized(t *testing.T) {
|
||||
Use(c, Settings{})
|
||||
clear(rows)
|
||||
TreeTable(c, &st, TreeTableOptions[int]{Roots: roots, Children: kids, Label: strconv.Itoa,
|
||||
Columns: []TreeColumn[int]{{ID: "n", Title: "N", Cell: cell(c), Compare: cmp.Compare[int]}, {ID: "m", Title: "M", Cell: cell(c)}}}).Size(320, 360)
|
||||
Columns: []TreeColumn[int]{{ID: "n", Title: "N", Cell: cell(c), Compare: cmp.Compare[int]}, {ID: "m", Title: "M", Cell: cell(c)}}}).Element.Size(320, 360)
|
||||
}
|
||||
tt := ui.NewTester(view, 400, 400)
|
||||
const bound = 26 // see TestTreeLargeIsVirtualized
|
||||
@@ -187,7 +187,7 @@ func TestTreeTableSnapshot(t *testing.T) {
|
||||
})
|
||||
(*h).c = c
|
||||
ui.Column(c).Padding(16).Children(func() {
|
||||
TreeTable(c, &(*h).st, (*h).opts).Size(388, 208)
|
||||
TreeTable(c, &(*h).st, (*h).opts).Element.Size(388, 208)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func TestTreeTableSelectionFollowsLargeSort(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
Columns: []TreeColumn[int]{{ID: "n", Title: "N", Cell: func(n int) { ui.Text(c, "#"+strconv.Itoa(n)) }, Compare: cmp.Compare[int]}},
|
||||
}).Size(320, 360)
|
||||
}).Element.Size(320, 360)
|
||||
}
|
||||
st.SetOpen(0, true)
|
||||
tt := ui.NewTester(view, 400, 400)
|
||||
|
||||
+4
-4
@@ -53,7 +53,7 @@ func (h *treeHarness) view(c *ui.Context) {
|
||||
opts.Label = func(n string) string { h.built[n] = true; return label(n) }
|
||||
ui.Column(c).Padding(10).Children(func() {
|
||||
v := Tree(c, &h.st, opts)
|
||||
v.Size(320, 360)
|
||||
v.Element.Size(320, 360)
|
||||
if v.Changed() {
|
||||
h.changes++
|
||||
}
|
||||
@@ -186,7 +186,7 @@ func TestTreeLargeIsVirtualized(t *testing.T) {
|
||||
Tree(c, &st, TreeOptions[int]{Roots: roots, Children: kids, Label: func(n int) string {
|
||||
built++
|
||||
return fmt.Sprintf("Node %d", n)
|
||||
}}).Size(320, 360)
|
||||
}}).Element.Size(320, 360)
|
||||
}
|
||||
tt := ui.NewTester(view, 400, 400)
|
||||
// Two Label calls per row; a jump builds 2×(10 rows + 2 overscan) + 2 edges.
|
||||
@@ -220,7 +220,7 @@ func TestTreeSnapshot(t *testing.T) {
|
||||
return h
|
||||
})
|
||||
ui.Column(c).Padding(16).Children(func() {
|
||||
Tree(c, &(*h).st, (*h).opts).Size(360, 228)
|
||||
Tree(c, &(*h).st, (*h).opts).Element.Size(360, 228)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func TestTreeSelectionFollowsLargeReorder(t *testing.T) {
|
||||
view := func(c *ui.Context) {
|
||||
Use(c, Settings{Mode: Light})
|
||||
Tree(c, &st, TreeOptions[int]{Roots: roots, Children: func(int) []int { return nil },
|
||||
Label: func(n int) string { return fmt.Sprintf("Node %d", n) }}).Size(320, 360)
|
||||
Label: func(n int) string { return fmt.Sprintf("Node %d", n) }}).Element.Size(320, 360)
|
||||
}
|
||||
tt := ui.NewTester(view, 400, 400)
|
||||
if err := tt.Click("Node 2"); err != nil {
|
||||
|
||||
Reference in new issue
Block a user