Skip to content
Merged
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
10 changes: 5 additions & 5 deletions KIVY_PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,17 @@ mutually exclusive. `allow_no_selection` defaults to `true`; set it to `false`
when one member must remain selected. `toggle_button_group_members(id)` returns
the mounted IDs belonging to the same group.

## GridLayout
## Grid

![GridLayout example](docs/images/widgets/grid_layout.png)
![Grid example](docs/images/widgets/grid_layout.png)

`GridLayout` assigns children to matrix cells in declaration order. At least
`Grid` assigns children to matrix cells in declaration order. At least
one of `columns`/`cols` or `rows` is required. With one constraint, the other
dimension grows to fit the children; setting both places a fixed limit on the
number of cells.

```vml
GridLayout {
Grid {
columns: 3
padding: 8
spacing: 8
Expand All @@ -172,7 +172,7 @@ GridLayout {
}
```

The V API is `grid_layout(GridLayoutConfig{...})` and returns an error for an
The V API is `grid(GridConfig{...})` and returns an error for an
invalid constraint or capacity. It supports all eight two-axis orientations,
per-edge `GridPadding`, horizontal/vertical `GridSpacing`, default or forced
row and column sizes, and per-index minimum sizes. VML also accepts
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ and Android.
The Linux backend draws and handles widgets directly through `gg`/Sokol; it has
no GTK dependency.

The V API includes responsive Flex and Grid placement with intrinsic text
measurement. See the [layout guide](docs/modern-layout.md) and
[responsive example](examples/responsive_layout/main.v).

You can also build desktop ui2 apps in C or any language with a C FFI. Build
the shared library and the C counter with `make c-example`; the standalone
header is [`c/ui2.h`](c/ui2.h). See the [C API guide](c/README.md) for the build
Expand Down
103 changes: 103 additions & 0 deletions benchmarks/flex_measure/main.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Measures intrinsic text and Flex/Grid placement through the V API.
// Run with the compiler and module path documented in README.md.
module main

import os
import time
import ui2

const runs = 7

struct Case {
name string
width f64
depth int
grid bool
}

fn measured_label(text string, width f64) !ui2.Element {
element := ui2.label('', text, ui2.Rect{}, ui2.TextStyle{ lines: 3 })
size := ui2.measure_layout_element(element, ui2.LayoutConstraints{ max_width: width }, ui2.measure_layout_text)!
return ui2.Element{ ...element, frame: ui2.rect(0, 0, size.width, size.height) }
}

fn build(case Case) !int {
if case.grid {
mut children := []ui2.Element{}
for index in 0 .. 12 {
children << measured_label('Project ${index}: a description that wraps in narrow cards', case.width / 3)!
}
config := ui2.GridConfig{
frame: ui2.rect(0, 0, case.width, 780)
auto_columns_min_width: 240
max_columns: 3
spacing: ui2.GridSpacing{ horizontal: 16, vertical: 16 }
child_spans: [ui2.GridSpan{ column_span: 2 }]
children: children
}
return ui2.grid(config)!.children.len
}
if case.depth == 0 {
mut children := []ui2.FlexChild{}
for index in 0 .. 200 {
children << ui2.FlexChild{ element: measured_label('Item ${index}', case.width)! }
}
return ui2.flex(ui2.FlexConfig{ frame: ui2.rect(0, 0, case.width, 780), gap: 6, wrap: true, children: children })!.children.len
}
mut element := measured_label('Content that can wrap at a narrow width', case.width)!
for level in 0 .. case.depth {
config := ui2.FlexConfig{
orientation: if level % 2 == 0 {
ui2.LayoutOrientation.horizontal
} else {
ui2.LayoutOrientation.vertical
}
gap: 4
children: [
ui2.FlexChild{ element: measured_label('Level ${level}', case.width)! },
ui2.FlexChild{ element: element },
]
}
preferred := ui2.flex_preferred_size(config)!
element = ui2.flex(ui2.FlexConfig{ ...config, frame: preferred })!
}
return element.children.len
}

fn median(values []f64) f64 {
mut sorted := values.clone()
sorted.sort(a < b)
return sorted[sorted.len / 2]
}

fn main() {
cases := [
Case{ name: 'grid wide', width: 1000, grid: true },
Case{ name: 'grid compact', width: 390, grid: true },
Case{ name: 'flat wrap 200', width: 1000 },
Case{ name: 'nested 4', width: 1000, depth: 4 },
Case{ name: 'nested 8', width: 1000, depth: 8 },
Case{ name: 'nested 10', width: 1000, depth: 10 },
]
filter := if os.args.len > 1 { os.args[1] } else { '' }
mut checksum := 0
for case in cases {
if filter.len > 0 && !case.name.contains(filter) { continue }
checksum += build(case)!
mut iterations := 1
for {
watch := time.new_stopwatch()
for _ in 0 .. iterations { checksum += build(case)! }
if watch.elapsed().milliseconds() >= 100 || iterations >= 100_000 { break }
iterations *= 2
}
mut samples := []f64{cap: runs}
for _ in 0 .. runs {
watch := time.new_stopwatch()
for _ in 0 .. iterations { checksum += build(case)! }
samples << f64(watch.elapsed().microseconds()) / f64(iterations)
}
println('${case.name:-28} ${median(samples):12.1f} us/build')
}
println('checksum ${checksum}')
}
64 changes: 64 additions & 0 deletions docs/modern-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Flex and Grid

