Skip to content

Commit f4f0e6d

Browse files
committed
showcase: add data grid demo, fix default+sort
1 parent 79af741 commit f4f0e6d

1 file changed

Lines changed: 215 additions & 4 deletions

File tree

examples/showcase.v

Lines changed: 215 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ pub mut:
5454
// table
5555
table_sort_by int
5656
table_border_style string = 'all'
57+
// data grid
58+
data_grid_query gui.GridQueryState
59+
data_grid_selection gui.GridSelection = gui.GridSelection{
60+
selected_row_ids: map[string]bool{}
61+
}
5762
// radio
5863
select_radio bool
5964
// expand_pad
@@ -301,6 +306,13 @@ fn demo_entries() []DemoEntry {
301306
summary: 'Declarative and sortable table data'
302307
tags: ['rows', 'columns', 'csv']
303308
},
309+
DemoEntry{
310+
id: 'data_grid'
311+
label: 'Data Grid'
312+
group: 'data'
313+
summary: 'Controlled virtualized grid for interactive tabular data'
314+
tags: ['grid', 'virtualized', 'data']
315+
},
304316
DemoEntry{
305317
id: 'text'
306318
label: 'Text'
@@ -479,13 +491,23 @@ fn selected_entry(entries []DemoEntry, selected string) DemoEntry {
479491
return entries[0]
480492
}
481493

494+
fn preferred_component_for_group(group string, entries []DemoEntry) string {
495+
if entries.len == 0 {
496+
return ''
497+
}
498+
if group == 'data' && has_entry(entries, 'data_grid') {
499+
return 'data_grid'
500+
}
501+
return entries[0].id
502+
}
503+
482504
fn catalog_panel(mut w gui.Window) gui.View {
483505
mut app := w.state[ShowcaseApp]()
484506
entries := filtered_entries(app)
485507
if entries.len == 0 {
486508
app.selected_component = ''
487509
} else if !has_entry(entries, app.selected_component) {
488-
app.selected_component = entries[0].id
510+
app.selected_component = preferred_component_for_group(app.selected_group, entries)
489511
}
490512
return gui.column(
491513
width: 300
@@ -581,6 +603,10 @@ fn group_picker_item(label string, key string, app &ShowcaseApp) gui.View {
581603
on_click: fn [key] (_ voidptr, mut _ gui.Event, mut w gui.Window) {
582604
mut app := w.state[ShowcaseApp]()
583605
app.selected_group = key
606+
if key == 'data' {
607+
entries := filtered_entries(app)
608+
app.selected_component = preferred_component_for_group(key, entries)
609+
}
584610
w.scroll_vertical_to(id_scroll_catalog, 0)
585611
w.update_window()
586612
}
@@ -666,6 +692,14 @@ fn catalog_row(entry DemoEntry, app &ShowcaseApp) gui.View {
666692
)
667693
}
668694

695+
fn detail_panel_padding() gui.Padding {
696+
base := gui.theme().padding_large
697+
return gui.Padding{
698+
...base
699+
right: base.right + gui.theme().scrollbar_style.size + f32(4)
700+
}
701+
}
702+
669703
fn detail_panel(mut w gui.Window) gui.View {
670704
mut app := w.state[ShowcaseApp]()
671705
entries := filtered_entries(app)
@@ -676,14 +710,14 @@ fn detail_panel(mut w gui.Window) gui.View {
676710
gap_edge: 4
677711
}
678712
sizing: gui.fill_fill
679-
padding: gui.theme().padding_large
713+
padding: detail_panel_padding()
680714
content: [
681715
gui.text(text: 'No component matches filter', text_style: gui.theme().b2),
682716
]
683717
)
684718
}
685719
if !has_entry(entries, app.selected_component) {
686-
app.selected_component = entries[0].id
720+
app.selected_component = preferred_component_for_group(app.selected_group, entries)
687721
}
688722
entry := selected_entry(entries, app.selected_component)
689723
mut content := []gui.View{}
@@ -701,7 +735,7 @@ fn detail_panel(mut w gui.Window) gui.View {
701735
gap_edge: 4
702736
}
703737
sizing: gui.fill_fill
704-
padding: gui.theme().padding_large
738+
padding: detail_panel_padding()
705739
spacing: gui.spacing_large
706740
content: content
707741
)
@@ -728,6 +762,7 @@ fn component_demo(mut w gui.Window, id string) gui.View {
728762
'text' { demo_text() }
729763
'rtf' { demo_rtf() }
730764
'table' { demo_table(mut w) }
765+
'data_grid' { demo_data_grid(mut w) }
731766
'date_picker' { demo_date_picker(mut w) }
732767
'input_date' { demo_input_date(mut w) }
733768
'date_picker_roller' { demo_date_picker_roller(mut w) }
@@ -769,6 +804,7 @@ fn related_examples(id string) string {
769804
'text' { 'examples/fonts.v, examples/system_font.v' }
770805
'rtf' { 'examples/rtf.v' }
771806
'table' { 'examples/table_demo.v' }
807+
'data_grid' { 'examples/data_grid_demo.v, docs/DATA_GRID.md' }
772808
'date_picker', 'input_date' { 'examples/date_picker_options.v, examples/date_time.v' }
773809
'date_picker_roller' { 'examples/date_picker_roller.v' }
774810
'svg' { 'examples/svg_demo.v, examples/tiger.v' }
@@ -1363,6 +1399,31 @@ fn markdown_preview(window &gui.Window) gui.View {
13631399
| Table | Data | Ready |
13641400
| Dialog | Overlay | Ready |'
13651401

1402+
const showcase_data_grid_features_source = '# Data Grid Features
1403+
1404+
- Virtual row rendering
1405+
- Single and multi-column sorting (shift-click)
1406+
- Per-column filter row + quick filter input
1407+
- Row selection: single, toggle, range
1408+
- Keyboard navigation + `ctrl/cmd+a`
1409+
- Header keyboard controls (sort/reorder/resize/pin/focus)
1410+
- Column resize drag + double-click auto-fit
1411+
- Controlled column reorder (`<` / `>` header controls)
1412+
- Controlled column pin cycle (`•` -> `↤` -> `↦`)
1413+
- Controlled column chooser (`show_column_chooser`, `hidden_column_ids`)
1414+
- Group headers (`group_by`) with optional aggregates
1415+
- Controlled master-detail rows
1416+
- Controlled row edit mode + typed cell editors (`text/select/date/checkbox`)
1417+
- Conditional cell formatting (`on_cell_format`)
1418+
- Controlled pagination (`page_size`, `page_index`)
1419+
- Controlled top frozen rows (`frozen_top_row_ids`)
1420+
- Optional frozen header row (`freeze_header`)
1421+
- Clipboard copy (`ctrl/cmd+c`) to TSV
1422+
- CSV import helper
1423+
- CSV helper export
1424+
- XLSX helper export
1425+
- PDF helper export'
1426+
13661427
fn demo_button(mut w gui.Window) gui.View {
13671428
app := w.state[ShowcaseApp]()
13681429
return gui.column(
@@ -2359,6 +2420,114 @@ fn demo_rtf() gui.View {
23592420
)
23602421
}
23612422

2423+
fn showcase_data_grid_columns() []gui.GridColumnCfg {
2424+
return [
2425+
gui.GridColumnCfg{
2426+
id: 'name'
2427+
title: 'Name'
2428+
width: 180
2429+
},
2430+
gui.GridColumnCfg{
2431+
id: 'team'
2432+
title: 'Team'
2433+
width: 140
2434+
},
2435+
gui.GridColumnCfg{
2436+
id: 'status'
2437+
title: 'Status'
2438+
width: 120
2439+
},
2440+
]
2441+
}
2442+
2443+
fn showcase_data_grid_rows() []gui.GridRow {
2444+
return [
2445+
gui.GridRow{
2446+
id: '1'
2447+
cells: {
2448+
'name': 'Alex'
2449+
'team': 'Core'
2450+
'status': 'Active'
2451+
}
2452+
},
2453+
gui.GridRow{
2454+
id: '2'
2455+
cells: {
2456+
'name': 'Mina'
2457+
'team': 'Data'
2458+
'status': 'Active'
2459+
}
2460+
},
2461+
gui.GridRow{
2462+
id: '3'
2463+
cells: {
2464+
'name': 'Noah'
2465+
'team': 'Platform'
2466+
'status': 'Paused'
2467+
}
2468+
},
2469+
gui.GridRow{
2470+
id: '4'
2471+
cells: {
2472+
'name': 'Priya'
2473+
'team': 'Core'
2474+
'status': 'Active'
2475+
}
2476+
},
2477+
gui.GridRow{
2478+
id: '5'
2479+
cells: {
2480+
'name': 'Sam'
2481+
'team': 'Security'
2482+
'status': 'Offline'
2483+
}
2484+
},
2485+
]
2486+
}
2487+
2488+
fn showcase_data_grid_apply_query(rows []gui.GridRow, query gui.GridQueryState) []gui.GridRow {
2489+
mut filtered := rows.filter(showcase_data_grid_row_matches_query(it, query))
2490+
for sort_idx in 0 .. query.sorts.len {
2491+
i := query.sorts.len - 1 - sort_idx
2492+
sort := query.sorts[i]
2493+
filtered.sort_with_compare(fn [sort] (a &gui.GridRow, b &gui.GridRow) int {
2494+
a_val := a.cells[sort.col_id] or { '' }
2495+
b_val := b.cells[sort.col_id] or { '' }
2496+
if a_val == b_val {
2497+
return 0
2498+
}
2499+
if sort.dir == .asc {
2500+
return if a_val < b_val { -1 } else { 1 }
2501+
}
2502+
return if a_val > b_val { -1 } else { 1 }
2503+
})
2504+
}
2505+
return filtered
2506+
}
2507+
2508+
fn showcase_data_grid_row_matches_query(row gui.GridRow, query gui.GridQueryState) bool {
2509+
if query.quick_filter.len > 0 {
2510+
needle := query.quick_filter.to_lower()
2511+
mut any := false
2512+
for _, value in row.cells {
2513+
if value.to_lower().contains(needle) {
2514+
any = true
2515+
break
2516+
}
2517+
}
2518+
if !any {
2519+
return false
2520+
}
2521+
}
2522+
for filter in query.filters {
2523+
cell := row.cells[filter.col_id] or { '' }
2524+
if !cell.to_lower().contains(filter.value.to_lower()) {
2525+
return false
2526+
}
2527+
}
2528+
return true
2529+
}
2530+
23622531
fn showcase_table_rows() [][]string {
23632532
return [
23642533
['Name', 'Role', 'Team', 'City'],
@@ -2473,6 +2642,48 @@ fn demo_table(mut w gui.Window) gui.View {
24732642
)
24742643
}
24752644

2645+
fn demo_data_grid(mut w gui.Window) gui.View {
2646+
app := w.state[ShowcaseApp]()
2647+
rows := showcase_data_grid_apply_query(showcase_data_grid_rows(), app.data_grid_query)
2648+
return gui.column(
2649+
spacing: gui.theme().spacing_small
2650+
content: [
2651+
gui.text(
2652+
text: 'Simple controlled grid. Sort, filter, and select rows.'
2653+
text_style: gui.theme().n5
2654+
),
2655+
gui.text(
2656+
text: 'Rows: ${rows.len} Selected: ${app.data_grid_selection.selected_row_ids.len}'
2657+
text_style: gui.theme().n5
2658+
),
2659+
w.data_grid(
2660+
id: 'catalog_data_grid'
2661+
id_focus: 9162
2662+
sizing: gui.fit_fit
2663+
columns: showcase_data_grid_columns()
2664+
rows: rows
2665+
query: app.data_grid_query
2666+
selection: app.data_grid_selection
2667+
max_height: 260
2668+
on_query_change: fn (query gui.GridQueryState, mut _ gui.Event, mut w gui.Window) {
2669+
mut app := w.state[ShowcaseApp]()
2670+
app.data_grid_query = query
2671+
}
2672+
on_selection_change: fn (selection gui.GridSelection, mut _ gui.Event, mut w gui.Window) {
2673+
mut app := w.state[ShowcaseApp]()
2674+
app.data_grid_selection = selection
2675+
}
2676+
),
2677+
w.markdown(
2678+
id: 'catalog_data_grid_features'
2679+
source: showcase_data_grid_features_source
2680+
mode: .wrap
2681+
padding: gui.padding_none
2682+
),
2683+
]
2684+
)
2685+
}
2686+
24762687
fn demo_date_picker(mut w gui.Window) gui.View {
24772688
app := w.state[ShowcaseApp]()
24782689
return w.date_picker(

0 commit comments

Comments
 (0)