Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions pkg/x/m3table/cmd/demo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"log"
"os"

"github.com/zodimo/go-compose/compose"
"github.com/zodimo/go-compose/pkg/api"
"github.com/zodimo/go-compose/runtime"
"github.com/zodimo/go-compose/store"
"github.com/zodimo/go-compose/theme"

"gioui.org/app"
"gioui.org/io/system"
"gioui.org/op"
"gioui.org/unit"
)

func main() {
go func() {
w := new(app.Window)
w.Option(app.Title("Table Demo"))
w.Option(app.Size(unit.Dp(600), unit.Dp(800)))

if err := Run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}

func Run(window *app.Window) error {
enLocale := system.Locale{Language: "en", Direction: system.LTR}
var ops op.Ops

store := store.NewPersistentState()
store.Subscribe(func() {
window.Invalidate()
})

runtime := runtime.NewRuntime()
themeManager := theme.GetThemeManager()

for {
switch frameEvent := window.Event().(type) {
case app.DestroyEvent:
return frameEvent.Err
case app.FrameEvent:
gtx := app.NewContext(&ops, frameEvent)
gtx.Locale = enLocale
gtx = themeManager.Material3ThemeInit(gtx)

composer := compose.NewComposer(api.ComposerWithStore(store))

callOp := runtime.Run(gtx, composer, UI())
callOp.Add(gtx.Ops)
frameEvent.Frame(gtx.Ops)
}
}
}
75 changes: 75 additions & 0 deletions pkg/x/m3table/cmd/demo/ui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"fmt"
"image/color"

"github.com/zodimo/go-compose/compose/foundation/layout/box"
"github.com/zodimo/go-compose/compose/foundation/layout/column"
ftext "github.com/zodimo/go-compose/compose/foundation/text"
"github.com/zodimo/go-compose/compose/material3/text"
"github.com/zodimo/go-compose/compose/ui/graphics"
"github.com/zodimo/go-compose/modifiers/background"
"github.com/zodimo/go-compose/modifiers/padding"
"github.com/zodimo/go-compose/modifiers/size"
"github.com/zodimo/go-compose/pkg/api"
"github.com/zodimo/go-compose/pkg/x/m3table"
)

func UI() api.Composable {
return func(c api.Composer) api.Composer {

return column.Column(
c.Sequence(
// Title for Fixed Grid
text.HeadlineMedium("Table",
ftext.WithModifier(padding.All(16)),
),

m3table.Table(
[]m3table.Column{
{
Header: text.BodyLarge("Header 1"),
Width: 100,
},
{
Header: text.BodyLarge("Header 2"),
Width: 200,
},
{
Header: text.BodyLarge("Header 3"),
Width: 300,
},
},
10,
func(row, col int) api.Composable {
return text.BodyLarge(fmt.Sprintf("Cell %d,%d", row, col))
},
),
),
column.WithModifier(size.FillMax()),
)(c)
}
}

// GridItem creates a single grid item with colored background
func GridItem(index int) api.Composable {
// Simple alternating colors for visual distinction
colors := []color.NRGBA{
{R: 234, G: 221, B: 255, A: 255}, // Primary container
{R: 232, G: 222, B: 248, A: 255}, // Secondary container
{R: 255, G: 216, B: 228, A: 255}, // Tertiary container
}
bgColor := colors[index%len(colors)]

return box.Box(
text.TitleLarge(fmt.Sprintf("%d", index)),
box.WithModifier(
size.Height(80).
Then(size.FillMaxWidth()).
Then(background.Background(graphics.FromNRGBA(bgColor))).
Then(padding.All(8)),
),
box.WithAlignment(box.Center),
)
}
54 changes: 54 additions & 0 deletions pkg/x/m3table/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package m3table

import (
"github.com/zodimo/go-compose/compose/ui"
"github.com/zodimo/go-compose/compose/ui/unit"
"github.com/zodimo/go-compose/pkg/api"
)

// Column defines the configuration for a single column in the table.
type Column struct {
// Header is an optional composable that defines the column header.
Header api.Composable
// Weight is the flex weight of the column. If greater than 0, the column
// scales proportionally based on its weight.
Weight int
// Width is the fixed width of the column. This is used if Weight is 0.
Width unit.Dp
}

type TableOptions struct {
Modifier ui.Modifier
MinHeaderRowHeight unit.Dp
MinRowHeight unit.Dp
}

