//! Composable, ordered layout for progress output. //! //! A [`Layout`] is an ordered list of [`Segment`]s joined by a separator. Each segment pulls its //! value from a [`RenderContext`] supplied by the call site; segments with nothing to show //! contribute no output and are skipped when joining, so spacing stays correct without manual //! padding. //! //! [`Layout::DEFAULT`] reproduces the built-in look (spinner, elapsed time, label, bar, message). //! Attach a custom layout to a [`Theme`](crate::Theme) with //! [`Theme::with_layout`](crate::Theme::with_layout): //! //! ```rust //! use strides::layout::{Layout, Segment}; //! //! let layout = Layout::from_segments([ //! Segment::spinner(), //! Segment::elapsed().with_border("[", "]"), //! Segment::bar(), //! Segment::message(), //! ]); //! ``` use std::borrow::Cow; use std::fmt::Write as _; use std::time::Duration; use owo_colors::{OwoColorize as _, Style}; use crate::bar::Bar; /// Values available to a [`Segment`] at render time. /// /// Call sites fill this in once per frame; segments read from it. Fields that hold an [`Option`] /// signal absence — the corresponding segment then renders nothing. pub struct RenderContext<'a> { /// Current spinner character, if the spinner has ticked at least once. pub spinner: Option, /// Time elapsed since rendering started. pub elapsed: Duration, /// Whether the elapsed time should be rendered at all. pub show_elapsed: bool, /// Bar style used by [`Segment::Bar`]. pub bar: &'a Bar<'a>, /// Bar width in characters. pub bar_width: usize, /// Current progress fraction, or `None` when no progress is tracked. pub progress: Option, /// Cumulative bytes transferred. `0` until the first byte-tracking update. pub bytes_done: u64, /// Total bytes expected, if known. Used by [`Segment::Bytes`] and [`Segment::Eta`]. pub bytes_total: Option, /// Smoothed transfer rate in bytes per second, if enough samples are available. pub rate: Option, /// Static label text, if any. pub label: Option<&'a str>, /// Dynamic message text, if any. pub message: Option<&'a str>, /// Fallback style for [`Segment::Spinner`] when it carries no explicit style. pub spinner_style: Style, /// Fallback style for [`Segment::Label`] when it carries no explicit style. pub annotation_style: Style, } /// A single renderable element of a [`Layout`]. /// /// Construct segments with the associated functions ([`Segment::spinner`], [`Segment::elapsed`], /// …) and refine them with the `with_*` builders. A `with_*` builder applied to a segment it does /// not affect returns that segment unchanged. #[derive(Clone)] pub enum Segment { /// The spinner character. Spinner { /// Explicit style; falls back to [`RenderContext::spinner_style`] when `None`. style: Option