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:
@@ -68,6 +68,10 @@ pub struct GestureState<Msg: Clone>
|
||||
/// Index of the Scroll viewport that owns the current gesture, if
|
||||
/// the press landed inside one.
|
||||
pub scrolling_widget: Option<( usize, crate::widget::scroll::ScrollAxis )>,
|
||||
/// Scroll viewports containing the press, innermost first.
|
||||
pub scroll_candidates: Vec<( usize, crate::widget::scroll::ScrollAxis )>,
|
||||
/// `true` once the first 8 px of motion has pinned `scrolling_widget` to a definite axis.
|
||||
pub scroll_locked: bool,
|
||||
/// Scroll-viewport drag exceeded the 8 px start tolerance — the
|
||||
/// release will be consumed as a scroll instead of a tap.
|
||||
pub scroll_drag_started: bool,
|
||||
@@ -136,6 +140,8 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
start: None,
|
||||
pressed_idx: None,
|
||||
scrolling_widget: None,
|
||||
scroll_candidates: Vec::new(),
|
||||
scroll_locked: false,
|
||||
scroll_drag_started: false,
|
||||
horizontal_drag_started: false,
|
||||
vertical_drag_started: false,
|
||||
@@ -175,9 +181,12 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
}).unwrap_or( ( None, None ) );
|
||||
|
||||
self.start = Some( pos );
|
||||
self.scrolling_widget = scroll_rects.iter().rev()
|
||||
.find( |( r, _, _ )| r.contains( pos ) )
|
||||
.map( |( _, idx, ax )| ( *idx, *ax ) );
|
||||
self.scroll_candidates = scroll_rects.iter()
|
||||
.filter( |( r, _, _ )| r.contains( pos ) )
|
||||
.map( |( _, idx, ax )| ( *idx, *ax ) )
|
||||
.collect();
|
||||
self.scrolling_widget = self.scroll_candidates.first().copied();
|
||||
self.scroll_locked = false;
|
||||
self.scroll_drag_started = false;
|
||||
self.horizontal_drag_started = false;
|
||||
self.vertical_drag_started = false;
|
||||
@@ -289,19 +298,30 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
return MoveOutcome::Idle;
|
||||
}
|
||||
|
||||
// Scroll viewport drag: mutate offset in place and advance the
|
||||
// gesture origin so the next delta is frame-to-frame, not
|
||||
// press-to-now (otherwise the first 8 px trip the threshold
|
||||
// and the entire scroll gets absorbed into one delta). Both
|
||||
// axes are routed independently; an axis the viewport does not
|
||||
// allow is just ignored (delta still consumed by the gesture,
|
||||
// so the swipe handler does not fight the scroll for it).
|
||||
if let Some( ( scroll_idx, axis ) ) = self.scrolling_widget
|
||||
if self.scrolling_widget.is_some()
|
||||
{
|
||||
if let Some( start ) = self.start
|
||||
{
|
||||
let dx = pos.x - start.x;
|
||||
let dy = pos.y - start.y;
|
||||
let dx = pos.x - start.x;
|
||||
let dy = pos.y - start.y;
|
||||
|
||||
if !self.scroll_locked
|
||||
{
|
||||
if dx.abs() <= 8.0 && dy.abs() <= 8.0
|
||||
{
|
||||
return MoveOutcome::Idle;
|
||||
}
|
||||
let prefer_x = dx.abs() > dy.abs();
|
||||
if let Some( c ) = self.scroll_candidates.iter()
|
||||
.find( |( _, ax )| if prefer_x { ax.allows_x() } else { ax.allows_y() } )
|
||||
.copied()
|
||||
{
|
||||
self.scrolling_widget = Some( c );
|
||||
}
|
||||
self.scroll_locked = true;
|
||||
}
|
||||
|
||||
let ( scroll_idx, axis ) = self.scrolling_widget.unwrap();
|
||||
let entry = scroll_offsets.entry( scroll_idx ).or_insert( ( 0.0, 0.0 ) );
|
||||
if axis.allows_x() { entry.0 = ( entry.0 - dx ).max( 0.0 ); }
|
||||
if axis.allows_y() { entry.1 = ( entry.1 - dy ).max( 0.0 ); }
|
||||
|
||||
@@ -33,7 +33,7 @@ impl<A: App> AppData<A>
|
||||
let scroll_hit =
|
||||
{
|
||||
let ss = self.surface( focus );
|
||||
ss.scroll_rects.iter().rev()
|
||||
ss.scroll_rects.iter()
|
||||
.find( |( r, _, _ )| r.contains( pos ) )
|
||||
.map( |( _, idx, ax )| ( *idx, *ax ) )
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user