Responsive scaling. ltk now offers two first-class ways to size a UI so it adapts across screens, chosen per process via `WidgetScaling { Fluid, Physical }` (`set_widget_scaling` / `widget_scaling`, default `Fluid`). Fluid sizing (`Length::fluid( px )`) makes a design pixel a proportion of the surface's smaller side, calibrated against a reference width (`set_fluid_reference` / `fluid_reference`, 412 px default) and bounded by `FLUID_MIN` / `FLUID_MAX`; physical sizing (`Length::dp( px )`) is a constant-physical-size pixel scaled by display density (`set_density` / `density`). `Length` gains `orient( portrait, landscape )` — resolve one value in portrait, another in landscape — plus `widget( px )`, which picks fluid or dp per the active mode. Canvas exposes `geom_px` (geometry, resolved in physical layout space) and `font_px` (font size, bridging logical / physical per mode) so widgets and apps share one resolution path. Note the rename: `set_design_reference` / `design_reference` became `set_fluid_reference` / `fluid_reference`, and `Length::dp` changed meaning — the old surface-proportional behaviour now lives on `Length::fluid`.
Widgets. Every stock widget resolves its default geometry and font through the widget-scaling mode instead of frozen pixels, so a whole UI scales coherently without per-call units. New size builders where they were missing: `button` gains `font_size` / `height`, `text_edit` gains `height` / `font_size_fluid`, `separator` gains `pad_v`, and assorted widgets accept a `Length` where they previously took only `f32`.
Overlays. `OverlaySpec::size` is now `( Length, Length )` instead of `( u32, u32 )`, resolved against the main surface when the overlay is materialized, so overlays can scale with the display; `Length::px( … )` reproduces the old fixed sizing.
API stabilization (toward 1.0). Widget struct fields are now `pub( crate )` — they are configured through builders, not field access — except the value / state types apps genuinely read or construct (`Time`, `Date`, `ComboState`), which stay public. The internal `test_support` helpers move behind a `test-support` Cargo feature (off by default, so third-party builds never see them; ltk's own `make test` enables it). `Separator` drops its `0.0`-means-mode sentinel for `Option<Length>`, so an explicit `pad_v( 0.0 )` is a real flush divider distinct from the mode-following default.
Performance guardrails. Opt-in diagnostics via `LTK_PERF_WARN=1` warn about stuck animations, sustained software-render animation, and low `poll_interval`; software-rendered animation is capped near 30 Hz to spare CPU on machines that fall back off EGL. Apps can override the cap with `App::cap_software_animation`.
Docs and build. The two scaling modes are documented in README, onboarding and architecture, with the earlier gradient / backdrop doc drift cleaned up. The Makefile now ships the `locales/` directory into the packaged crate (fixing i18n keys rendering raw for downstreams), builds the new `responsive` example, and runs tests with `--features test-support`.
268 lines
8.7 KiB
Rust
268 lines
8.7 KiB
Rust
// Integration coverage for `Stack` alignment / margin / translation and for
|
||
// the way `Spacer` distributes leftover space inside a `Column`.
|
||
|
||
use ltk::core::Canvas;
|
||
use ltk::{ stack, spacer, HAlign, VAlign, Rect };
|
||
|
||
fn make_canvas() -> Canvas
|
||
{
|
||
Canvas::new( 800, 600 )
|
||
}
|
||
|
||
fn rect( x: f32, y: f32, w: f32, h: f32 ) -> Rect
|
||
{
|
||
Rect { x, y, width: w, height: h }
|
||
}
|
||
|
||
// ── Stack: alignment ──────────────────────────────────────────────────────────
|
||
|
||
#[ test ]
|
||
fn stack_fill_child_takes_full_rect()
|
||
{
|
||
let canvas = make_canvas();
|
||
let s = stack::<()>().push( spacer().width( 100.0 ).height( 100.0 ) );
|
||
let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas );
|
||
|
||
assert_eq!( layout.len(), 1 );
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
// Fill ignores the spacer's preferred size and stretches across the
|
||
// entire stack rect.
|
||
assert_eq!( child_rect, rect( 0.0, 0.0, 400.0, 300.0 ) );
|
||
}
|
||
|
||
#[ test ]
|
||
fn stack_center_centers_child_inside_rect()
|
||
{
|
||
let canvas = make_canvas();
|
||
let child = spacer().width( 100.0 ).height( 50.0 );
|
||
let s = stack::<()>().push_aligned( child, HAlign::Center, VAlign::Center );
|
||
let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas );
|
||
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
assert_eq!( child_rect.x, 150.0 );
|
||
assert_eq!( child_rect.y, 125.0 );
|
||
assert_eq!( child_rect.width, 100.0 );
|
||
assert_eq!( child_rect.height, 50.0 );
|
||
}
|
||
|
||
#[ test ]
|
||
fn stack_end_bottom_anchors_to_bottom_right()
|
||
{
|
||
let canvas = make_canvas();
|
||
let child = spacer().width( 80.0 ).height( 40.0 );
|
||
let s = stack::<()>().push_aligned( child, HAlign::End, VAlign::Bottom );
|
||
let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas );
|
||
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
assert_eq!( child_rect.x, 320.0 );
|
||
assert_eq!( child_rect.y, 260.0 );
|
||
}
|
||
|
||
#[ test ]
|
||
fn stack_start_top_anchors_to_top_left()
|
||
{
|
||
let canvas = make_canvas();
|
||
let child = spacer().width( 40.0 ).height( 30.0 );
|
||
let s = stack::<()>().push_aligned( child, HAlign::Start, VAlign::Top );
|
||
let layout = s.layout( rect( 100.0, 50.0, 400.0, 300.0 ), &canvas );
|
||
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
assert_eq!( child_rect.x, 100.0 );
|
||
assert_eq!( child_rect.y, 50.0 );
|
||
}
|
||
|
||
// ── Stack: margin ─────────────────────────────────────────────────────────────
|
||
|
||
#[ test ]
|
||
fn stack_margin_insets_fill_child_on_all_sides()
|
||
{
|
||
let canvas = make_canvas();
|
||
let child = spacer().width( 50.0 ).height( 50.0 );
|
||
let s = stack::<()>()
|
||
.push_aligned_margin( child, HAlign::Fill, VAlign::Fill, 12.0 );
|
||
let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas );
|
||
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
// Fill + margin = original rect inset on every side.
|
||
assert_eq!( child_rect, rect( 12.0, 12.0, 376.0, 276.0 ) );
|
||
}
|
||
|
||
#[ test ]
|
||
fn stack_margin_pushes_end_anchored_child_inward()
|
||
{
|
||
let canvas = make_canvas();
|
||
let child = spacer().width( 50.0 ).height( 50.0 );
|
||
let s = stack::<()>()
|
||
.push_aligned_margin( child, HAlign::End, VAlign::Top, 8.0 );
|
||
let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas );
|
||
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
// 400 - 50 - 8 = 342 from left edge; y starts at 8.
|
||
assert_eq!( child_rect.x, 342.0 );
|
||
assert_eq!( child_rect.y, 8.0 );
|
||
}
|
||
|
||
#[ test ]
|
||
fn stack_margin_larger_than_half_rect_clamps_inner_to_zero()
|
||
{
|
||
let canvas = make_canvas();
|
||
let child = spacer().width( 10.0 ).height( 10.0 );
|
||
let s = stack::<()>()
|
||
.push_aligned_margin( child, HAlign::Fill, VAlign::Fill, 200.0 );
|
||
// Stack rect is 100×100; margin 200 → inner is clamped to (0, 0).
|
||
let layout = s.layout( rect( 0.0, 0.0, 100.0, 100.0 ), &canvas );
|
||
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
assert_eq!( child_rect.width, 0.0 );
|
||
assert_eq!( child_rect.height, 0.0 );
|
||
}
|
||
|
||
// ── Stack: translation ────────────────────────────────────────────────────────
|
||
|
||
#[ test ]
|
||
fn stack_translation_offsets_child_after_alignment()
|
||
{
|
||
let canvas = make_canvas();
|
||
let child = spacer().width( 40.0 ).height( 30.0 );
|
||
let s = stack::<()>()
|
||
.push_translated( child, HAlign::Start, VAlign::Top, 5.0, 7.0 );
|
||
let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas );
|
||
|
||
let ( child_rect, _ ) = layout[ 0 ];
|
||
assert_eq!( child_rect.x, 5.0 );
|
||
assert_eq!( child_rect.y, 7.0 );
|
||
}
|
||
|
||
// ── Stack: order ─────────────────────────────────────────────────────────────
|
||
|
||
#[ test ]
|
||
fn stack_layout_preserves_child_order()
|
||
{
|
||
let canvas = make_canvas();
|
||
let s = stack::<()>()
|
||
.push( spacer().width( 10.0 ).height( 10.0 ) )
|
||
.push( spacer().width( 20.0 ).height( 20.0 ) )
|
||
.push( spacer().width( 30.0 ).height( 30.0 ) );
|
||
let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas );
|
||
|
||
assert_eq!( layout.len(), 3 );
|
||
assert_eq!( layout[ 0 ].1, 0 );
|
||
assert_eq!( layout[ 1 ].1, 1 );
|
||
assert_eq!( layout[ 2 ].1, 2 );
|
||
}
|
||
|
||
// ── Stack: preferred_size ─────────────────────────────────────────────────────
|
||
|
||
#[ test ]
|
||
fn stack_preferred_size_returns_max_child_height()
|
||
{
|
||
let canvas = make_canvas();
|
||
let s = stack::<()>()
|
||
.push( spacer().width( 40.0 ).height( 30.0 ) )
|
||
.push( spacer().width( 60.0 ).height( 80.0 ) );
|
||
let ( w, h ) = s.preferred_size( 400.0, &canvas );
|
||
// Stack always reports `max_width` for width, and the tallest child for
|
||
// height — Stack is meant for overlaying, not flow.
|
||
assert_eq!( w, 400.0 );
|
||
assert_eq!( h, 80.0 );
|
||
}
|
||
|
||
#[ test ]
|
||
fn stack_empty_preferred_size_height_is_zero()
|
||
{
|
||
let canvas = make_canvas();
|
||
let s: ltk::Stack<()> = stack();
|
||
let ( _, h ) = s.preferred_size( 400.0, &canvas );
|
||
assert_eq!( h, 0.0 );
|
||
}
|
||
|
||
// ── Spacer: builder semantics ─────────────────────────────────────────────────
|
||
|
||
// Builder-field storage (weight / fixed dimensions) is covered by internal
|
||
// unit tests in `src/layout/spacer.rs`; here we exercise only the public
|
||
// observable — `preferred_size` — which reflects those fields.
|
||
|
||
#[ test ]
|
||
fn spacer_default_has_no_fixed_size()
|
||
{
|
||
let canvas = make_canvas();
|
||
assert_eq!( spacer().preferred_size( &canvas ), ( 0.0, 0.0 ) );
|
||
}
|
||
|
||
#[ test ]
|
||
fn spacer_height_pins_vertical_axis_only()
|
||
{
|
||
let canvas = make_canvas();
|
||
assert_eq!( spacer().height( 24.0 ).preferred_size( &canvas ), ( 0.0, 24.0 ) );
|
||
}
|
||
|
||
#[ test ]
|
||
fn spacer_width_pins_horizontal_axis_only()
|
||
{
|
||
let canvas = make_canvas();
|
||
assert_eq!( spacer().width( 12.0 ).preferred_size( &canvas ), ( 12.0, 0.0 ) );
|
||
}
|
||
|
||
// ── Spacer in Column: leftover distribution ───────────────────────────────────
|
||
|
||
// These use `column().layout(...)` directly (Column re-exports its `layout()`
|
||
// method as `pub`). Fixed-height children consume known space; flexible spacers
|
||
// must absorb the rest.
|
||
|
||
#[ test ]
|
||
fn flexible_spacer_in_column_consumes_leftover_height()
|
||
{
|
||
use ltk::column;
|
||
|
||
let canvas = make_canvas();
|
||
let col = column::<()>()
|
||
.padding( 0.0 )
|
||
.spacing( 0.0 )
|
||
.push( spacer().width( 50.0 ).height( 100.0 ) )
|
||
.push( spacer() ) // flexible
|
||
.push( spacer().width( 50.0 ).height( 100.0 ) );
|
||
|
||
let layout = col.layout( rect( 0.0, 0.0, 200.0, 500.0 ), &canvas );
|
||
assert_eq!( layout.len(), 3 );
|
||
|
||
// Two fixed children of 100 each + one spacer absorbing the leftover 300.
|
||
assert!( ( layout[ 0 ].0.height - 100.0 ).abs() < 1e-3 );
|
||
assert!( ( layout[ 1 ].0.height - 300.0 ).abs() < 1e-3 );
|
||
assert!( ( layout[ 2 ].0.height - 100.0 ).abs() < 1e-3 );
|
||
}
|
||
|
||
#[ test ]
|
||
fn weighted_spacers_split_leftover_proportionally()
|
||
{
|
||
use ltk::column;
|
||
|
||
let canvas = make_canvas();
|
||
let col = column::<()>()
|
||
.padding( 0.0 )
|
||
.spacing( 0.0 )
|
||
.push( spacer().weight( 1 ) )
|
||
.push( spacer().weight( 3 ) );
|
||
|
||
let layout = col.layout( rect( 0.0, 0.0, 200.0, 400.0 ), &canvas );
|
||
// Total weight 4; leftover 400; weight 1 → 100, weight 3 → 300.
|
||
assert!( ( layout[ 0 ].0.height - 100.0 ).abs() < 1e-3 );
|
||
assert!( ( layout[ 1 ].0.height - 300.0 ).abs() < 1e-3 );
|
||
}
|
||
|
||
#[ test ]
|
||
fn fixed_height_spacer_does_not_consume_leftover()
|
||
{
|
||
use ltk::column;
|
||
|
||
let canvas = make_canvas();
|
||
let col = column::<()>()
|
||
.padding( 0.0 )
|
||
.spacing( 0.0 )
|
||
.push( spacer().height( 50.0 ) ) // fixed — keeps its 50 px
|
||
.push( spacer() ); // flexible — absorbs the rest
|
||
|
||
let layout = col.layout( rect( 0.0, 0.0, 200.0, 500.0 ), &canvas );
|
||
assert!( ( layout[ 0 ].0.height - 50.0 ).abs() < 1e-3 );
|
||
assert!( ( layout[ 1 ].0.height - 450.0 ).abs() < 1e-3 );
|
||
}
|