Documentation pass: every claim in docs/ and the meta files was audited against the source and the drift fixed — around ninety corrections. CONTRIBUTING and the CI workflow now run cargo test with --features test-support (the gated test_support module made both the documented commands and the CI build fail to compile), make example becomes make examples, make doctest-md and the debhelper requirement of make clean are documented, and patch shape asks for a CHANGELOG entry. theming.md loses the nonexistent surface.backdrop, gains the real gradient defaults (linear-rgb, oklab), the six slot variants including typography, the ten-field palette, a truthful effects-consumer table, the ThemePreference/from_hour API and a responsive-sizing note; the stale docstrings in src/theme that fed the drift are fixed too. architecture.md's "Known gaps" section is rewritten against reality (multi-touch slots, xdg-activation, a11y live regions and SetValue/Increment/Decrement are implemented), gains a module map, subsurfaces and window-lifecycle coverage, and correct crustace/loginmanager paths. widgets.md fixes the ten factual errors (stateless spinner, toast/combo via overlays(), tooltip hover contract, row has no max_width, scroll axes, multiline text_edit, dialog panic wording) and now states the column() 16 px default padding — the recurring ambush — plus row's differing 0 default and dialog's max_width. onboarding, README and cookbook get the remaining sweep: build/test instructions, complete example lists, img_widget, clipping-parity honesty, ~30 Hz software cap, read_rgba_pixels signature, tab indentation in snippets, and rustdoc-style links that rendered literally are gone everywhere. CHANGELOG is restructured per Keep a Changelog with the missing entries (window_resizable, claims_raw_touch, Row::align_top/fill_height, caret fixes, dependency pins) and the pad_v Added/Changed contradiction resolved. New adaptive-layout API: ltk::orientation() with the Orientation enum, backed by viewport_size()/set_viewport_size — the runtime records the main surface's physical dimensions on every configure, before App::on_resize, so view() can branch a layout on portrait vs landscape without hand-tracking resizes. The portrait rule matches Length::orient (square counts as portrait); embedders driving core::UiSurface call set_viewport_size themselves. Documented in the crate root's responsive-design section and architecture.md. Fluid-vs-fixed sizing fixes in widgets, all the same disease — fluid content inside a fixed-pixel box. TextEdit::fixed_width takes impl Into<Length> (f32 call sites keep compiling as px) and the time picker's digit fields move to Length::fluid( 72.0 ), matching their fluid font so digits can no longer outgrow the box. Dialog::max_width takes impl Into<Length> with a Length::fluid( 480.0 ) default so the card scales with the stock buttons inside it, and the card's interior no longer stacks the column() default 16 px padding on top of CARD_PADDING — that double inset squeezed the action row until its buttons clipped on narrow windows. App::on_pointer_axis now triggers a view rebuild and repaint; previously state mutated in the hook did not paint until the next unrelated event. Examples reworked to be honest demos: responsive's mode/density controls become stock buttons in a grid/column so they follow the modes they demonstrate instead of overflowing; dialog's openers stack vertically, and the example gains the app-level ESC handler so the ESC chain closes an open dialog first and quits second; widgets' tab strip now switches real per-tab pages; carousel gains pointer/touch drag through the horizontal-swipe hooks (crustace's pager pattern), one-tile-per-detent mouse wheel, and snap math driven by the real surface width from on_resize instead of a hardcoded 800; clip_path arranges its cells by ltk::orientation() and sizes them from the counter-axis of the flow.
158 lines
4.8 KiB
Rust
158 lines
4.8 KiB
Rust
//! `cargo run --example responsive`
|
||
//!
|
||
//! Demonstrates ltk's two responsive modes side by side on stock widgets.
|
||
//! None of the widgets below set an explicit size — they all follow the
|
||
//! process-wide [`ltk::WidgetScaling`] mode:
|
||
//!
|
||
//! - **Fluid** (the default): sizes are a fraction of the surface, so the
|
||
//! whole set grows and shrinks as you resize the window.
|
||
//! - **Physical**: sizes are a constant real-world size scaled by
|
||
//! [`ltk::density`], independent of the surface.
|
||
//!
|
||
//! Tap **Switch mode** to flip between them, and **−/+ density** to change
|
||
//! the physical density. Watch the button, field, checkbox, switch, slider
|
||
//! and progress bar resize (or not) accordingly. Esc exits.
|
||
//!
|
||
//! NOTE: ltk is a Wayland layer-shell toolkit. This example requires a running
|
||
//! Wayland compositor (e.g. sway, labwc, or a full desktop session).
|
||
|
||
use ltk::
|
||
{
|
||
App, Element, Keysym, ButtonVariant, WidgetScaling,
|
||
button, checkbox, column, grid, progress_bar, separator, slider, spacer, text, text_edit, toggle,
|
||
set_widget_scaling, set_density, density,
|
||
};
|
||
|
||
#[ derive( Clone ) ]
|
||
enum Message
|
||
{
|
||
SwitchMode,
|
||
DensityUp,
|
||
DensityDown,
|
||
NameChanged( String ),
|
||
ToggleCheck,
|
||
ToggleSwitch,
|
||
SliderChanged( f32 ),
|
||
}
|
||
|
||
struct ResponsiveApp
|
||
{
|
||
physical: bool,
|
||
name: String,
|
||
checked: bool,
|
||
switched: bool,
|
||
volume: f32,
|
||
}
|
||
|
||
impl ResponsiveApp
|
||
{
|
||
fn new() -> Self
|
||
{
|
||
// Start in the default fluid mode with a neutral density.
|
||
set_widget_scaling( WidgetScaling::Fluid );
|
||
set_density( 1.5 );
|
||
Self
|
||
{
|
||
physical: false,
|
||
name: String::new(),
|
||
checked: true,
|
||
switched: false,
|
||
volume: 0.4,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl App for ResponsiveApp
|
||
{
|
||
type Message = Message;
|
||
|
||
fn view( &self ) -> Element<Message>
|
||
{
|
||
let palette = ltk::theme_palette();
|
||
let primary = palette.text_primary;
|
||
let secondary = palette.text_secondary;
|
||
|
||
let mode_label = if self.physical
|
||
{
|
||
format!( "Mode: Physical (density {:.1})", density() )
|
||
} else {
|
||
"Mode: Fluid (resize the window to see it scale)".to_string()
|
||
};
|
||
|
||
// Mode + density controls. Explicit sizes here so the controls stay
|
||
// stable while the demo widgets below react to the mode.
|
||
// Mode + density controls. Stock buttons like the demo widgets
|
||
// below, so they follow the active mode too. The switch gets the
|
||
// full width and the density pair half a cell each: every button
|
||
// clamps to its slot instead of overflowing a shared row, and the
|
||
// slots leave room for Physical-mode text at raised densities.
|
||
let controls = column::<Message>()
|
||
.padding( 0.0 )
|
||
.spacing( 8.0 )
|
||
.push( button::<Message>( "Switch mode".to_string() )
|
||
.variant( ButtonVariant::Primary )
|
||
.on_press( Message::SwitchMode ) )
|
||
.push( grid::<Message>( 2 )
|
||
.push( button::<Message>( "− density".to_string() )
|
||
.on_press( Message::DensityDown ) )
|
||
.push( button::<Message>( "+ density".to_string() )
|
||
.on_press( Message::DensityUp ) ) );
|
||
|
||
// The demo widgets — NO explicit sizes, so they follow the mode.
|
||
let demo = column::<Message>()
|
||
.spacing( 16.0 )
|
||
.max_width( 520.0 )
|
||
.push( button::<Message>( "A stock button".to_string() ).variant( ButtonVariant::Secondary ).on_press( Message::SwitchMode ) )
|
||
.push( text_edit( "A stock text field".to_string(), self.name.clone() ).on_change( Message::NameChanged ) )
|
||
.push( checkbox( self.checked ).label( "A stock checkbox".to_string() ).on_toggle( Message::ToggleCheck ) )
|
||
.push( toggle( self.switched ).label( "A stock switch".to_string() ).on_toggle( Message::ToggleSwitch ) )
|
||
.push( slider( self.volume ).on_change( Message::SliderChanged ) )
|
||
.push( progress_bar( self.volume ) );
|
||
|
||
column::<Message>()
|
||
.padding( 32.0 )
|
||
.spacing( 20.0 )
|
||
.center_y( true )
|
||
.push( text( "ltk responsive modes".to_string() ).size( 26.0 ).color( primary ).align_center() )
|
||
.push( text( mode_label ).size( 15.0 ).color( secondary ).align_center() )
|
||
.push( controls )
|
||
.push( separator() )
|
||
.push( demo )
|
||
.push( spacer().weight( 1 ) )
|
||
.push( text( "Esc to exit".to_string() ).size( 12.0 ).color( secondary ).align_center() )
|
||
.into()
|
||
}
|
||
|
||
fn update( &mut self, msg: Message )
|
||
{
|
||
match msg
|
||
{
|
||
Message::SwitchMode =>
|
||
{
|
||
self.physical = !self.physical;
|
||
set_widget_scaling( if self.physical { WidgetScaling::Physical } else { WidgetScaling::Fluid } );
|
||
}
|
||
Message::DensityUp => set_density( ( density() + 0.25 ).min( 4.0 ) ),
|
||
Message::DensityDown => set_density( ( density() - 0.25 ).max( 0.5 ) ),
|
||
Message::NameChanged( v ) => self.name = v,
|
||
Message::ToggleCheck => self.checked = !self.checked,
|
||
Message::ToggleSwitch => self.switched = !self.switched,
|
||
Message::SliderChanged( v ) => self.volume = v,
|
||
}
|
||
}
|
||
|
||
fn on_key( &mut self, keysym: Keysym ) -> Option<Message>
|
||
{
|
||
if keysym == Keysym::Escape
|
||
{
|
||
std::process::exit( 0 );
|
||
}
|
||
None
|
||
}
|
||
}
|
||
|
||
fn main()
|
||
{
|
||
ltk::run( ResponsiveApp::new() );
|
||
}
|