The V layout API separates measurement from placement. `measure_layout_element`
measures text and content under `LayoutConstraints`; `flex_frames` and
`grid_frames` assign child frames. `flex` and `grid` return an `Element` with
those frames applied. Dimensions and spacing stay fractional through these steps.

## Flex

`FlexConfig` arranges `FlexChild` values horizontally or vertically. A child's
`basis` supplies its preferred main-axis size, `grow` shares extra space, and
`shrink` shares a deficit in proportion to its basis. Minima and maxima constrain
the result. Wrapping creates separate lines with `line_gap` between them.

```v
children := [
ui2.FlexChild{ element: heading, grow: 1, minimum_width: 170 },
ui2.FlexChild{ element: action, shrink: 0 },
]
toolbar := ui2.flex(ui2.FlexConfig{
frame: ui2.rect(0, 0, available_width, 40)
gap: 12
align: .center
children: children
})!
```

`LayoutOrientation`, `LayoutAlignment`, and `LayoutPadding` provide shared
orientation, alignment, and padding values. `FlexChild.align_self` overrides the
container's alignment for one child; `.auto` inherits the container's alignment.

Measure a width-dependent child again after calculating its assigned width.
For example, a wrapped label can need more height when its Flex sibling grows.
Use `measure_layout_element` with that width and then place the measured child.
The measurement callback receives the declared `TextStyle`.

## Grid

`GridConfig` supports fixed rows or columns, track minima, spacing and padding.
With `auto_columns_min_width`, it selects the number of columns that fit the
available width. `max_columns` caps that count. A `GridSpan` can reserve adjacent
rows or columns for a child; automatic grids clamp column spans when the viewport
becomes narrower.

```v
cards := ui2.grid(ui2.GridConfig{
frame: ui2.rect(0, 0, available_width, available_height)
auto_columns_min_width: 240
max_columns: 3
spacing: ui2.GridSpacing{ horizontal: 16, vertical: 16 }
child_spans: [ui2.GridSpan{ column_span: 2 }]
children: project_cards
})!
```

`flex_preferred_size` and `grid_preferred_size` report natural sizes from measured
children. They calculate geometry without constructing a view or invoking actions.
Explicit child frames provide preferences; the parent assigns the final frames.

The [responsive example](../examples/responsive_layout/main.v) combines wrapped
toolbar rows, a search control, and a spanning project grid. The
[grid example](../examples/grid_layout/main.v) demonstrates equal cells. The
[measurement benchmark](../benchmarks/flex_measure/main.v) exercises text
measurement, wrapping, nested Flex containers and responsive Grid placement.
33 changes: 0 additions & 33 deletions examples/grid_layout/grid_layout.vml

This file was deleted.

3 changes: 1 addition & 2 deletions examples/grid_layout/grid_layout_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ fn find_grid_element(element ui2.Element, id string) ?ui2.Element {
}

fn test_grid_layout_demo_uses_responsive_equal_cells() {
root := ui2.element_from_vml_model(grid_vml_source, GridLayoutDemo{}, ui2.rect(0, 0,
grid_width, grid_height)) or { panic(err) }
root := grid_tree(ui2.rect(0, 0, grid_width, grid_height)) or { panic(err) }
grid := find_grid_element(root, 'dashboard') or { panic('missing dashboard grid') }
assert grid.children.len == 6
assert grid.children[0].frame == ui2.rect(8, 8, 96, 78)
Expand Down
39 changes: 30 additions & 9 deletions examples/grid_layout/main.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,37 @@ import ui2

const grid_width = 360
const grid_height = 260
const grid_vml_source = $embed_file('grid_layout.vml').to_string()

pub struct GridLayoutDemo {}
fn grid_tree(frame ui2.Rect) !ui2.Element {
captions := ['Overview', 'Activity', 'Messages', 'Files', 'Calendar', 'Settings']
colors := [u32(0xdbeafe), u32(0xdcfce7), u32(0xfce7f3), u32(0xfef3c7), u32(0xede9fe),
u32(0xe2e8f0)]
mut children := []ui2.Element{}
for index, caption in captions {
children << ui2.button('dashboard-${index}', caption, ui2.Rect{},
ui2.BoxStyle{ bg: colors[index], radius: 8 }, ui2.TextStyle{})
}
dashboard := ui2.grid(ui2.GridConfig{
id: 'dashboard'
frame: ui2.rect(20, 60, frame.width - 40, frame.height - 80)
columns: 3
padding: ui2.GridPadding{ left: 8, right: 8, top: 8, bottom: 8 }
spacing: ui2.GridSpacing{ horizontal: 8, vertical: 8 }
children: children
})!
return ui2.screen(0xf1f5f9, [
ui2.label('heading', 'Dashboard', ui2.rect(20, 16, frame.width - 40, 32),
ui2.TextStyle{ color: 0x0f172a, size: 22, bold: true }),
dashboard,
])
}

fn build() ui2.Element {
return grid_tree(ui2.bounds()) or { panic(err) }
}

fn event(_id string) {}

fn main() {
ui2.run_vml[GridLayoutDemo](
source: grid_vml_source
model: GridLayoutDemo{}
title: 'Grid Layout'
width: grid_width
height: grid_height
) or { panic(err) }
ui2.run_window('Grid layout', grid_width, grid_height, build, event)
}
Loading
Loading