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.
106 lines
2.8 KiB
Rust
106 lines
2.8 KiB
Rust
use std::io::Read;
|
|
use std::path::{ Path, PathBuf };
|
|
|
|
/// Every shape `ltk::CursorShape` can request, by its CSS / freedesktop
|
|
/// cursor name. The default theme must ship an XCursor file (or alias
|
|
/// symlink) for each so the compositor never has to fall back. Keep in
|
|
/// sync with the `CursorShape` enum in `src/types.rs`.
|
|
const CURSOR_NAMES: &[ &str ] =
|
|
&[
|
|
"default", "context-menu", "help", "pointer", "progress", "wait",
|
|
"cell", "crosshair", "text", "vertical-text", "alias", "copy", "move",
|
|
"no-drop", "not-allowed", "grab", "grabbing", "e-resize", "n-resize",
|
|
"ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize",
|
|
"w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize",
|
|
"col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out",
|
|
];
|
|
|
|
fn theme_dir() -> PathBuf
|
|
{
|
|
PathBuf::from( env!( "CARGO_MANIFEST_DIR" ) ).join( "themes/default" )
|
|
}
|
|
|
|
fn cursors_dir() -> PathBuf
|
|
{
|
|
theme_dir().join( "cursors" )
|
|
}
|
|
|
|
/// True if `path` (following symlinks) is a binary XCursor file, i.e. it
|
|
/// opens and starts with the four magic bytes `Xcur`.
|
|
fn is_xcursor( path: &Path ) -> bool
|
|
{
|
|
let mut file = match std::fs::File::open( path )
|
|
{
|
|
Ok( f ) => f,
|
|
Err( _ ) => return false,
|
|
};
|
|
let mut magic = [ 0u8; 4 ];
|
|
file.read_exact( &mut magic ).is_ok() && &magic == b"Xcur"
|
|
}
|
|
|
|
#[ test ]
|
|
fn cursor_theme_manifest_present()
|
|
{
|
|
let manifest = theme_dir().join( "cursor.theme" );
|
|
let body = std::fs::read_to_string( &manifest )
|
|
.unwrap_or_else( |e| panic!( "reading {}: {e}", manifest.display() ) );
|
|
assert!
|
|
(
|
|
body.contains( "[Icon Theme]" ),
|
|
"cursor.theme is missing the [Icon Theme] header",
|
|
);
|
|
}
|
|
|
|
#[ test ]
|
|
fn every_cursor_shape_has_a_valid_xcursor_file()
|
|
{
|
|
let dir = cursors_dir();
|
|
let missing: Vec<&str> = CURSOR_NAMES
|
|
.iter()
|
|
.copied()
|
|
.filter( |name| !is_xcursor( &dir.join( name ) ) )
|
|
.collect();
|
|
|
|
assert!
|
|
(
|
|
missing.is_empty(),
|
|
"default theme has no valid XCursor file for: {missing:?}",
|
|
);
|
|
}
|
|
|
|
/// Packaging (`dh_install`) can in principle drop or break the alias
|
|
/// symlinks; a dangling link would make a cursor name unresolvable at
|
|
/// runtime. Every entry must resolve to an existing file.
|
|
#[ test ]
|
|
fn no_dangling_entries()
|
|
{
|
|
let dir = cursors_dir();
|
|
let entries = std::fs::read_dir( &dir )
|
|
.unwrap_or_else( |e| panic!( "reading {}: {e}", dir.display() ) );
|
|
|
|
let mut dangling = Vec::new();
|
|
for entry in entries
|
|
{
|
|
let path = entry.expect( "dir entry" ).path();
|
|
// `metadata` follows symlinks, so a broken link is an Err here.
|
|
if std::fs::metadata( &path ).is_err()
|
|
{
|
|
dangling.push( path );
|
|
}
|
|
}
|
|
|
|
assert!( dangling.is_empty(), "dangling cursor entries: {dangling:?}" );
|
|
}
|
|
|
|
/// Guard against the name list above drifting from the enum it mirrors.
|
|
#[ test ]
|
|
fn cursor_name_list_matches_shape_count()
|
|
{
|
|
assert_eq!
|
|
(
|
|
CURSOR_NAMES.len(),
|
|
34,
|
|
"CURSOR_NAMES is out of sync with ltk::CursorShape (34 variants)",
|
|
);
|
|
}
|