Data: review r1 fixes
This commit is contained in:
38 files changed
+602
-148
No files matched your search
+122
-21
@@ -1,7 +1,10 @@
|
||||
package mujica
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
@@ -10,58 +13,156 @@ import (
|
||||
"mujicaui/theme"
|
||||
)
|
||||
|
||||
// DataTableColumnPicker is a Columns button whose popover shows and hides columns.
|
||||
// The last visible column cannot be hidden.
|
||||
func DataTableColumnPicker[R any, K comparable](c *ui.Context, s *DataTableState[K], cols []DataColumn[R]) *ui.Element {
|
||||
// tableColumn is what the column menu knows of a column.
|
||||
type tableColumn struct {
|
||||
ID, Title string
|
||||
Width, MinWidth float32
|
||||
Sortable bool
|
||||
}
|
||||
|
||||
// tableWidthStep is how far one Narrower or Wider press resizes a column.
|
||||
const tableWidthStep float32 = 16
|
||||
|
||||
// tableColumnMenu is the keyboard path to sort, move, resize and, with hidden, show columns.
|
||||
func tableColumnMenu(c *ui.Context, cols []tableColumn, sort *ui.SortOrder, layout *ui.TableLayout, hidden *map[string]bool) {
|
||||
k := Tokens(c)
|
||||
u := Density(c).Unit()
|
||||
b := Button(c, msg(c, "data_table.columns"), ButtonOptions{Variant: Secondary, Icon: icons.Must("eye")})
|
||||
b := Button(c, msg(c, "data_table.columns"), ButtonOptions{Variant: Secondary, Icon: icons.Must("settings")})
|
||||
open := ui.Local(b, "open", func() bool { return false })
|
||||
if b.Clicked() {
|
||||
*open = !*open
|
||||
}
|
||||
visible := 0
|
||||
for _, col := range cols {
|
||||
if !s.Hidden[col.ID] {
|
||||
visible++
|
||||
order := tableOrder(cols, *layout)
|
||||
visible := len(cols)
|
||||
if hidden != nil {
|
||||
for _, col := range cols {
|
||||
if (*hidden)[col.ID] {
|
||||
visible--
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.PopoverBase(c, b, open, func(panel *ui.Element) {
|
||||
panel.Background(k.Surface).Border(theme.BorderWidth, k.ControlBorder).Radius(theme.PopupRadius).
|
||||
Padding(u * 2).Gap(u).MinWidth(200).MarginY(u)
|
||||
for _, col := range cols {
|
||||
ui.Column(c).Key(col.ID).Children(func() { dataTablePickerItem(c, s, col.ID, col.Title, visible) })
|
||||
Padding(u * 2).Gap(u).MinWidth(240).MarginY(u)
|
||||
for at, id := range order {
|
||||
col := cols[slices.IndexFunc(cols, func(col tableColumn) bool { return col.ID == id })]
|
||||
ui.Row(c).Key(id).Gap(u).AlignItems(ui.Center).Children(func() {
|
||||
if hidden != nil {
|
||||
tableVisibility(c, col, hidden, visible)
|
||||
} else {
|
||||
ui.Text(c, col.Title).SingleLine().Grow(1).Padding(0, u*2)
|
||||
}
|
||||
tableColumnActions(c, col, at, order, sort, layout)
|
||||
})
|
||||
}
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// dataTablePickerItem is one column's check box in the picker.
|
||||
func dataTablePickerItem[K comparable](c *ui.Context, s *DataTableState[K], id, title string, visible int) {
|
||||
// tableColumnActions are one column's sort, move and width buttons.
|
||||
func tableColumnActions(c *ui.Context, col tableColumn, at int, order []string, sort *ui.SortOrder, layout *ui.TableLayout) {
|
||||
act := func(icon, key string, disabled bool) bool {
|
||||
return Button(c, "", ButtonOptions{Variant: Ghost, Icon: icons.Must(icon), Label: fmt.Sprintf(msg(c, key), col.Title), Disabled: disabled}).Clicked()
|
||||
}
|
||||
if col.Sortable {
|
||||
for _, desc := range []bool{false, true} {
|
||||
icon, key := "arrow-up", "data_table.sortAsc"
|
||||
if desc {
|
||||
icon, key = "arrow-down", "data_table.sortDesc"
|
||||
}
|
||||
if act(icon, key, *sort == ui.SortOrder{Column: col.ID, Descending: desc}) {
|
||||
*sort = ui.SortOrder{Column: col.ID, Descending: desc}
|
||||
slog.Debug("mujica: table sort", "column", col.ID, "descending", desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, step := range []int{-1, 1} {
|
||||
icon, key := "chevron-left", "data_table.moveLeft"
|
||||
if step > 0 {
|
||||
icon, key = "chevron-right", "data_table.moveRight"
|
||||
}
|
||||
to := at + step
|
||||
if act(icon, key, to < 0 || to >= len(order)) {
|
||||
ids := slices.Clone(order)
|
||||
ids[at], ids[to] = ids[to], ids[at]
|
||||
layout.Order = ids
|
||||
slog.Debug("mujica: table move", "column", col.ID, "to", to)
|
||||
}
|
||||
}
|
||||
w := tableWidth(col, *layout)
|
||||
least := max(col.MinWidth, tableWidthStep)
|
||||
if act("minus", "data_table.narrower", w <= least) {
|
||||
tableSetWidth(layout, col.ID, max(least, w-tableWidthStep))
|
||||
}
|
||||
if act("plus", "data_table.wider", false) {
|
||||
tableSetWidth(layout, col.ID, w+tableWidthStep)
|
||||
}
|
||||
}
|
||||
|
||||
// tableVisibility is a column's show/hide check box; the last visible one cannot hide.
|
||||
func tableVisibility(c *ui.Context, col tableColumn, hidden *map[string]bool, visible int) {
|
||||
k := Tokens(c)
|
||||
d := Density(c)
|
||||
u := d.Unit()
|
||||
on := !s.Hidden[id]
|
||||
on := !(*hidden)[col.ID]
|
||||
last := on && visible == 1
|
||||
cb := ui.CheckboxBase(c, &on).Disabled(last).MinHeight(d.ControlHeight()).
|
||||
cb := ui.CheckboxBase(c, &on).Label(col.Title).Disabled(last).MinHeight(d.ControlHeight()).Grow(1).
|
||||
Padding(0, u*2).Gap(u * 2).AlignItems(ui.Center).Radius(theme.ControlRadius)
|
||||
internal.Ring(cb, k.Focus, theme.ControlRadius)
|
||||
if cb.Hovered() && !last {
|
||||
cb.Background(k.SurfaceHover)
|
||||
}
|
||||
if cb.Changed() && !last {
|
||||
if s.Hidden == nil {
|
||||
s.Hidden = map[string]bool{}
|
||||
m := maps.Clone(*hidden)
|
||||
if m == nil {
|
||||
m = map[string]bool{}
|
||||
}
|
||||
s.Hidden[id] = !on
|
||||
slog.Debug("mujica: data_table column", "id", id, "visible", on)
|
||||
m[col.ID] = !on
|
||||
*hidden = m
|
||||
slog.Debug("mujica: table column", "id", col.ID, "visible", on)
|
||||
}
|
||||
cb.Children(func() {
|
||||
dataTableCheckBox(c, on)
|
||||
ui.Text(c, title).SingleLine()
|
||||
ui.Text(c, col.Title).SingleLine()
|
||||
})
|
||||
}
|
||||
|
||||
// tableOrder lists column IDs as the layout orders them, the rest after.
|
||||
func tableOrder(cols []tableColumn, l ui.TableLayout) []string {
|
||||
var ids []string
|
||||
for _, id := range l.Order {
|
||||
if slices.ContainsFunc(cols, func(col tableColumn) bool { return col.ID == id }) && !slices.Contains(ids, id) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
for _, col := range cols {
|
||||
if !slices.Contains(ids, col.ID) {
|
||||
ids = append(ids, col.ID)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// tableWidth is a column's set width; a sharing column starts from its least, at least 120.
|
||||
func tableWidth(col tableColumn, l ui.TableLayout) float32 {
|
||||
if w, ok := l.Widths[col.ID]; ok {
|
||||
return w
|
||||
}
|
||||
if col.Width > 0 {
|
||||
return col.Width
|
||||
}
|
||||
return max(col.MinWidth, 120)
|
||||
}
|
||||
|
||||
func tableSetWidth(l *ui.TableLayout, id string, w float32) {
|
||||
m := maps.Clone(l.Widths)
|
||||
if m == nil {
|
||||
m = map[string]float32{}
|
||||
}
|
||||
m[id] = w
|
||||
l.Widths = m
|
||||
slog.Debug("mujica: table width", "column", id, "width", w)
|
||||
}
|
||||
|
||||
// dataTableCheckBox draws a 16 DIP check box: OnAccent check on Accent when on.
|
||||
func dataTableCheckBox(c *ui.Context, on bool) {
|
||||
k := Tokens(c)
|
||||
|
||||
Reference in new issue
Block a user