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:
58
debian/copyright
vendored
58
debian/copyright
vendored
@@ -62,6 +62,27 @@ Comment:
|
|||||||
sibling `Sora-LICENSE.txt`. Upstream:
|
sibling `Sora-LICENSE.txt`. Upstream:
|
||||||
https://github.com/sora-xor/sora-font
|
https://github.com/sora-xor/sora-font
|
||||||
|
|
||||||
|
Files: themes/default/cursors/*
|
||||||
|
Copyright: 2002-2014 The GNOME Project and the Adwaita icon theme authors
|
||||||
|
License: CC-BY-SA-3.0 or LGPL-3, and CC-BY-SA-4.0
|
||||||
|
Comment:
|
||||||
|
Pointer cursors under themes/default/cursors/ are GNOME's *Adwaita*
|
||||||
|
cursor theme — the cursors GNOME Shell ships — bundled verbatim from
|
||||||
|
the adwaita-icon-theme distribution so the default ltk theme is
|
||||||
|
self-contained (it does not depend on adwaita-icon-theme being
|
||||||
|
installed). The files are in XCursor binary format, one per CSS cursor
|
||||||
|
name (default, text, pointer, *-resize, …) plus the customary X11 alias
|
||||||
|
symlinks (arrow → default, hand2 → pointer, …); the sibling
|
||||||
|
cursor.theme makes the tree a valid XCursor theme. They are unmodified
|
||||||
|
from upstream. A Wayland compositor draws them in response to
|
||||||
|
wp_cursor_shape_v1, so ltk applications get them without bundling their
|
||||||
|
own. Upstream offers the artwork under the disjunction in the License
|
||||||
|
field; redistributing under any one of the options satisfies the
|
||||||
|
licence. Attribution shorthand: "Cursors by the GNOME Project". The
|
||||||
|
full functional and licence notes live in
|
||||||
|
themes/default/cursors/README.md and themes/default/cursors/LICENSE.md.
|
||||||
|
Upstream: https://gitlab.gnome.org/GNOME/adwaita-icon-theme
|
||||||
|
|
||||||
License: LGPL-2.1-only
|
License: LGPL-2.1-only
|
||||||
This library is free software; you can redistribute it and/or modify it
|
This library is free software; you can redistribute it and/or modify it
|
||||||
under the terms of the GNU Lesser General Public License as published by
|
under the terms of the GNU Lesser General Public License as published by
|
||||||
@@ -196,3 +217,40 @@ License: CC-BY-4.0
|
|||||||
The licence text above is a summary; the canonical legal text at the URL
|
The licence text above is a summary; the canonical legal text at the URL
|
||||||
is what governs use, distribution and modification of the Licensed
|
is what governs use, distribution and modification of the Licensed
|
||||||
Material referenced under `Files: themes/default/icons/catalogue/*`.
|
Material referenced under `Files: themes/default/icons/catalogue/*`.
|
||||||
|
|
||||||
|
License: CC-BY-SA-3.0
|
||||||
|
Creative Commons Attribution-ShareAlike 3.0 (CC BY-SA 3.0).
|
||||||
|
.
|
||||||
|
You are free to share and adapt the material for any purpose, even
|
||||||
|
commercially, provided you give appropriate credit, provide a link to
|
||||||
|
the licence, indicate if changes were made, and distribute your
|
||||||
|
contributions under the same licence as the original (ShareAlike).
|
||||||
|
.
|
||||||
|
The canonical legal text is at:
|
||||||
|
https://creativecommons.org/licenses/by-sa/3.0/legalcode
|
||||||
|
.
|
||||||
|
A human-readable summary (which is not a substitute for the licence) is
|
||||||
|
available at:
|
||||||
|
https://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
.
|
||||||
|
The summary above governs the Adwaita cursor files referenced under
|
||||||
|
`Files: themes/default/cursors/*` when distributed under the 3.0 option.
|
||||||
|
|
||||||
|
License: CC-BY-SA-4.0
|
||||||
|
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0).
|
||||||
|
.
|
||||||
|
You are free to share and adapt the material for any purpose, even
|
||||||
|
commercially, under the same Attribution and ShareAlike terms as the
|
||||||
|
CC BY-SA 3.0 paragraph above.
|
||||||
|
.
|
||||||
|
The canonical legal text is at:
|
||||||
|
https://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
.
|
||||||
|
A human-readable summary (which is not a substitute for the licence) is
|
||||||
|
available at:
|
||||||
|
https://creativecommons.org/licenses/by-sa/4.0/
|
||||||
|
|
||||||
|
License: LGPL-3
|
||||||
|
The Adwaita cursors are alternatively available under the GNU Lesser
|
||||||
|
General Public License version 3. On Debian systems the complete text
|
||||||
|
of the LGPL version 3 is in `/usr/share/common-licenses/LGPL-3`.
|
||||||
|
|||||||
@@ -195,12 +195,18 @@ pub( crate ) fn layout_and_draw<Msg: Clone>(
|
|||||||
{
|
{
|
||||||
canvas.stroke_rect( rect, color, width, c.corners );
|
canvas.stroke_rect( rect, color, width, c.corners );
|
||||||
}
|
}
|
||||||
|
let vp = canvas.viewport_logical();
|
||||||
|
let em = crate::types::Length::EM_BASE_DEFAULT;
|
||||||
|
let pad_l = c.pad_left.resolve( vp, em );
|
||||||
|
let pad_r = c.pad_right.resolve( vp, em );
|
||||||
|
let pad_t = c.pad_top.resolve( vp, em );
|
||||||
|
let pad_b = c.pad_bottom.resolve( vp, em );
|
||||||
let inner = crate::types::Rect
|
let inner = crate::types::Rect
|
||||||
{
|
{
|
||||||
x: rect.x + c.pad_left,
|
x: rect.x + pad_l,
|
||||||
y: rect.y + c.pad_top,
|
y: rect.y + pad_t,
|
||||||
width: ( rect.width - c.pad_left - c.pad_right ).max( 0.0 ),
|
width: ( rect.width - pad_l - pad_r ).max( 0.0 ),
|
||||||
height: ( rect.height - c.pad_top - c.pad_bottom ).max( 0.0 ),
|
height: ( rect.height - pad_t - pad_b ).max( 0.0 ),
|
||||||
};
|
};
|
||||||
let result = layout_and_draw::<Msg>( c.child.as_ref(), canvas, inner, ctx, flat_idx );
|
let result = layout_and_draw::<Msg>( c.child.as_ref(), canvas, inner, ctx, flat_idx );
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ pub struct GestureState<Msg: Clone>
|
|||||||
/// Index of the Scroll viewport that owns the current gesture, if
|
/// Index of the Scroll viewport that owns the current gesture, if
|
||||||
/// the press landed inside one.
|
/// the press landed inside one.
|
||||||
pub scrolling_widget: Option<( usize, crate::widget::scroll::ScrollAxis )>,
|
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
|
/// Scroll-viewport drag exceeded the 8 px start tolerance — the
|
||||||
/// release will be consumed as a scroll instead of a tap.
|
/// release will be consumed as a scroll instead of a tap.
|
||||||
pub scroll_drag_started: bool,
|
pub scroll_drag_started: bool,
|
||||||
@@ -136,6 +140,8 @@ impl<Msg: Clone> GestureState<Msg>
|
|||||||
start: None,
|
start: None,
|
||||||
pressed_idx: None,
|
pressed_idx: None,
|
||||||
scrolling_widget: None,
|
scrolling_widget: None,
|
||||||
|
scroll_candidates: Vec::new(),
|
||||||
|
scroll_locked: false,
|
||||||
scroll_drag_started: false,
|
scroll_drag_started: false,
|
||||||
horizontal_drag_started: false,
|
horizontal_drag_started: false,
|
||||||
vertical_drag_started: false,
|
vertical_drag_started: false,
|
||||||
@@ -175,9 +181,12 @@ impl<Msg: Clone> GestureState<Msg>
|
|||||||
}).unwrap_or( ( None, None ) );
|
}).unwrap_or( ( None, None ) );
|
||||||
|
|
||||||
self.start = Some( pos );
|
self.start = Some( pos );
|
||||||
self.scrolling_widget = scroll_rects.iter().rev()
|
self.scroll_candidates = scroll_rects.iter()
|
||||||
.find( |( r, _, _ )| r.contains( pos ) )
|
.filter( |( r, _, _ )| r.contains( pos ) )
|
||||||
.map( |( _, idx, ax )| ( *idx, *ax ) );
|
.map( |( _, idx, ax )| ( *idx, *ax ) )
|
||||||
|
.collect();
|
||||||
|
self.scrolling_widget = self.scroll_candidates.first().copied();
|
||||||
|
self.scroll_locked = false;
|
||||||
self.scroll_drag_started = false;
|
self.scroll_drag_started = false;
|
||||||
self.horizontal_drag_started = false;
|
self.horizontal_drag_started = false;
|
||||||
self.vertical_drag_started = false;
|
self.vertical_drag_started = false;
|
||||||
@@ -289,19 +298,30 @@ impl<Msg: Clone> GestureState<Msg>
|
|||||||
return MoveOutcome::Idle;
|
return MoveOutcome::Idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll viewport drag: mutate offset in place and advance the
|
if self.scrolling_widget.is_some()
|
||||||
// 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 let Some( start ) = self.start
|
if let Some( start ) = self.start
|
||||||
{
|
{
|
||||||
let dx = pos.x - start.x;
|
let dx = pos.x - start.x;
|
||||||
let dy = pos.y - start.y;
|
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 ) );
|
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_x() { entry.0 = ( entry.0 - dx ).max( 0.0 ); }
|
||||||
if axis.allows_y() { entry.1 = ( entry.1 - dy ).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 scroll_hit =
|
||||||
{
|
{
|
||||||
let ss = self.surface( focus );
|
let ss = self.surface( focus );
|
||||||
ss.scroll_rects.iter().rev()
|
ss.scroll_rects.iter()
|
||||||
.find( |( r, _, _ )| r.contains( pos ) )
|
.find( |( r, _, _ )| r.contains( pos ) )
|
||||||
.map( |( _, idx, ax )| ( *idx, *ax ) )
|
.map( |( _, idx, ax )| ( *idx, *ax ) )
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -224,21 +224,19 @@ impl<Msg: Clone> Column<Msg>
|
|||||||
let pad = self.resolved_padding( canvas );
|
let pad = self.resolved_padding( canvas );
|
||||||
let spacing = self.resolved_spacing( canvas );
|
let spacing = self.resolved_spacing( canvas );
|
||||||
|
|
||||||
// Flexible spacers and Scroll widgets claim remaining vertical space.
|
|
||||||
// Fixed-height spacers behave like normal fixed-size children.
|
|
||||||
let total_weight: u32 = self.children.iter()
|
let total_weight: u32 = self.children.iter()
|
||||||
.map( |c| match c
|
.map( |c| match c
|
||||||
{
|
{
|
||||||
Element::Spacer( s ) if s.resolved_height( canvas ).is_none() => s.weight,
|
Element::Spacer( s ) if s.resolved_height( canvas ).is_none() => s.weight,
|
||||||
Element::Scroll( _ ) => 1,
|
Element::Scroll( s ) if s.axis.allows_y() => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
} )
|
} )
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
let fixed_h: f32 = self.children.iter()
|
let fixed_h: f32 = self.children.iter()
|
||||||
.map( |c|
|
.map( |c|
|
||||||
{
|
{
|
||||||
if matches!( c, Element::Scroll( _ ) )
|
if matches!( c, Element::Scroll( s ) if s.axis.allows_y() )
|
||||||
{
|
{
|
||||||
0.0
|
0.0
|
||||||
} else if let Element::Spacer( s ) = c {
|
} else if let Element::Spacer( s ) = c {
|
||||||
@@ -282,7 +280,7 @@ impl<Msg: Clone> Column<Msg>
|
|||||||
};
|
};
|
||||||
( inner_w, h )
|
( inner_w, h )
|
||||||
},
|
},
|
||||||
Element::Scroll( _ ) =>
|
Element::Scroll( s ) if s.axis.allows_y() =>
|
||||||
{
|
{
|
||||||
let h = if total_weight > 0
|
let h = if total_weight > 0
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||||
|
|
||||||
use crate::render::Canvas;
|
use crate::render::Canvas;
|
||||||
use crate::types::Rect;
|
use crate::types::{ Length, Rect };
|
||||||
use crate::widget::Element;
|
use crate::widget::Element;
|
||||||
|
|
||||||
/// A grid layout that wraps children into rows of a fixed column count.
|
/// 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>>,
|
pub children: Vec<Element<Msg>>,
|
||||||
/// Number of columns per row.
|
/// Number of columns per row.
|
||||||
pub columns: usize,
|
pub columns: usize,
|
||||||
/// Horizontal gap between cells (pixels).
|
/// Horizontal gap between cells.
|
||||||
pub spacing_x: f32,
|
pub spacing_x: Length,
|
||||||
/// Vertical gap between rows (pixels).
|
/// Vertical gap between rows.
|
||||||
pub spacing_y: f32,
|
pub spacing_y: Length,
|
||||||
/// Padding on all sides (pixels).
|
/// Padding on all sides.
|
||||||
pub padding: f32,
|
pub padding: Length,
|
||||||
/// When `true`, a partial last row is centred horizontally within
|
/// When `true`, a partial last row is centred horizontally within
|
||||||
/// the grid's content rect instead of being left-aligned.
|
/// the grid's content rect instead of being left-aligned.
|
||||||
pub centre_last_row: bool,
|
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).
|
/// 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_x = s;
|
||||||
self.spacing_y = s;
|
self.spacing_y = s;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set only the horizontal gap between cells; leaves vertical spacing untouched.
|
/// 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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set only the vertical gap between rows; leaves horizontal spacing untouched.
|
/// 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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the padding on all sides (default 0.0).
|
/// 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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +93,17 @@ impl<Msg: Clone> WrapGrid<Msg>
|
|||||||
self
|
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.
|
/// Compute the preferred size given an available width.
|
||||||
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
|
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 );
|
return ( max_width, 0.0 );
|
||||||
}
|
}
|
||||||
|
let ( sx, sy, pad ) = self.resolved( canvas );
|
||||||
let cols = self.columns;
|
let cols = self.columns;
|
||||||
let inner_w = (max_width - self.padding * 2.0).max( 0.0 );
|
let inner_w = (max_width - pad * 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 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 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
|
for row in 0..row_count
|
||||||
{
|
{
|
||||||
let start = row * cols;
|
let start = row * cols;
|
||||||
@@ -114,7 +127,7 @@ impl<Msg: Clone> WrapGrid<Msg>
|
|||||||
.map( |c| c.preferred_size( cell_w, canvas ).1 )
|
.map( |c| c.preferred_size( cell_w, canvas ).1 )
|
||||||
.fold( 0.0_f32, f32::max );
|
.fold( 0.0_f32, f32::max );
|
||||||
total_h += row_h;
|
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 )
|
( max_width, total_h )
|
||||||
}
|
}
|
||||||
@@ -126,11 +139,12 @@ impl<Msg: Clone> WrapGrid<Msg>
|
|||||||
{
|
{
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
let ( sx, sy, pad ) = self.resolved( canvas );
|
||||||
let cols = self.columns;
|
let cols = self.columns;
|
||||||
let inner_w = (rect.width - self.padding * 2.0).max( 0.0 );
|
let inner_w = (rect.width - pad * 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 cell_w = (inner_w - sx * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
|
||||||
let x0 = rect.x + self.padding;
|
let x0 = rect.x + pad;
|
||||||
let mut y = rect.y + self.padding;
|
let mut y = rect.y + pad;
|
||||||
|
|
||||||
let row_count = (self.children.len() + cols - 1) / cols;
|
let row_count = (self.children.len() + cols - 1) / cols;
|
||||||
let mut out = Vec::with_capacity( self.children.len() );
|
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 row_offset = if self.centre_last_row && items_in_row < cols
|
||||||
{
|
{
|
||||||
let missing = (cols - items_in_row) as f32;
|
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 };
|
} else { 0.0 };
|
||||||
|
|
||||||
for col in 0..items_in_row
|
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 };
|
let crect = Rect { x, y, width: cell_w, height: row_h };
|
||||||
out.push( ( crect, start + col ) );
|
out.push( ( crect, start + col ) );
|
||||||
}
|
}
|
||||||
y += row_h + self.spacing_y;
|
y += row_h + sy;
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
@@ -396,9 +410,9 @@ pub fn grid<Msg: Clone>( columns: usize ) -> WrapGrid<Msg>
|
|||||||
{
|
{
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
columns,
|
columns,
|
||||||
spacing_x: 8.0,
|
spacing_x: Length::px( 8.0 ),
|
||||||
spacing_y: 8.0,
|
spacing_y: Length::px( 8.0 ),
|
||||||
padding: 0.0,
|
padding: Length::px( 0.0 ),
|
||||||
centre_last_row: false,
|
centre_last_row: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/lib.rs
11
src/lib.rs
@@ -159,6 +159,16 @@
|
|||||||
//! - **Sora Regular** (`src/theme/fallback/Sora-Regular.otf`) — the
|
//! - **Sora Regular** (`src/theme/fallback/Sora-Regular.otf`) — the
|
||||||
//! embedded font fallback, [SIL OFL 1.1](https://scripts.sil.org/OFL),
|
//! embedded font fallback, [SIL OFL 1.1](https://scripts.sil.org/OFL),
|
||||||
//! © The Sora Project Authors, Jonathan Barnbrook, Julián Moncada.
|
//! © The Sora Project Authors, Jonathan Barnbrook, Julián Moncada.
|
||||||
|
//! - **Pointer cursors** under `themes/default/cursors/` — GNOME's
|
||||||
|
//! *Adwaita* cursor theme (the cursors GNOME Shell ships), bundled
|
||||||
|
//! verbatim, © the GNOME Project. Offered upstream under
|
||||||
|
//! [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) *or*
|
||||||
|
//! [LGPL 3](https://www.gnu.org/licenses/lgpl-3.0.html), and
|
||||||
|
//! [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) for
|
||||||
|
//! the newer assets; any one option satisfies the licence. See
|
||||||
|
//! `themes/default/cursors/README.md` (what the set is and how it is
|
||||||
|
//! used) and `themes/default/cursors/LICENSE.md` (attribution).
|
||||||
|
//! Upstream: <https://gitlab.gnome.org/GNOME/adwaita-icon-theme>.
|
||||||
//!
|
//!
|
||||||
//! The remaining artwork in the default theme (wallpapers, lockscreens,
|
//! The remaining artwork in the default theme (wallpapers, lockscreens,
|
||||||
//! launcher logo, brand-mark variants, per-application icons) is
|
//! launcher logo, brand-mark variants, per-application icons) is
|
||||||
@@ -230,6 +240,7 @@ pub use gles_render::{ BorrowedGlesTexture, GlesVersion };
|
|||||||
pub use render::is_software_render;
|
pub use render::is_software_render;
|
||||||
pub use wallpaper::{ WallpaperBundle, ImageData };
|
pub use wallpaper::{ WallpaperBundle, ImageData };
|
||||||
pub use types::{ Color, Corners, CursorShape, Length, LengthBase, Point, Rect, Size, WidgetId };
|
pub use types::{ Color, Corners, CursorShape, Length, LengthBase, Point, Rect, Size, WidgetId };
|
||||||
|
pub use types::{ design_reference, set_design_reference };
|
||||||
pub use widget::{ Element, button, icon_button, text_edit, image as img_widget, text, container };
|
pub use widget::{ Element, button, icon_button, text_edit, image as img_widget, text, container };
|
||||||
pub use widget::button::ButtonVariant;
|
pub use widget::button::ButtonVariant;
|
||||||
pub use widget::slider::{ Slider, slider, SliderAxis };
|
pub use widget::slider::{ Slider, slider, SliderAxis };
|
||||||
|
|||||||
30
src/types.rs
30
src/types.rs
@@ -24,6 +24,8 @@
|
|||||||
//! `ltk::Rect`, …) so application code rarely needs the `ltk::types::`
|
//! `ltk::Rect`, …) so application code rarely needs the `ltk::types::`
|
||||||
//! prefix.
|
//! prefix.
|
||||||
|
|
||||||
|
use std::sync::atomic::{ AtomicU32, Ordering };
|
||||||
|
|
||||||
/// An RGBA color with floating-point channels in the range `[0.0, 1.0]`.
|
/// An RGBA color with floating-point channels in the range `[0.0, 1.0]`.
|
||||||
#[ derive( Debug, Clone, Copy, PartialEq ) ]
|
#[ derive( Debug, Clone, Copy, PartialEq ) ]
|
||||||
pub struct Color
|
pub struct Color
|
||||||
@@ -524,6 +526,17 @@ impl Length
|
|||||||
pub const fn vmax( v: f32 ) -> Self { Self::from_base( LengthBase::Vmax( v ) ) }
|
pub const fn vmax( v: f32 ) -> Self { Self::from_base( LengthBase::Vmax( v ) ) }
|
||||||
pub const fn em( v: f32 ) -> Self { Self::from_base( LengthBase::Em( v ) ) }
|
pub const fn em( v: f32 ) -> Self { Self::from_base( LengthBase::Em( v ) ) }
|
||||||
|
|
||||||
|
/// "Design pixel": `px` interpreted at the reference vmin set via
|
||||||
|
/// [`set_design_reference`] (defaults to 412 px — the eydos mobile
|
||||||
|
/// reference width). The result is a `Vmin` value clamped to
|
||||||
|
/// `[px * 0.7, px * 1.5]`, so the layout scales with the screen
|
||||||
|
/// without collapsing on tiny surfaces or ballooning on 4K.
|
||||||
|
pub fn dp( px: f32 ) -> Self
|
||||||
|
{
|
||||||
|
let r = design_reference();
|
||||||
|
Length::vmin( px / r * 100.0 ).clamp( px * 0.7, px * 1.5 )
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve to a concrete logical-pixel value given a viewport and an
|
/// Resolve to a concrete logical-pixel value given a viewport and an
|
||||||
/// `em_base` (the root font size that `Em` is a fraction of).
|
/// `em_base` (the root font size that `Em` is a fraction of).
|
||||||
pub fn resolve( &self, viewport: ( f32, f32 ), em_base: f32 ) -> f32
|
pub fn resolve( &self, viewport: ( f32, f32 ), em_base: f32 ) -> f32
|
||||||
@@ -573,6 +586,23 @@ impl Length
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DESIGN_REFERENCE_BITS: AtomicU32 = AtomicU32::new( 412.0_f32.to_bits() );
|
||||||
|
|
||||||
|
/// Set the reference vmin width that [`Length::dp`] interprets `px` against.
|
||||||
|
/// Call once at startup (e.g. before [`crate::run`]) to align the design
|
||||||
|
/// scale to the surface mock-up the app was designed for.
|
||||||
|
pub fn set_design_reference( reference_vmin: f32 )
|
||||||
|
{
|
||||||
|
DESIGN_REFERENCE_BITS.store( reference_vmin.to_bits(), Ordering::Relaxed );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current value used by [`Length::dp`] — the px width at which `dp(n)`
|
||||||
|
/// resolves to `n` logical pixels.
|
||||||
|
pub fn design_reference() -> f32
|
||||||
|
{
|
||||||
|
f32::from_bits( DESIGN_REFERENCE_BITS.load( Ordering::Relaxed ) )
|
||||||
|
}
|
||||||
|
|
||||||
impl From<f32> for Length
|
impl From<f32> for Length
|
||||||
{
|
{
|
||||||
fn from( v: f32 ) -> Self { Length::px( v ) }
|
fn from( v: f32 ) -> Self { Length::px( v ) }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||||
|
|
||||||
use crate::theme::Paint;
|
use crate::theme::Paint;
|
||||||
use crate::types::{ Color, Corners };
|
use crate::types::{ Color, Corners, Length };
|
||||||
use crate::render::Canvas;
|
use crate::render::Canvas;
|
||||||
use super::Element;
|
use super::Element;
|
||||||
|
|
||||||
@@ -72,15 +72,16 @@ pub struct Container<Msg: Clone>
|
|||||||
/// left edge, …) without hitting the renderer with an offset
|
/// left edge, …) without hitting the renderer with an offset
|
||||||
/// trick.
|
/// trick.
|
||||||
pub corners: Corners,
|
pub corners: Corners,
|
||||||
/// Padding on the top edge in logical px — gap between the
|
/// Padding on the top edge — gap between the container's top boundary
|
||||||
/// container's top boundary and its child.
|
/// and its child. Stored as a [`Length`] so it can scale with the
|
||||||
pub pad_top: f32,
|
/// viewport via [`Length::dp`] / [`Length::vmin`].
|
||||||
/// Padding on the right edge in logical px.
|
pub pad_top: Length,
|
||||||
pub pad_right: f32,
|
/// Padding on the right edge.
|
||||||
/// Padding on the bottom edge in logical px.
|
pub pad_right: Length,
|
||||||
pub pad_bottom: f32,
|
/// Padding on the bottom edge.
|
||||||
/// Padding on the left edge in logical px.
|
pub pad_bottom: Length,
|
||||||
pub pad_left: f32,
|
/// Padding on the left edge.
|
||||||
|
pub pad_left: Length,
|
||||||
pub opacity: f32,
|
pub opacity: f32,
|
||||||
/// Optional `( color, width_px )` border stroke painted around the
|
/// Optional `( color, width_px )` border stroke painted around the
|
||||||
/// container's rounded rectangle, after the fill / surface and
|
/// container's rounded rectangle, after the fill / surface and
|
||||||
@@ -109,10 +110,10 @@ impl<Msg: Clone> Container<Msg>
|
|||||||
background: None,
|
background: None,
|
||||||
surface: None,
|
surface: None,
|
||||||
corners: Corners::ZERO,
|
corners: Corners::ZERO,
|
||||||
pad_top: 0.0,
|
pad_top: Length::px( 0.0 ),
|
||||||
pad_right: 0.0,
|
pad_right: Length::px( 0.0 ),
|
||||||
pad_bottom: 0.0,
|
pad_bottom: Length::px( 0.0 ),
|
||||||
pad_left: 0.0,
|
pad_left: Length::px( 0.0 ),
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
border: None,
|
border: None,
|
||||||
max_width: None,
|
max_width: None,
|
||||||
@@ -197,8 +198,9 @@ impl<Msg: Clone> Container<Msg>
|
|||||||
/// edges, so calling this first and then a per-edge setter is the
|
/// edges, so calling this first and then a per-edge setter is the
|
||||||
/// idiomatic way to express "uniform padding except for one
|
/// idiomatic way to express "uniform padding except for one
|
||||||
/// edge".
|
/// edge".
|
||||||
pub fn padding( mut self, p: f32 ) -> Self
|
pub fn padding( mut self, p: impl Into<Length> ) -> Self
|
||||||
{
|
{
|
||||||
|
let p = p.into();
|
||||||
self.pad_top = p;
|
self.pad_top = p;
|
||||||
self.pad_right = p;
|
self.pad_right = p;
|
||||||
self.pad_bottom = p;
|
self.pad_bottom = p;
|
||||||
@@ -207,16 +209,18 @@ impl<Msg: Clone> Container<Msg>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set horizontal padding (left + right each).
|
/// Set horizontal padding (left + right each).
|
||||||
pub fn padding_h( mut self, p: f32 ) -> Self
|
pub fn padding_h( mut self, p: impl Into<Length> ) -> Self
|
||||||
{
|
{
|
||||||
|
let p = p.into();
|
||||||
self.pad_left = p;
|
self.pad_left = p;
|
||||||
self.pad_right = p;
|
self.pad_right = p;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set vertical padding (top + bottom each).
|
/// Set vertical padding (top + bottom each).
|
||||||
pub fn padding_v( mut self, p: f32 ) -> Self
|
pub fn padding_v( mut self, p: impl Into<Length> ) -> Self
|
||||||
{
|
{
|
||||||
|
let p = p.into();
|
||||||
self.pad_top = p;
|
self.pad_top = p;
|
||||||
self.pad_bottom = p;
|
self.pad_bottom = p;
|
||||||
self
|
self
|
||||||
@@ -225,30 +229,30 @@ impl<Msg: Clone> Container<Msg>
|
|||||||
/// Set the top edge padding only. Pairs with
|
/// Set the top edge padding only. Pairs with
|
||||||
/// [`padding_bottom`](Self::padding_bottom) for asymmetric
|
/// [`padding_bottom`](Self::padding_bottom) for asymmetric
|
||||||
/// vertical insets.
|
/// vertical insets.
|
||||||
pub fn padding_top( mut self, p: f32 ) -> Self
|
pub fn padding_top( mut self, p: impl Into<Length> ) -> Self
|
||||||
{
|
{
|
||||||
self.pad_top = p;
|
self.pad_top = p.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the right edge padding only.
|
/// Set the right edge padding only.
|
||||||
pub fn padding_right( mut self, p: f32 ) -> Self
|
pub fn padding_right( mut self, p: impl Into<Length> ) -> Self
|
||||||
{
|
{
|
||||||
self.pad_right = p;
|
self.pad_right = p.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the bottom edge padding only.
|
/// Set the bottom edge padding only.
|
||||||
pub fn padding_bottom( mut self, p: f32 ) -> Self
|
pub fn padding_bottom( mut self, p: impl Into<Length> ) -> Self
|
||||||
{
|
{
|
||||||
self.pad_bottom = p;
|
self.pad_bottom = p.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the left edge padding only.
|
/// Set the left edge padding only.
|
||||||
pub fn padding_left( mut self, p: f32 ) -> Self
|
pub fn padding_left( mut self, p: impl Into<Length> ) -> Self
|
||||||
{
|
{
|
||||||
self.pad_left = p;
|
self.pad_left = p.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,9 +275,15 @@ impl<Msg: Clone> Container<Msg>
|
|||||||
/// Return the preferred `(width, height)` accounting for padding.
|
/// Return the preferred `(width, height)` accounting for padding.
|
||||||
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> ( f32, f32 )
|
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> ( f32, f32 )
|
||||||
{
|
{
|
||||||
|
let vp = canvas.viewport_logical();
|
||||||
|
let em = Length::EM_BASE_DEFAULT;
|
||||||
|
let pad_l = self.pad_left.resolve( vp, em );
|
||||||
|
let pad_r = self.pad_right.resolve( vp, em );
|
||||||
|
let pad_t = self.pad_top.resolve( vp, em );
|
||||||
|
let pad_b = self.pad_bottom.resolve( vp, em );
|
||||||
let avail = self.max_width.map( |m| max_width.min( m ) ).unwrap_or( max_width );
|
let avail = self.max_width.map( |m| max_width.min( m ) ).unwrap_or( max_width );
|
||||||
let pad_x = self.pad_left + self.pad_right;
|
let pad_x = pad_l + pad_r;
|
||||||
let pad_y = self.pad_top + self.pad_bottom;
|
let pad_y = pad_t + pad_b;
|
||||||
let inner_w = ( avail - pad_x ).max( 0.0 );
|
let inner_w = ( avail - pad_x ).max( 0.0 );
|
||||||
let ( cw, ch ) = self.child.preferred_size( inner_w, canvas );
|
let ( cw, ch ) = self.child.preferred_size( inner_w, canvas );
|
||||||
( cw + pad_x, ch + pad_y )
|
( cw + pad_x, ch + pad_y )
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::spacer::spacer;
|
use crate::layout::spacer::spacer;
|
||||||
|
use crate::types::Length;
|
||||||
|
|
||||||
#[ test ]
|
#[ test ]
|
||||||
fn default_no_background()
|
fn default_no_background()
|
||||||
@@ -15,30 +16,30 @@ fn default_no_background()
|
|||||||
fn padding_sets_all_four_sides()
|
fn padding_sets_all_four_sides()
|
||||||
{
|
{
|
||||||
let c = container::<()>( spacer() ).padding( 10.0 );
|
let c = container::<()>( spacer() ).padding( 10.0 );
|
||||||
assert_eq!( c.pad_top, 10.0 );
|
assert_eq!( c.pad_top, Length::px( 10.0 ) );
|
||||||
assert_eq!( c.pad_right, 10.0 );
|
assert_eq!( c.pad_right, Length::px( 10.0 ) );
|
||||||
assert_eq!( c.pad_bottom, 10.0 );
|
assert_eq!( c.pad_bottom, Length::px( 10.0 ) );
|
||||||
assert_eq!( c.pad_left, 10.0 );
|
assert_eq!( c.pad_left, Length::px( 10.0 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ test ]
|
#[ test ]
|
||||||
fn padding_h_only_touches_left_and_right()
|
fn padding_h_only_touches_left_and_right()
|
||||||
{
|
{
|
||||||
let c = container::<()>( spacer() ).padding_h( 8.0 );
|
let c = container::<()>( spacer() ).padding_h( 8.0 );
|
||||||
assert_eq!( c.pad_left, 8.0 );
|
assert_eq!( c.pad_left, Length::px( 8.0 ) );
|
||||||
assert_eq!( c.pad_right, 8.0 );
|
assert_eq!( c.pad_right, Length::px( 8.0 ) );
|
||||||
assert_eq!( c.pad_top, 0.0 );
|
assert_eq!( c.pad_top, Length::px( 0.0 ) );
|
||||||
assert_eq!( c.pad_bottom, 0.0 );
|
assert_eq!( c.pad_bottom, Length::px( 0.0 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ test ]
|
#[ test ]
|
||||||
fn padding_v_only_touches_top_and_bottom()
|
fn padding_v_only_touches_top_and_bottom()
|
||||||
{
|
{
|
||||||
let c = container::<()>( spacer() ).padding_v( 6.0 );
|
let c = container::<()>( spacer() ).padding_v( 6.0 );
|
||||||
assert_eq!( c.pad_top, 6.0 );
|
assert_eq!( c.pad_top, Length::px( 6.0 ) );
|
||||||
assert_eq!( c.pad_bottom, 6.0 );
|
assert_eq!( c.pad_bottom, Length::px( 6.0 ) );
|
||||||
assert_eq!( c.pad_left, 0.0 );
|
assert_eq!( c.pad_left, Length::px( 0.0 ) );
|
||||||
assert_eq!( c.pad_right, 0.0 );
|
assert_eq!( c.pad_right, Length::px( 0.0 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ test ]
|
#[ test ]
|
||||||
@@ -48,10 +49,10 @@ fn per_edge_overrides_uniform_padding()
|
|||||||
let c = container::<()>( spacer() )
|
let c = container::<()>( spacer() )
|
||||||
.padding( 12.0 )
|
.padding( 12.0 )
|
||||||
.padding_bottom( 22.0 );
|
.padding_bottom( 22.0 );
|
||||||
assert_eq!( c.pad_top, 12.0 );
|
assert_eq!( c.pad_top, Length::px( 12.0 ) );
|
||||||
assert_eq!( c.pad_right, 12.0 );
|
assert_eq!( c.pad_right, Length::px( 12.0 ) );
|
||||||
assert_eq!( c.pad_bottom, 22.0 );
|
assert_eq!( c.pad_bottom, Length::px( 22.0 ) );
|
||||||
assert_eq!( c.pad_left, 12.0 );
|
assert_eq!( c.pad_left, Length::px( 12.0 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ test ]
|
#[ test ]
|
||||||
|
|||||||
@@ -93,13 +93,27 @@ impl<Msg: Clone> Scroll<Msg>
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `(max_width, 0.0)` — the Scroll node claims all remaining space in
|
/// Preferred size — axis-aware.
|
||||||
/// the parent layout, exactly like a [`Spacer`](crate::layout::spacer::Spacer).
|
///
|
||||||
/// The actual viewport size is determined at render time from the rect the
|
/// - **Vertical or both**: returns `(max_width, 0.0)`. The Scroll node
|
||||||
/// parent assigns.
|
/// claims all remaining space in the parent layout, exactly like a
|
||||||
pub fn preferred_size( &self, max_width: f32, _canvas: &Canvas ) -> (f32, f32)
|
/// [`Spacer`](crate::layout::spacer::Spacer). The actual viewport size
|
||||||
|
/// is determined at render time from the rect the parent assigns.
|
||||||
|
/// - **Horizontal-only**: claims `max_width` but reports the child's
|
||||||
|
/// natural height. A horizontal scroll has an intrinsic height (the
|
||||||
|
/// row of items it clips) and must not steal Y space from its siblings
|
||||||
|
/// when it sits inside a [`Column`](crate::layout::column::Column).
|
||||||
|
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
|
||||||
{
|
{
|
||||||
( max_width, 0.0 )
|
match self.axis
|
||||||
|
{
|
||||||
|
ScrollAxis::Horizontal =>
|
||||||
|
{
|
||||||
|
let ( _, h ) = self.child.preferred_size( max_width, canvas );
|
||||||
|
( max_width, h )
|
||||||
|
}
|
||||||
|
ScrollAxis::Vertical | ScrollAxis::Both => ( max_width, 0.0 ),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// No-op — rendering is handled entirely by `layout_and_draw` in `draw.rs`.
|
/// No-op — rendering is handled entirely by `layout_and_draw` in `draw.rs`.
|
||||||
|
|||||||
105
tests/cursor_assets.rs
Normal file
105
tests/cursor_assets.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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)",
|
||||||
|
);
|
||||||
|
}
|
||||||
3
themes/default/cursor.theme
Normal file
3
themes/default/cursor.theme
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[Icon Theme]
|
||||||
|
Name=Default
|
||||||
|
Comment=Liberux default cursor theme
|
||||||
57
themes/default/cursors/LICENSE.md
Normal file
57
themes/default/cursors/LICENSE.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Cursor theme licence
|
||||||
|
|
||||||
|
The pointer cursors in this directory are GNOME's **Adwaita** cursor
|
||||||
|
theme — the cursors GNOME Shell ships — bundled verbatim into the ltk
|
||||||
|
default theme. They are distributed by upstream under a choice of
|
||||||
|
licences:
|
||||||
|
|
||||||
|
- **Author:** the GNOME Project (Adwaita icon theme authors, 2002–2014)
|
||||||
|
- **Source:** <https://gitlab.gnome.org/GNOME/adwaita-icon-theme>
|
||||||
|
- **Licence:** **CC BY-SA 3.0 _or_ LGPL 3**, and CC BY-SA 4.0 for the
|
||||||
|
newer assets — see below.
|
||||||
|
|
||||||
|
The licence is a disjunction: redistributing the cursors under **any
|
||||||
|
one** of the offered options satisfies it. ltk passes the choice
|
||||||
|
through unchanged.
|
||||||
|
|
||||||
|
- CC BY-SA 3.0 — <https://creativecommons.org/licenses/by-sa/3.0/>
|
||||||
|
- CC BY-SA 4.0 — <https://creativecommons.org/licenses/by-sa/4.0/>
|
||||||
|
- LGPL 3 — <https://www.gnu.org/licenses/lgpl-3.0.html>
|
||||||
|
|
||||||
|
## Required attribution
|
||||||
|
|
||||||
|
When redistributing this directory (or a binary that embeds these
|
||||||
|
cursor files), credit must be given to the GNOME Project and a link to
|
||||||
|
the chosen licence must be reachable. The wording is flexible — any of
|
||||||
|
the following satisfies the obligation:
|
||||||
|
|
||||||
|
> Cursors by the [GNOME Project](https://www.gnome.org), Adwaita cursor
|
||||||
|
> theme, CC BY-SA 3.0 / LGPL 3.
|
||||||
|
|
||||||
|
> Pointer cursors: © the GNOME Project, Adwaita, distributed under
|
||||||
|
> CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/).
|
||||||
|
|
||||||
|
Upstream notes that, for attribution, "using 'GNOME Project' is enough".
|
||||||
|
|
||||||
|
The full Debian-style attribution lives in the top-level
|
||||||
|
[`debian/copyright`](../../../debian/copyright) of the `ltk` source
|
||||||
|
tree under the `Files: themes/default/cursors/*` paragraph.
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
|
||||||
|
The cursor files in this directory are **unmodified** copies of the
|
||||||
|
upstream Adwaita XCursor files (the alias symlinks are part of the
|
||||||
|
upstream theme). No content changes have been made, so there is nothing
|
||||||
|
to declare under the ShareAlike "indicate if changes were made" clause.
|
||||||
|
Anyone needing a pristine copy can also fetch it from the upstream
|
||||||
|
`adwaita-icon-theme` release.
|
||||||
|
|
||||||
|
## How licensing applies to the rest of the theme
|
||||||
|
|
||||||
|
The rest of the default theme is licensed separately: branding artwork,
|
||||||
|
app icons and the unknown-app fallback are original Liberux Labs work
|
||||||
|
under `LGPL-2.1-only`, and the catalogue glyphs under
|
||||||
|
`themes/default/icons/catalogue/` are Streamline's under CC BY 4.0 (see
|
||||||
|
that directory's `LICENSE.md`). Forks should preserve the split:
|
||||||
|
re-license your own artwork freely, but the contents of this `cursors/`
|
||||||
|
directory remain the GNOME Project's under the terms above.
|
||||||
73
themes/default/cursors/README.md
Normal file
73
themes/default/cursors/README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Default theme cursors
|
||||||
|
|
||||||
|
This directory holds the pointer cursors for the ltk **default** theme.
|
||||||
|
They are GNOME's **Adwaita** cursors (what GNOME Shell uses), bundled
|
||||||
|
verbatim so the theme is self-contained and does not depend on
|
||||||
|
`adwaita-icon-theme` being installed on the target system.
|
||||||
|
|
||||||
|
Licence and attribution are documented separately in
|
||||||
|
[`LICENSE.md`](./LICENSE.md).
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
Standard XCursor theme layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
themes/default/
|
||||||
|
├── cursor.theme # XCursor theme manifest (Name=Default)
|
||||||
|
└── cursors/
|
||||||
|
├── default # one binary XCursor file per CSS cursor name
|
||||||
|
├── text
|
||||||
|
├── pointer
|
||||||
|
├── ew-resize
|
||||||
|
├── …
|
||||||
|
├── arrow -> default # customary X11 alias symlinks
|
||||||
|
└── hand2 -> pointer
|
||||||
|
```
|
||||||
|
|
||||||
|
- Each regular file is a binary XCursor image set (magic `Xcur`),
|
||||||
|
carrying one or more nominal sizes and, for the busy cursors, several
|
||||||
|
animation frames.
|
||||||
|
- The file **name is the CSS / freedesktop cursor name** (`default`,
|
||||||
|
`text`, `pointer`, `not-allowed`, `ns-resize`, …), which is exactly
|
||||||
|
the name a `wp_cursor_shape_v1` shape resolves to. The alias symlinks
|
||||||
|
(`arrow`, `hand2`, `bottom_left_corner`, …) are the legacy X11 names;
|
||||||
|
they are not required by the cursor-shape protocol but are kept so the
|
||||||
|
tree also works as a plain XCursor theme (e.g. for XWayland).
|
||||||
|
|
||||||
|
## Cursor set
|
||||||
|
|
||||||
|
The theme provides a file for every shape ltk can request — the 34
|
||||||
|
variants of [`ltk::CursorShape`](../../../src/types.rs), which mirror
|
||||||
|
`cursor_icon::CursorIcon` 1:1:
|
||||||
|
|
||||||
|
`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`.
|
||||||
|
|
||||||
|
`wait` and `progress` are animated (multiple frames with per-frame
|
||||||
|
delays). The `tests/cursor_assets.rs` integration test enforces that
|
||||||
|
every name above resolves to a valid XCursor file.
|
||||||
|
|
||||||
|
## How they are used
|
||||||
|
|
||||||
|
ltk does **not** rasterise cursors itself; as a Wayland client it
|
||||||
|
declares a shape per widget through `wp_cursor_shape_v1` and the
|
||||||
|
compositor draws it. The Liberux compositor (forge) resolves the
|
||||||
|
requested shape against the active theme's `cursors/` directory by CSS
|
||||||
|
name, picks the image whose nominal size is closest to `24px × output
|
||||||
|
scale`, and uploads it at the cursor's hotspot — so these files are what
|
||||||
|
the user actually sees under forge. A compositor that does not advertise
|
||||||
|
`wp_cursor_shape_v1`, or that uses a different cursor theme, will ignore
|
||||||
|
this directory.
|
||||||
|
|
||||||
|
## Forking / overriding
|
||||||
|
|
||||||
|
To ship different cursors in a derived theme, drop replacement XCursor
|
||||||
|
files (same CSS names) into the fork's `cursors/` directory. Keep
|
||||||
|
`cursor.theme` so the tree stays a valid XCursor theme, and update the
|
||||||
|
`Files: themes/default/cursors/*` paragraph in `debian/copyright` plus
|
||||||
|
this directory's `LICENSE.md` to match the new source and licence.
|
||||||
BIN
themes/default/cursors/X_cursor
Normal file
BIN
themes/default/cursors/X_cursor
Normal file
Binary file not shown.
BIN
themes/default/cursors/alias
Normal file
BIN
themes/default/cursors/alias
Normal file
Binary file not shown.
BIN
themes/default/cursors/all-resize
Normal file
BIN
themes/default/cursors/all-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/all-scroll
Normal file
BIN
themes/default/cursors/all-scroll
Normal file
Binary file not shown.
1
themes/default/cursors/arrow
Symbolic link
1
themes/default/cursors/arrow
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
default
|
||||||
1
themes/default/cursors/bd_double_arrow
Symbolic link
1
themes/default/cursors/bd_double_arrow
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
nwse-resize
|
||||||
1
themes/default/cursors/bottom_left_corner
Symbolic link
1
themes/default/cursors/bottom_left_corner
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
sw-resize
|
||||||
1
themes/default/cursors/bottom_right_corner
Symbolic link
1
themes/default/cursors/bottom_right_corner
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
se-resize
|
||||||
1
themes/default/cursors/bottom_side
Symbolic link
1
themes/default/cursors/bottom_side
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
s-resize
|
||||||
BIN
themes/default/cursors/cell
Normal file
BIN
themes/default/cursors/cell
Normal file
Binary file not shown.
BIN
themes/default/cursors/col-resize
Normal file
BIN
themes/default/cursors/col-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/context-menu
Normal file
BIN
themes/default/cursors/context-menu
Normal file
Binary file not shown.
BIN
themes/default/cursors/copy
Normal file
BIN
themes/default/cursors/copy
Normal file
Binary file not shown.
1
themes/default/cursors/cross
Symbolic link
1
themes/default/cursors/cross
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
crosshair
|
||||||
1
themes/default/cursors/cross_reverse
Symbolic link
1
themes/default/cursors/cross_reverse
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
crosshair
|
||||||
BIN
themes/default/cursors/crosshair
Normal file
BIN
themes/default/cursors/crosshair
Normal file
Binary file not shown.
BIN
themes/default/cursors/default
Normal file
BIN
themes/default/cursors/default
Normal file
Binary file not shown.
1
themes/default/cursors/diamond_cross
Symbolic link
1
themes/default/cursors/diamond_cross
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
crosshair
|
||||||
1
themes/default/cursors/dnd-move
Symbolic link
1
themes/default/cursors/dnd-move
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
default
|
||||||
BIN
themes/default/cursors/e-resize
Normal file
BIN
themes/default/cursors/e-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/ew-resize
Normal file
BIN
themes/default/cursors/ew-resize
Normal file
Binary file not shown.
1
themes/default/cursors/fd_double_arrow
Symbolic link
1
themes/default/cursors/fd_double_arrow
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
nesw-resize
|
||||||
1
themes/default/cursors/fleur
Symbolic link
1
themes/default/cursors/fleur
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
all-resize
|
||||||
BIN
themes/default/cursors/grab
Normal file
BIN
themes/default/cursors/grab
Normal file
Binary file not shown.
BIN
themes/default/cursors/grabbing
Normal file
BIN
themes/default/cursors/grabbing
Normal file
Binary file not shown.
1
themes/default/cursors/hand1
Symbolic link
1
themes/default/cursors/hand1
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
grab
|
||||||
1
themes/default/cursors/hand2
Symbolic link
1
themes/default/cursors/hand2
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
pointer
|
||||||
BIN
themes/default/cursors/help
Normal file
BIN
themes/default/cursors/help
Normal file
Binary file not shown.
1
themes/default/cursors/left_ptr
Symbolic link
1
themes/default/cursors/left_ptr
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
default
|
||||||
1
themes/default/cursors/left_side
Symbolic link
1
themes/default/cursors/left_side
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
w-resize
|
||||||
1
themes/default/cursors/move
Symbolic link
1
themes/default/cursors/move
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
default
|
||||||
BIN
themes/default/cursors/n-resize
Normal file
BIN
themes/default/cursors/n-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/ne-resize
Normal file
BIN
themes/default/cursors/ne-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/nesw-resize
Normal file
BIN
themes/default/cursors/nesw-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/no-drop
Normal file
BIN
themes/default/cursors/no-drop
Normal file
Binary file not shown.
BIN
themes/default/cursors/not-allowed
Normal file
BIN
themes/default/cursors/not-allowed
Normal file
Binary file not shown.
BIN
themes/default/cursors/ns-resize
Normal file
BIN
themes/default/cursors/ns-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/nw-resize
Normal file
BIN
themes/default/cursors/nw-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/nwse-resize
Normal file
BIN
themes/default/cursors/nwse-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/pointer
Normal file
BIN
themes/default/cursors/pointer
Normal file
Binary file not shown.
BIN
themes/default/cursors/progress
Normal file
BIN
themes/default/cursors/progress
Normal file
Binary file not shown.
1
themes/default/cursors/question_arrow
Symbolic link
1
themes/default/cursors/question_arrow
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
help
|
||||||
1
themes/default/cursors/right_side
Symbolic link
1
themes/default/cursors/right_side
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
e-resize
|
||||||
BIN
themes/default/cursors/row-resize
Normal file
BIN
themes/default/cursors/row-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/s-resize
Normal file
BIN
themes/default/cursors/s-resize
Normal file
Binary file not shown.
1
themes/default/cursors/sb_h_double_arrow
Symbolic link
1
themes/default/cursors/sb_h_double_arrow
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
ew-resize
|
||||||
1
themes/default/cursors/sb_v_double_arrow
Symbolic link
1
themes/default/cursors/sb_v_double_arrow
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
ns-resize
|
||||||
BIN
themes/default/cursors/se-resize
Normal file
BIN
themes/default/cursors/se-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/sw-resize
Normal file
BIN
themes/default/cursors/sw-resize
Normal file
Binary file not shown.
1
themes/default/cursors/tcross
Symbolic link
1
themes/default/cursors/tcross
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
crosshair
|
||||||
BIN
themes/default/cursors/text
Normal file
BIN
themes/default/cursors/text
Normal file
Binary file not shown.
1
themes/default/cursors/top_left_arrow
Symbolic link
1
themes/default/cursors/top_left_arrow
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
default
|
||||||
1
themes/default/cursors/top_left_corner
Symbolic link
1
themes/default/cursors/top_left_corner
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
nw-resize
|
||||||
1
themes/default/cursors/top_right_corner
Symbolic link
1
themes/default/cursors/top_right_corner
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
ne-resize
|
||||||
1
themes/default/cursors/top_side
Symbolic link
1
themes/default/cursors/top_side
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
n-resize
|
||||||
BIN
themes/default/cursors/vertical-text
Normal file
BIN
themes/default/cursors/vertical-text
Normal file
Binary file not shown.
BIN
themes/default/cursors/w-resize
Normal file
BIN
themes/default/cursors/w-resize
Normal file
Binary file not shown.
BIN
themes/default/cursors/wait
Normal file
BIN
themes/default/cursors/wait
Normal file
Binary file not shown.
1
themes/default/cursors/watch
Symbolic link
1
themes/default/cursors/watch
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
wait
|
||||||
1
themes/default/cursors/xterm
Symbolic link
1
themes/default/cursors/xterm
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
text
|
||||||
BIN
themes/default/cursors/zoom-in
Normal file
BIN
themes/default/cursors/zoom-in
Normal file
Binary file not shown.
BIN
themes/default/cursors/zoom-out
Normal file
BIN
themes/default/cursors/zoom-out
Normal file
Binary file not shown.
@@ -37,7 +37,18 @@
|
|||||||
"glass-elev": "#212121",
|
"glass-elev": "#212121",
|
||||||
"ink": "#000000",
|
"ink": "#000000",
|
||||||
"cyan-deep": "#0091AD",
|
"cyan-deep": "#0091AD",
|
||||||
"teal-fill-end": "#00687C"
|
"teal-fill-end": "#00687C",
|
||||||
|
"green": "#00D344",
|
||||||
|
"green-deep": "#00B41A",
|
||||||
|
"yellow": "#FFD400",
|
||||||
|
"orange": "#FF8156",
|
||||||
|
"orange-deep": "#BD6403",
|
||||||
|
"pink": "#EF15FA",
|
||||||
|
"pink-soft": "#FFD7FF",
|
||||||
|
"sky-deep": "#1E8FD9",
|
||||||
|
"error": "#F00013",
|
||||||
|
"error-soft": "#FF6B6B",
|
||||||
|
"neutral-tertiary": "#807E88"
|
||||||
},
|
},
|
||||||
|
|
||||||
"gradients": {
|
"gradients": {
|
||||||
@@ -111,6 +122,21 @@
|
|||||||
"accent": { "type": "color", "value": "@cyan", "meta": { "semantic": "palette/accent" } },
|
"accent": { "type": "color", "value": "@cyan", "meta": { "semantic": "palette/accent" } },
|
||||||
"divider": { "type": "color", "value": "@navy/14", "meta": { "semantic": "palette/divider" } },
|
"divider": { "type": "color", "value": "@navy/14", "meta": { "semantic": "palette/divider" } },
|
||||||
"icon": { "type": "color", "value": "@navy", "meta": { "semantic": "palette/icon" } },
|
"icon": { "type": "color", "value": "@navy", "meta": { "semantic": "palette/icon" } },
|
||||||
|
"danger": { "type": "color", "value": "@error", "meta": { "semantic": "palette/danger" } },
|
||||||
|
"text-tertiary": { "type": "color", "value": "@neutral-tertiary", "meta": { "semantic": "palette/text_tertiary" } },
|
||||||
|
"accept": { "type": "color", "value": "@green-deep", "meta": { "semantic": "status/accept" } },
|
||||||
|
"chip": { "type": "color", "value": "@off-white", "meta": { "semantic": "palette/chip" } },
|
||||||
|
"chip-active": { "type": "color", "value": "@navy", "meta": { "semantic": "palette/chip_active" } },
|
||||||
|
"chip-active-fg": { "type": "color", "value": "@white", "meta": { "semantic": "palette/chip_active_fg" } },
|
||||||
|
"avatar-1": { "type": "color", "value": "@sky", "meta": { "semantic": "avatar/1" } },
|
||||||
|
"avatar-2": { "type": "color", "value": "@green", "meta": { "semantic": "avatar/2" } },
|
||||||
|
"avatar-3": { "type": "color", "value": "@orange", "meta": { "semantic": "avatar/3" } },
|
||||||
|
"avatar-4": { "type": "color", "value": "@pink", "meta": { "semantic": "avatar/4" } },
|
||||||
|
"avatar-5": { "type": "color", "value": "@yellow", "meta": { "semantic": "avatar/5" } },
|
||||||
|
"avatar-6": { "type": "color", "value": "@sky-deep", "meta": { "semantic": "avatar/6" } },
|
||||||
|
"avatar-7": { "type": "color", "value": "@cyan", "meta": { "semantic": "avatar/7" } },
|
||||||
|
"avatar-8": { "type": "color", "value": "@green-deep", "meta": { "semantic": "avatar/8" } },
|
||||||
|
"avatar-9": { "type": "color", "value": "@orange-deep", "meta": { "semantic": "avatar/9" } },
|
||||||
|
|
||||||
"shadows-glass": {
|
"shadows-glass": {
|
||||||
"type": "shadows",
|
"type": "shadows",
|
||||||
@@ -225,6 +251,21 @@
|
|||||||
"accent": { "type": "color", "value": "@cyan", "meta": { "semantic": "palette/accent" } },
|
"accent": { "type": "color", "value": "@cyan", "meta": { "semantic": "palette/accent" } },
|
||||||
"divider": { "type": "color", "value": "@white/14", "meta": { "semantic": "palette/divider" } },
|
"divider": { "type": "color", "value": "@white/14", "meta": { "semantic": "palette/divider" } },
|
||||||
"icon": { "type": "color", "value": "@white", "meta": { "semantic": "palette/icon" } },
|
"icon": { "type": "color", "value": "@white", "meta": { "semantic": "palette/icon" } },
|
||||||
|
"danger": { "type": "color", "value": "@error-soft", "meta": { "semantic": "palette/danger" } },
|
||||||
|
"text-tertiary": { "type": "color", "value": "@white/80", "meta": { "semantic": "palette/text_tertiary" } },
|
||||||
|
"accept": { "type": "color", "value": "@green", "meta": { "semantic": "status/accept" } },
|
||||||
|
"chip": { "type": "color", "value": "@white/14", "meta": { "semantic": "palette/chip" } },
|
||||||
|
"chip-active": { "type": "color", "value": "@cyan", "meta": { "semantic": "palette/chip_active" } },
|
||||||
|
"chip-active-fg": { "type": "color", "value": "@navy", "meta": { "semantic": "palette/chip_active_fg" } },
|
||||||
|
"avatar-1": { "type": "color", "value": "@sky", "meta": { "semantic": "avatar/1" } },
|
||||||
|
"avatar-2": { "type": "color", "value": "@green", "meta": { "semantic": "avatar/2" } },
|
||||||
|
"avatar-3": { "type": "color", "value": "@orange", "meta": { "semantic": "avatar/3" } },
|
||||||
|
"avatar-4": { "type": "color", "value": "@pink", "meta": { "semantic": "avatar/4" } },
|
||||||
|
"avatar-5": { "type": "color", "value": "@yellow", "meta": { "semantic": "avatar/5" } },
|
||||||
|
"avatar-6": { "type": "color", "value": "@sky-deep", "meta": { "semantic": "avatar/6" } },
|
||||||
|
"avatar-7": { "type": "color", "value": "@cyan", "meta": { "semantic": "avatar/7" } },
|
||||||
|
"avatar-8": { "type": "color", "value": "@green-deep", "meta": { "semantic": "avatar/8" } },
|
||||||
|
"avatar-9": { "type": "color", "value": "@orange-deep", "meta": { "semantic": "avatar/9" } },
|
||||||
|
|
||||||
"shadows-glass": {
|
"shadows-glass": {
|
||||||
"type": "shadows",
|
"type": "shadows",
|
||||||
|
|||||||
Reference in New Issue
Block a user