ltk: responsive padding/spacing and scrolling, expanded theme palette, and bundled Adwaita cursors
A mixed pass over the default theme and the layout/input core, plus the toolkit's own cursor set. Grouped by area below. == Responsive sizing == Add `Length::dp( px )` — a "design pixel". It interprets `px` against a configurable reference vmin (default 412 px, the eydos mobile reference width) and returns `Vmin( px / reference * 100 ).clamp( px * 0.7, px * 1.5 )`, so a value authored against a mock-up scales with the surface without collapsing on tiny screens or ballooning on a 4K desktop. The reference is process-global, set via `set_design_reference()` and read via `design_reference()` (stored as f32 bits in an AtomicU32); both are re-exported from `lib.rs`. Make container and grid insets relative. `Container`'s four padding fields become `Length` instead of `f32`; every setter (`padding`, `padding_h`, `padding_v`, `padding_top`/`right`/`bottom`/`left`) now takes `impl Into<Length>`, so existing `f32` call sites keep compiling via the `From<f32>` shim. The values are resolved against the viewport in `Container::preferred_size` and in the container draw path (`draw/layout.rs`). `WrapGrid`'s `spacing_x`, `spacing_y` and `padding` get the same treatment, with a `resolved( canvas )` helper funnelling the per-frame resolution and `grid()` seeding `Length::px` defaults. Container tests now compare against `Length::px( … )`. == Scrolling == `Scroll::preferred_size` is now axis-aware. A horizontal-only scroll reports its child's natural height rather than claiming all remaining vertical space, so it no longer steals Y from its siblings when it sits inside a `Column`; vertical and both-axis scrolls keep the spacer-like `( max_width, 0.0 )`. `Column`'s space-distribution correspondingly treats a `Scroll` as a vertical space-claimer only when its axis allows Y. Disambiguate nested scroll viewports by direction. On press the gesture state now collects every scroll viewport under the point (`scroll_candidates`, innermost first) instead of committing to one; on the first 8 px of motion it locks onto the candidate whose axis matches the dominant direction (`scroll_locked`), so a horizontal scroller nested inside a vertical list no longer grabs the wrong axis. The pointer scroll hit test is aligned to the same innermost-first ordering. == Theme palette == `themes/default/theme.json` gains named colours (green / green-deep, yellow, orange / orange-deep, pink / pink-soft, sky-deep, error / error-soft, neutral-tertiary) and new semantic slots in both light and dark modes: `danger`, `text-tertiary`, `accept`, `chip` / `chip-active` / `chip-active-fg`, and `avatar-1` … `avatar-9`. == Cursors == Bundle GNOME's Adwaita cursor theme — the cursors GNOME Shell uses — into `themes/default/cursors/` so a Wayland compositor can draw consistent, complete pointers for ltk applications without the toolkit rasterising cursors itself and without depending on adwaita-icon-theme being installed on the target. The cursors are copied verbatim in XCursor binary format: 35 image files, one per CSS/freedesktop cursor name (default, text, pointer, *-resize, …), plus 27 customary X11 alias symlinks (arrow → default, hand2 → pointer, …); a sibling `cursor.theme` makes the tree a valid XCursor theme. The existing `ltk-theme-default.install` copies `themes/default` recursively, so the directory ships with no packaging change. Applications keep declaring a `CursorShape` per widget over `wp_cursor_shape_v1`; the compositor resolves it against the active theme's `cursors/` directory by name, and the set covers all 34 `CursorShape` variants. Document the set in `themes/default/cursors/README.md` (what it is, the XCursor layout, the full shape list, how the compositor consumes it, guidance for forks) and `themes/default/cursors/LICENSE.md` (attribution and licence options, modelled on the icons catalogue LICENSE). `lib.rs` lists the cursors in its third-party-assets section. Close out licensing in `debian/copyright`: a `Files: themes/default/cursors/*` paragraph records the upstream dual offer (CC-BY-SA-3.0 or LGPL-3, and CC-BY-SA-4.0 for the newer assets) attributed to the GNOME Project, with standalone CC-BY-SA-3.0, CC-BY-SA-4.0 and LGPL-3 paragraphs (summary-plus-canonical-URL for the CC licences, matching the existing CC-BY-4.0 entry; LGPL-3 referencing /usr/share/common-licenses/LGPL-3). The files are unmodified from upstream, so there is nothing to declare under the ShareAlike "indicate if changes were made" clause. Add `tests/cursor_assets.rs`: every `CursorShape` name resolves to a valid XCursor file (Xcur magic, following symlinks), `cursor.theme` is present, no entry is a dangling symlink, and the expected-name list stays in sync with the enum's 34 variants.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use crate::render::Canvas;
|
||||
use crate::types::Rect;
|
||||
use crate::types::{ Length, Rect };
|
||||
use crate::widget::Element;
|
||||
|
||||
/// A grid layout that wraps children into rows of a fixed column count.
|
||||
@@ -35,12 +35,12 @@ pub struct WrapGrid<Msg: Clone>
|
||||
pub children: Vec<Element<Msg>>,
|
||||
/// Number of columns per row.
|
||||
pub columns: usize,
|
||||
/// Horizontal gap between cells (pixels).
|
||||
pub spacing_x: f32,
|
||||
/// Vertical gap between rows (pixels).
|
||||
pub spacing_y: f32,
|
||||
/// Padding on all sides (pixels).
|
||||
pub padding: f32,
|
||||
/// Horizontal gap between cells.
|
||||
pub spacing_x: Length,
|
||||
/// Vertical gap between rows.
|
||||
pub spacing_y: Length,
|
||||
/// Padding on all sides.
|
||||
pub padding: Length,
|
||||
/// When `true`, a partial last row is centred horizontally within
|
||||
/// the grid's content rect instead of being left-aligned.
|
||||
pub centre_last_row: bool,
|
||||
@@ -56,31 +56,32 @@ impl<Msg: Clone> WrapGrid<Msg>
|
||||
}
|
||||
|
||||
/// Set both horizontal and vertical gap between cells (default 8.0).
|
||||
pub fn spacing( mut self, s: f32 ) -> Self
|
||||
pub fn spacing( mut self, s: impl Into<Length> ) -> Self
|
||||
{
|
||||
let s = s.into();
|
||||
self.spacing_x = s;
|
||||
self.spacing_y = s;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set only the horizontal gap between cells; leaves vertical spacing untouched.
|
||||
pub fn spacing_x( mut self, s: f32 ) -> Self
|
||||
pub fn spacing_x( mut self, s: impl Into<Length> ) -> Self
|
||||
{
|
||||
self.spacing_x = s;
|
||||
self.spacing_x = s.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set only the vertical gap between rows; leaves horizontal spacing untouched.
|
||||
pub fn spacing_y( mut self, s: f32 ) -> Self
|
||||
pub fn spacing_y( mut self, s: impl Into<Length> ) -> Self
|
||||
{
|
||||
self.spacing_y = s;
|
||||
self.spacing_y = s.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the padding on all sides (default 0.0).
|
||||
pub fn padding( mut self, p: f32 ) -> Self
|
||||
pub fn padding( mut self, p: impl Into<Length> ) -> Self
|
||||
{
|
||||
self.padding = p;
|
||||
self.padding = p.into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -92,6 +93,17 @@ impl<Msg: Clone> WrapGrid<Msg>
|
||||
self
|
||||
}
|
||||
|
||||
fn resolved( &self, canvas: &Canvas ) -> ( f32, f32, f32 )
|
||||
{
|
||||
let vp = canvas.viewport_logical();
|
||||
let em = Length::EM_BASE_DEFAULT;
|
||||
(
|
||||
self.spacing_x.resolve( vp, em ),
|
||||
self.spacing_y.resolve( vp, em ),
|
||||
self.padding.resolve( vp, em ),
|
||||
)
|
||||
}
|
||||
|
||||
/// Compute the preferred size given an available width.
|
||||
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
|
||||
{
|
||||
@@ -99,12 +111,13 @@ impl<Msg: Clone> WrapGrid<Msg>
|
||||
{
|
||||
return ( max_width, 0.0 );
|
||||
}
|
||||
let ( sx, sy, pad ) = self.resolved( canvas );
|
||||
let cols = self.columns;
|
||||
let inner_w = (max_width - self.padding * 2.0).max( 0.0 );
|
||||
let cell_w = (inner_w - self.spacing_x * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
|
||||
let inner_w = (max_width - pad * 2.0).max( 0.0 );
|
||||
let cell_w = (inner_w - sx * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
|
||||
let row_count = (self.children.len() + cols - 1) / cols;
|
||||
|
||||
let mut total_h = self.padding * 2.0;
|
||||
let mut total_h = pad * 2.0;
|
||||
for row in 0..row_count
|
||||
{
|
||||
let start = row * cols;
|
||||
@@ -114,7 +127,7 @@ impl<Msg: Clone> WrapGrid<Msg>
|
||||
.map( |c| c.preferred_size( cell_w, canvas ).1 )
|
||||
.fold( 0.0_f32, f32::max );
|
||||
total_h += row_h;
|
||||
if row + 1 < row_count { total_h += self.spacing_y; }
|
||||
if row + 1 < row_count { total_h += sy; }
|
||||
}
|
||||
( max_width, total_h )
|
||||
}
|
||||
@@ -126,11 +139,12 @@ impl<Msg: Clone> WrapGrid<Msg>
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
let ( sx, sy, pad ) = self.resolved( canvas );
|
||||
let cols = self.columns;
|
||||
let inner_w = (rect.width - self.padding * 2.0).max( 0.0 );
|
||||
let cell_w = (inner_w - self.spacing_x * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
|
||||
let x0 = rect.x + self.padding;
|
||||
let mut y = rect.y + self.padding;
|
||||
let inner_w = (rect.width - pad * 2.0).max( 0.0 );
|
||||
let cell_w = (inner_w - sx * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
|
||||
let x0 = rect.x + pad;
|
||||
let mut y = rect.y + pad;
|
||||
|
||||
let row_count = (self.children.len() + cols - 1) / cols;
|
||||
let mut out = Vec::with_capacity( self.children.len() );
|
||||
@@ -148,16 +162,16 @@ impl<Msg: Clone> WrapGrid<Msg>
|
||||
let row_offset = if self.centre_last_row && items_in_row < cols
|
||||
{
|
||||
let missing = (cols - items_in_row) as f32;
|
||||
missing * (cell_w + self.spacing_x) / 2.0
|
||||
missing * (cell_w + sx) / 2.0
|
||||
} else { 0.0 };
|
||||
|
||||
for col in 0..items_in_row
|
||||
{
|
||||
let x = x0 + row_offset + col as f32 * (cell_w + self.spacing_x);
|
||||
let x = x0 + row_offset + col as f32 * (cell_w + sx);
|
||||
let crect = Rect { x, y, width: cell_w, height: row_h };
|
||||
out.push( ( crect, start + col ) );
|
||||
}
|
||||
y += row_h + self.spacing_y;
|
||||
y += row_h + sy;
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -396,9 +410,9 @@ pub fn grid<Msg: Clone>( columns: usize ) -> WrapGrid<Msg>
|
||||
{
|
||||
children: Vec::new(),
|
||||
columns,
|
||||
spacing_x: 8.0,
|
||||
spacing_y: 8.0,
|
||||
padding: 0.0,
|
||||
spacing_x: Length::px( 8.0 ),
|
||||
spacing_y: Length::px( 8.0 ),
|
||||
padding: Length::px( 0.0 ),
|
||||
centre_last_row: false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user