type TableOption func(*TableOptions)

func DefaultTableOptions() TableOptions {
return TableOptions{
Modifier: ui.EmptyModifier,
MinHeaderRowHeight: 56, // Material 3 standard data table header row height
MinRowHeight: 52, // Material 3 standard data table row height
}
}

func WithModifier(modifier ui.Modifier) TableOption {
return func(o *TableOptions) {
o.Modifier = modifier
}
}

// WithMinHeaderRowHeight sets the minimum height for the header row.
func WithMinHeaderRowHeight(height unit.Dp) TableOption {
return func(o *TableOptions) {
o.MinHeaderRowHeight = height
}
}

// WithMinRowHeight sets the minimum height for data rows.
func WithMinRowHeight(height unit.Dp) TableOption {
return func(o *TableOptions) {
o.MinRowHeight = height
}
}
84 changes: 84 additions & 0 deletions pkg/x/m3table/table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package m3table

import (
"github.com/zodimo/go-compose/compose"
"github.com/zodimo/go-compose/compose/foundation/layout/box"
"github.com/zodimo/go-compose/compose/foundation/layout/column"
"github.com/zodimo/go-compose/compose/foundation/layout/row"
"github.com/zodimo/go-compose/compose/material3/divider"
"github.com/zodimo/go-compose/compose/ui"
"github.com/zodimo/go-compose/modifiers/size"
"github.com/zodimo/go-compose/modifiers/weight"
"github.com/zodimo/go-compose/pkg/api"
)

// Table constructs a basic material3 table layout given a list of columns,
// the number of data rows, and a factory function to create each cell's content.
func Table(
columns []Column,
rowCount int,
cellContent func(row, col int) api.Composable,
options ...TableOption,
) api.Composable {

opts := DefaultTableOptions()
for _, option := range options {
if option != nil {
option(&opts)
}
}

return func(c api.Composer) api.Composer {

hasHeaders := false
for _, col := range columns {
if col.Header != nil {
hasHeaders = true
break
}
}

return column.Column(
c.Sequence(
c.When(hasHeaders, func(c api.Composer) api.Composer {
return c.Sequence(
row.Row(
c.Range(len(columns), func(i int) api.Composable {
return wrapCell(columns[i], columns[i].Header)
}),
row.WithAlignment(row.Middle),
row.WithModifier(size.MinHeight(int(opts.MinHeaderRowHeight))),
),
divider.Divider(),
)(c)
}),
c.Range(rowCount, func(r int) api.Composable {
return row.Row(
c.Range(len(columns), func(cIdx int) api.Composable {
return wrapCell(columns[cIdx], cellContent(r, cIdx))
}),
row.WithAlignment(row.Middle),
row.WithModifier(size.MinHeight(int(opts.MinRowHeight))),
)
}),
),
column.WithModifier(opts.Modifier),
)(c)
}
}

// wrapCell wraps the provided content into a box with proper width modifiers.
func wrapCell(col Column, content api.Composable) api.Composable {
var mod ui.Modifier = ui.EmptyModifier
if col.Weight > 0 {
mod = mod.Then(weight.Weight(col.Weight))
} else if col.Width > 0 {
mod = mod.Then(size.Width(int(col.Width)))
}

if content == nil {
content = compose.Id()
}

return box.Box(content, box.WithModifier(mod))
}
15 changes: 15 additions & 0 deletions test-plan.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Ah, the user asked "What about row height constraints ?"
In `Table`, we currently don't constrain the row height.
Usually a table row in Material 3 has a standard minimum height, or we can add `MinHeight` to `TableOptions` or `RowOptions`.
But `pkg/x/m3table/table.go` uses `row.Row`. We can apply a `MinHeight` modifier to the `row.Row` for both headers and cells.
Let's see if there's a standard Material 3 table row height. The Material 3 spec for Data tables says:
Row height: 52dp
Header row height: 56dp

We can add `MinHeight` modifier to the row.
Wait, let's look at `modifiers/size/constructor.go` to see if we have `MinHeight`.
Yes, `MinHeight(minHeight int) ui.Modifier`.

Let's modify `pkg/x/m3table/table.go` to add a default `MinHeight` to rows, or maybe pass it via `TableOptions`.
Since it's a basic table, maybe we can just hardcode or provide options.
Let's provide `MinRowHeight unit.Dp` and `MinHeaderRowHeight unit.Dp` in `TableOptions`.