diff --git a/tui-scrollbar/Cargo.toml b/tui-scrollbar/Cargo.toml index 8d3a27ea..90e2732a 100644 --- a/tui-scrollbar/Cargo.toml +++ b/tui-scrollbar/Cargo.toml @@ -41,6 +41,13 @@ crossterm_0_29 = ["dep:crossterm_0_29"] color-eyre.workspace = true ratatui = { workspace = true, default-features = true } +[[example]] +name = "scrollbar" +doc-scrape-examples = true # applies to all examples, not just this one + [[example]] name = "scrollbar_mouse" required-features = ["crossterm"] + +[[example]] +name = "scrollbar_styled" diff --git a/tui-scrollbar/README.md b/tui-scrollbar/README.md index a271986e..36cc72d5 100644 --- a/tui-scrollbar/README.md +++ b/tui-scrollbar/README.md @@ -39,11 +39,19 @@ exposes a small interaction API plus pure metrics so apps can control behavior e cargo add tui-scrollbar ``` +## Important + +- Zero lengths are treated as 1. +- Arrow endcaps are disabled by default; configure them with [`ScrollBarArrows`]. +- The default [`GlyphSet`] hides the track using spaces; use [`GlyphSet::box_drawing`] or + [`GlyphSet::unicode`] for a visible track. +- The default glyphs use [Symbols for Legacy Computing] for missing upper/right eighth blocks. + Use [`GlyphSet::unicode`] if you need only standard Unicode block elements. + ## Quick start This example renders a vertical [`ScrollBar`] into a [`Buffer`] using a fixed track size and offset. Use it as a minimal template when you just need a thumb and track on screen. -If you prefer named arguments, use [`ScrollLengths`]. ```rust use ratatui_core::buffer::Buffer; @@ -80,7 +88,35 @@ Most apps update `offset` in response to input events and re-render each frame. axis. For many apps, those units are items or lines. The ratio between `viewport_len` and `content_len` is what matters, so any consistent unit works. -Zero lengths are treated as 1. +## Styling + +Style the track, thumb, and arrow endcaps directly on [`ScrollBar`]. See [`ScrollBar`] for a +full method map and more focused examples. + +Scrollbar glyphs are terminal characters. For visible track glyphs, thumb blocks, and arrow +symbols, `Style::fg` colors the glyph itself and `Style::bg` colors the cell behind it. The +default [`GlyphSet::minimal`] track renders spaces, so only the track background is visible in +empty track cells. Visible track glyph sets, such as [`GlyphSet::box_drawing`] and +[`GlyphSet::unicode`], can use foreground color for the track line. Thumb glyphs are block +characters, so `Style::fg` is usually the useful knob for thumb color; `Style::bg` still colors +the rest of the cell. With partial thumb glyphs, especially on a visible line track such as +[`GlyphSet::box_drawing`], that background can show at the ends of the thumb. Match the thumb +background to the track background unless that contrast is intentional. + +```rust +use ratatui_core::style::{Color, Style}; +use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths}; + +let lengths = ScrollLengths { + content_len: 120, + viewport_len: 30, +}; +let scrollbar = ScrollBar::vertical(lengths) + .arrows(ScrollBarArrows::Both) + .track_style(Style::new().bg(Color::Black)) + .thumb_style(Style::new().fg(Color::Rgb(255, 158, 100))) + .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black)); +``` ## Layout integration @@ -159,9 +195,11 @@ assert!(metrics.thumb_len() >= SUBCELL); ## Glyph selection -The default glyphs include [Symbols for Legacy Computing] so the thumb can render upper/right -partial fills that are missing from the standard block set. Use [`GlyphSet`] when you want to -switch to a glyph set that avoids legacy symbols. +[`GlyphSet`] controls the track and thumb characters. The default glyphs include +[Symbols for Legacy Computing] so the thumb can render upper/right partial fills that are +missing from the standard block set. Use [`GlyphSet::box_drawing`] for a visible line track, or +[`GlyphSet::unicode`] when the terminal font should avoid [Symbols for Legacy Computing] +glyphs. ```rust use tui_scrollbar::{GlyphSet, ScrollBar, ScrollLengths}; @@ -193,28 +231,10 @@ let scrollbar = ScrollBar::vertical(lengths).glyph_set(GlyphSet::unicode()); - [`PointerEvent`], [`PointerEventKind`], [`PointerButton`] - [`ScrollWheel`], [`ScrollAxis`] -## Features - -- `crossterm`: enables crossterm mouse events (latest supported version, currently `crossterm` - 0.29). -- `crossterm_0_28`: enables crossterm mouse events using `crossterm` 0.28. -- `crossterm_0_29`: enables crossterm mouse events using `crossterm` 0.29. - -When multiple crossterm versions are enabled, the latest one is used. -The selected version is re-exported as `tui_scrollbar::crossterm`. - -## Important - -- Zero lengths are treated as 1. -- Arrow endcaps are disabled by default; configure them with [`ScrollBarArrows`]. -- The default [`GlyphSet`] hides the track using spaces; use [`GlyphSet::box_drawing`] or - [`GlyphSet::unicode`] for a visible track. -- The default glyphs use [Symbols for Legacy Computing] for missing upper/right eighth blocks. - Use [`GlyphSet::unicode`] if you need only standard Unicode block elements. - ## See also - [tui-scrollbar examples] +- [`scrollbar_styled` example] - [`scrollbar_mouse` example] - [`scrollbar` example] - [`Widget`] @@ -245,6 +265,7 @@ For the full suite of widgets, see [tui-widgets]. [Contributing]: https://github.com/ratatui/tui-widgets/blob/main/CONTRIBUTING.md [Crate source]: https://github.com/ratatui/tui-widgets/blob/main/tui-scrollbar/src/lib.rs [`scrollbar_mouse` example]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples/scrollbar_mouse.rs +[`scrollbar_styled` example]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples/scrollbar_styled.rs [`scrollbar` example]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples/scrollbar.rs [tui-scrollbar examples]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples [`Buffer`]: ratatui_core::buffer::Buffer diff --git a/tui-scrollbar/examples/scrollbar_styled.rs b/tui-scrollbar/examples/scrollbar_styled.rs new file mode 100644 index 00000000..38f17e89 --- /dev/null +++ b/tui-scrollbar/examples/scrollbar_styled.rs @@ -0,0 +1,73 @@ +//! Styled scrollbar showcase. +//! +//! This example renders a static layout with independently styled track, thumb, and arrow glyphs. + +use std::time::Duration; + +use color_eyre::Result; +use ratatui::layout::{Margin, Rect}; +use ratatui::style::{Color, Modifier}; +use ratatui::widgets::{Block, Borders, Paragraph}; +use tui_scrollbar::{GlyphSet, ScrollBar, ScrollBarArrows, ScrollLengths}; + +fn main() -> Result<()> { + color_eyre::install()?; + let mut terminal = ratatui::init(); + terminal.draw(|frame| render(frame.area(), frame))?; + std::thread::sleep(Duration::from_secs(3)); + ratatui::restore(); + Ok(()) +} + +fn render(area: Rect, frame: &mut ratatui::Frame) { + if area.width < 2 || area.height < 2 { + return; + } + + let horizontal_bar = area + .rows() + .next_back() + .unwrap_or(area) + .inner(Margin::new(1, 0)); + let vertical_bar = area + .columns() + .next_back() + .unwrap_or(area) + .inner(Margin::new(0, 1)); + + let block = Block::new() + .borders(Borders::ALL) + .title("styled scrollbars") + .border_style((Color::LightBlue, Color::Black)) + .style((Color::Gray, Color::Black)); + let content = block.inner(area).inner(Margin::new(2, 1)); + frame.render_widget(block, area); + + frame.render_widget( + Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors") + .style((Color::Gray, Color::Black)), + content, + ); + + let lengths = ScrollLengths { + content_len: 160, + viewport_len: 40, + }; + let horizontal = ScrollBar::horizontal(lengths) + .offset(48) + .arrows(ScrollBarArrows::Both) + .glyph_set(GlyphSet::box_drawing()) + .track_style((Color::Blue, Color::Black).into()) + .thumb_style((Color::Yellow, Modifier::BOLD).into()) + .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into()); + let vertical = ScrollBar::vertical(lengths) + .offset(80) + .arrows(ScrollBarArrows::Both) + .glyph_set(GlyphSet::box_drawing()) + .track_style((Color::Magenta, Color::Black).into()) + .thumb_style((Color::Cyan, Modifier::BOLD).into()) + .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into()); + + frame.render_widget(&horizontal, horizontal_bar); + frame.render_widget(&vertical, vertical_bar); +} diff --git a/tui-scrollbar/src/glyphs.rs b/tui-scrollbar/src/glyphs.rs index 8ea07d66..2a28e4dc 100644 --- a/tui-scrollbar/src/glyphs.rs +++ b/tui-scrollbar/src/glyphs.rs @@ -30,6 +30,9 @@ pub struct GlyphSet { impl GlyphSet { /// Minimal glyphs: no visible track by default. /// + /// Choose this when the thumb should stand out by color against a filled background instead of + /// a visible line. + /// /// This uses a space character for the track so the scrollbar is "all thumb", with the /// background color coming from `track_style`. /// @@ -53,6 +56,8 @@ impl GlyphSet { /// Glyphs that include box-drawing line symbols for the track. /// + /// Choose this when callers should see a visible track line behind the thumb. + /// /// ```plain /// [██──────] /// [🮋█▏─────] @@ -70,6 +75,9 @@ impl GlyphSet { /// Glyphs that mix standard block elements with legacy supplement glyphs. /// + /// Choose this when the terminal font supports [Symbols for Legacy Computing] and you want + /// precise 1/8th-cell rendering on every thumb edge. + /// /// Use this to get full 1/8th coverage for upper and right edges that the standard block set /// lacks; these glyphs come from [Symbols for Legacy Computing]. /// @@ -107,7 +115,7 @@ impl GlyphSet { /// Glyphs using only standard Unicode block elements. /// - /// Use this if your font lacks the legacy glyphs. + /// Choose this if your font lacks the legacy glyphs. /// /// The standard block set does not include 1/8th upper or right fills (those come from /// [Symbols for Legacy Computing]), so this set approximates upper and right partials by @@ -128,6 +136,8 @@ impl GlyphSet { /// [▕█▉─────] /// [─██─────] /// ``` + /// + /// [Symbols for Legacy Computing]: https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing pub const fn unicode() -> Self { let vertical_lower = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; let vertical_upper = ['▔', '▔', '▀', '▀', '▀', '▀', '█', '█']; diff --git a/tui-scrollbar/src/lib.rs b/tui-scrollbar/src/lib.rs index 6ffac2d1..8302056a 100644 --- a/tui-scrollbar/src/lib.rs +++ b/tui-scrollbar/src/lib.rs @@ -35,11 +35,19 @@ //! cargo add tui-scrollbar //! ``` //! +//! # Important +//! +//! - Zero lengths are treated as 1. +//! - Arrow endcaps are disabled by default; configure them with [`ScrollBarArrows`]. +//! - The default [`GlyphSet`] hides the track using spaces; use [`GlyphSet::box_drawing`] or +//! [`GlyphSet::unicode`] for a visible track. +//! - The default glyphs use [Symbols for Legacy Computing] for missing upper/right eighth blocks. +//! Use [`GlyphSet::unicode`] if you need only standard Unicode block elements. +//! //! # Quick start //! //! This example renders a vertical [`ScrollBar`] into a [`Buffer`] using a fixed track size and //! offset. Use it as a minimal template when you just need a thumb and track on screen. -//! If you prefer named arguments, use [`ScrollLengths`]. //! //! ```rust //! use ratatui_core::buffer::Buffer; @@ -76,7 +84,35 @@ //! axis. For many apps, those units are items or lines. The ratio between `viewport_len` and //! `content_len` is what matters, so any consistent unit works. //! -//! Zero lengths are treated as 1. +//! # Styling +//! +//! Style the track, thumb, and arrow endcaps directly on [`ScrollBar`]. See [`ScrollBar`] for a +//! full method map and more focused examples. +//! +//! Scrollbar glyphs are terminal characters. For visible track glyphs, thumb blocks, and arrow +//! symbols, `Style::fg` colors the glyph itself and `Style::bg` colors the cell behind it. The +//! default [`GlyphSet::minimal`] track renders spaces, so only the track background is visible in +//! empty track cells. Visible track glyph sets, such as [`GlyphSet::box_drawing`] and +//! [`GlyphSet::unicode`], can use foreground color for the track line. Thumb glyphs are block +//! characters, so `Style::fg` is usually the useful knob for thumb color; `Style::bg` still colors +//! the rest of the cell. With partial thumb glyphs, especially on a visible line track such as +//! [`GlyphSet::box_drawing`], that background can show at the ends of the thumb. Match the thumb +//! background to the track background unless that contrast is intentional. +//! +//! ```rust +//! use ratatui_core::style::{Color, Style}; +//! use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths}; +//! +//! let lengths = ScrollLengths { +//! content_len: 120, +//! viewport_len: 30, +//! }; +//! let scrollbar = ScrollBar::vertical(lengths) +//! .arrows(ScrollBarArrows::Both) +//! .track_style(Style::new().bg(Color::Black)) +//! .thumb_style(Style::new().fg(Color::Rgb(255, 158, 100))) +//! .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black)); +//! ``` //! //! # Layout integration //! @@ -161,9 +197,11 @@ //! //! # Glyph selection //! -//! The default glyphs include [Symbols for Legacy Computing] so the thumb can render upper/right -//! partial fills that are missing from the standard block set. Use [`GlyphSet`] when you want to -//! switch to a glyph set that avoids legacy symbols. +//! [`GlyphSet`] controls the track and thumb characters. The default glyphs include +//! [Symbols for Legacy Computing] so the thumb can render upper/right partial fills that are +//! missing from the standard block set. Use [`GlyphSet::box_drawing`] for a visible line track, or +//! [`GlyphSet::unicode`] when the terminal font should avoid [Symbols for Legacy Computing] +//! glyphs. //! //! ```rust //! use tui_scrollbar::{GlyphSet, ScrollBar, ScrollLengths}; @@ -195,28 +233,10 @@ //! - [`PointerEvent`], [`PointerEventKind`], [`PointerButton`] //! - [`ScrollWheel`], [`ScrollAxis`] //! -//! # Features -//! -//! - `crossterm`: enables crossterm mouse events (latest supported version, currently `crossterm` -//! 0.29). -//! - `crossterm_0_28`: enables crossterm mouse events using `crossterm` 0.28. -//! - `crossterm_0_29`: enables crossterm mouse events using `crossterm` 0.29. -//! -//! When multiple crossterm versions are enabled, the latest one is used. -//! The selected version is re-exported as `tui_scrollbar::crossterm`. -//! -//! # Important -//! -//! - Zero lengths are treated as 1. -//! - Arrow endcaps are disabled by default; configure them with [`ScrollBarArrows`]. -//! - The default [`GlyphSet`] hides the track using spaces; use [`GlyphSet::box_drawing`] or -//! [`GlyphSet::unicode`] for a visible track. -//! - The default glyphs use [Symbols for Legacy Computing] for missing upper/right eighth blocks. -//! Use [`GlyphSet::unicode`] if you need only standard Unicode block elements. -//! //! # See also //! //! - [tui-scrollbar examples] +//! - [`scrollbar_styled` example] //! - [`scrollbar_mouse` example] //! - [`scrollbar` example] //! - [`Widget`] @@ -247,6 +267,7 @@ //! [Contributing]: https://github.com/ratatui/tui-widgets/blob/main/CONTRIBUTING.md //! [Crate source]: https://github.com/ratatui/tui-widgets/blob/main/tui-scrollbar/src/lib.rs //! [`scrollbar_mouse` example]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples/scrollbar_mouse.rs +//! [`scrollbar_styled` example]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples/scrollbar_styled.rs //! [`scrollbar` example]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples/scrollbar.rs //! [tui-scrollbar examples]: https://github.com/ratatui/tui-widgets/tree/main/tui-scrollbar/examples //! [`Buffer`]: ratatui_core::buffer::Buffer diff --git a/tui-scrollbar/src/scrollbar/interaction.rs b/tui-scrollbar/src/scrollbar/interaction.rs index efb4748b..bd7513bc 100644 --- a/tui-scrollbar/src/scrollbar/interaction.rs +++ b/tui-scrollbar/src/scrollbar/interaction.rs @@ -22,7 +22,8 @@ use crate::ScrollLengths; impl ScrollBar { /// Handles a backend-agnostic scrollbar event. /// - /// Returns a [`ScrollCommand`] when the event should update the offset. + /// Returns a [`ScrollCommand`] when the event should update the caller-owned offset. This + /// method does not mutate your application state directly. /// /// Pointer events outside the track are ignored. Scroll wheel events are ignored unless the /// axis matches the scrollbar orientation. @@ -88,7 +89,8 @@ impl ScrollBar { /// Handles crossterm mouse events for this scrollbar. /// /// This helper converts crossterm events into [`ScrollEvent`] values before delegating to - /// [`Self::handle_event`]. + /// [`Self::handle_event`]. See the `scrollbar_mouse` example for a complete terminal event + /// loop with mouse capture. pub fn handle_mouse_event( &self, area: Rect, diff --git a/tui-scrollbar/src/scrollbar/mod.rs b/tui-scrollbar/src/scrollbar/mod.rs index 60a25135..def61052 100644 --- a/tui-scrollbar/src/scrollbar/mod.rs +++ b/tui-scrollbar/src/scrollbar/mod.rs @@ -1,14 +1,15 @@ //! Rendering and interaction for proportional scrollbars. //! -//! This module provides the widget, glyph selection, and interaction helpers. The pure math lives -//! in [`crate::metrics`]. +//! This module provides the widget and interaction helpers. Glyph configuration lives in +//! [`crate::GlyphSet`], and the pure scrollbar geometry lives in [`crate::ScrollMetrics`]. //! -//! # How the parts interact +//! # Local model //! -//! 1. Your app owns `content_len`, `viewport_len`, and `offset`. -//! 2. [`ScrollMetrics`] converts them into thumb geometry. -//! 3. [`ScrollBar`] renders using the selected [`GlyphSet`]. -//! 4. Input events update `offset` via [`ScrollCommand`]. +//! 1. [`ScrollBar`] stores orientation, logical lengths, offset, styles, glyphs, and interaction +//! behavior. +//! 2. [`ScrollMetrics`] converts the current lengths and offset into thumb geometry. +//! 3. Rendering chooses track, thumb, and arrow glyphs from [`GlyphSet`]. +//! 4. Input helpers return [`ScrollCommand`] values for the application to apply. //! //! The scrollbar renders only a single row or column. If you provide a larger [`Rect`], it will //! still render into the first row/column of that area. @@ -29,10 +30,6 @@ //! - Arrow endcaps consume track space; the inner track is used for metrics and hit testing so //! thumb math stays consistent regardless of arrows. //! -//! Partial glyph selection uses [`CellFill::Partial`]: `start == 0` means the partial fill begins -//! at the leading edge (top/left), so the upper/left glyphs are chosen. Non-zero `start` uses the -//! lower/right glyphs to indicate a trailing-edge fill. -//! //! Drag operations store a "grab offset" in subcells (1/8 of a cell; see [`crate::SUBCELL`]) so the //! thumb does not jump when the pointer starts dragging; subsequent drag events subtract that //! offset to keep the grab point stable. @@ -131,15 +128,36 @@ struct ArrowLayout { /// A proportional scrollbar widget with fractional thumb rendering. /// -/// # Key methods +/// # Method map +/// +/// ## Construction /// /// - [`Self::new`] /// - [`Self::orientation`] -/// - [`Self::arrows`] +/// - [`Self::vertical`] +/// - [`Self::horizontal`] +/// +/// ## Position and lengths +/// /// - [`Self::content_len`] /// - [`Self::viewport_len`] /// - [`Self::offset`] /// +/// ## Appearance +/// +/// - [`Self::track_style`] +/// - [`Self::thumb_style`] +/// - [`Self::arrow_style`] +/// - [`Self::glyph_set`] +/// - [`Self::arrows`] +/// +/// ## Interaction +/// +/// - [`Self::handle_event`] +/// - [`Self::handle_mouse_event`], when a crossterm feature is enabled +/// - [`Self::track_click_behavior`] +/// - [`Self::scroll_step`] +/// /// # Important /// /// - `content_len` and `viewport_len` are in logical units. @@ -160,12 +178,39 @@ struct ArrowLayout { /// Track glyphs use `track_style`. Thumb glyphs use `thumb_style`. Arrow endcaps use /// `arrow_style`, which defaults to white on dark gray. /// +/// Scrollbar glyphs are terminal characters. For visible track glyphs, thumb blocks, and arrow +/// symbols, `Style::fg` colors the glyph itself and `Style::bg` colors the cell behind it. The +/// default [`GlyphSet::minimal`] track renders spaces, so only the track background is visible in +/// empty track cells. Visible track glyph sets, such as [`GlyphSet::box_drawing`] and +/// [`GlyphSet::unicode`], can use foreground color for the track line. Thumb glyphs are block +/// characters, so `Style::fg` is usually the useful knob for thumb color; `Style::bg` still colors +/// the rest of the cell. With partial thumb glyphs, especially on a visible line track such as +/// [`GlyphSet::box_drawing`], that background can show at the ends of the thumb. Match the thumb +/// background to the track background unless that contrast is intentional. +/// +/// ```rust +/// use ratatui_core::style::{Color, Style}; +/// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths}; +/// +/// let lengths = ScrollLengths { +/// content_len: 120, +/// viewport_len: 30, +/// }; +/// let scrollbar = ScrollBar::vertical(lengths) +/// .arrows(ScrollBarArrows::Both) +/// .track_style(Style::new().bg(Color::Black)) +/// .thumb_style(Style::new().fg(Color::Rgb(255, 158, 100))) +/// .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black)); +/// ``` +/// /// # State /// /// This widget is stateless. Pointer drag state lives in [`crate::ScrollBarInteraction`]. /// /// # Examples /// +/// Minimal rendering only needs an area, lengths, an offset, and a buffer. +/// /// ```rust /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; @@ -265,6 +310,9 @@ pub struct ScrollBar { impl ScrollBar { /// Creates a scrollbar with the given orientation and lengths. /// + /// Use [`Self::vertical`] or [`Self::horizontal`] when the orientation is known at the call + /// site. + /// /// Zero lengths are treated as 1. /// /// ```rust @@ -293,16 +341,52 @@ impl ScrollBar { } /// Creates a vertical scrollbar with the given content and viewport lengths. + /// + /// The track length is derived from the render area's height. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths); + /// ``` pub fn vertical(lengths: crate::ScrollLengths) -> Self { Self::new(ScrollBarOrientation::Vertical, lengths) } /// Creates a horizontal scrollbar with the given content and viewport lengths. + /// + /// The track length is derived from the render area's width. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::horizontal(lengths); + /// ``` pub fn horizontal(lengths: crate::ScrollLengths) -> Self { Self::new(ScrollBarOrientation::Horizontal, lengths) } /// Sets the scrollbar orientation. + /// + /// This is mostly useful when sharing a builder chain and choosing the orientation later. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).orientation(ScrollBarOrientation::Horizontal); + /// ``` pub const fn orientation(mut self, orientation: ScrollBarOrientation) -> Self { self.orientation = orientation; self @@ -313,6 +397,16 @@ impl ScrollBar { /// Larger values shrink the thumb, while smaller values enlarge it. /// /// Zero values are treated as 1. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).content_len(240); + /// ``` pub const fn content_len(mut self, content_len: usize) -> Self { self.content_len = content_len; self @@ -323,6 +417,16 @@ impl ScrollBar { /// When `viewport_len >= content_len`, the thumb fills the track. /// /// Zero values are treated as 1. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).viewport_len(60); + /// ``` pub const fn viewport_len(mut self, viewport_len: usize) -> Self { self.viewport_len = viewport_len; self @@ -330,7 +434,18 @@ impl ScrollBar { /// Sets the current scroll offset in logical units. /// - /// Offsets are clamped to `content_len - viewport_len` during rendering. + /// Offsets are clamped to `content_len - viewport_len` during rendering and input handling, + /// not when this builder is called. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).offset(30); + /// ``` pub const fn offset(mut self, offset: usize) -> Self { self.offset = offset; self @@ -338,7 +453,18 @@ impl ScrollBar { /// Sets the style applied to track glyphs. /// - /// Track styling applies only where the thumb is not rendered. + /// Track styling applies only to cells where the thumb is not rendered. + /// + /// ```rust + /// use ratatui_core::style::{Color, Style}; + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).track_style(Style::new().bg(Color::Black)); + /// ``` pub const fn track_style(mut self, style: Style) -> Self { self.track_style = style; self @@ -346,7 +472,22 @@ impl ScrollBar { /// Sets the style applied to thumb glyphs. /// - /// Thumb styling overrides track styling for covered cells. + /// Thumb styling applies to full and partial thumb cells. Thumb glyphs are block characters, + /// so `Style::fg` usually controls the visible thumb color. Use `Style::bg` only when the + /// cell behind the glyph should differ from the track. On partial thumb cells, the background + /// can show at the thumb ends. + /// + /// ```rust + /// use ratatui_core::style::{Color, Style}; + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = + /// ScrollBar::vertical(lengths).thumb_style(Style::new().fg(Color::Rgb(255, 158, 100))); + /// ``` pub const fn thumb_style(mut self, style: Style) -> Self { self.thumb_style = style; self @@ -354,7 +495,21 @@ impl ScrollBar { /// Sets the style applied to arrow glyphs. /// - /// Defaults to white on dark gray. + /// Arrow endcaps render only when enabled with [`Self::arrows`]. If no arrow style is + /// configured internally, arrows fall back to the track style. + /// + /// ```rust + /// use ratatui_core::style::{Color, Style}; + /// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths) + /// .arrows(ScrollBarArrows::Both) + /// .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black)); + /// ``` pub const fn arrow_style(mut self, style: Style) -> Self { self.arrow_style = Some(style); self @@ -362,14 +517,39 @@ impl ScrollBar { /// Selects the glyph set used to render the track and thumb. /// - /// [`GlyphSet::symbols_for_legacy_computing`] uses additional symbols for 1/8th upper/right - /// fills. Use [`GlyphSet::unicode`] if you want to avoid the legacy supplement. + /// [`GlyphSet::symbols_for_legacy_computing`] uses [Symbols for Legacy Computing] for 1/8th + /// upper/right fills. Use [`GlyphSet::unicode`] if you want to avoid the legacy supplement, or + /// [`GlyphSet::box_drawing`] when you want a visible line track. + /// + /// [Symbols for Legacy Computing]: https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing + /// + /// ```rust + /// use tui_scrollbar::{GlyphSet, ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).glyph_set(GlyphSet::unicode()); + /// ``` pub const fn glyph_set(mut self, glyph_set: GlyphSet) -> Self { self.glyph_set = glyph_set; self } /// Sets which arrow endcaps are rendered. + /// + /// Each enabled arrow reserves one cell at the start or end of the track. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both); + /// ``` pub const fn arrows(mut self, arrows: ScrollBarArrows) -> Self { self.arrows = arrows; self @@ -379,6 +559,19 @@ impl ScrollBar { /// /// Use [`TrackClickBehavior::Page`] for classic page-up/down behavior, or /// [`TrackClickBehavior::JumpToClick`] to move the thumb toward the click. + /// + /// This does not affect clicks on the thumb or arrow endcaps. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollLengths, TrackClickBehavior}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = + /// ScrollBar::vertical(lengths).track_click_behavior(TrackClickBehavior::JumpToClick); + /// ``` pub const fn track_click_behavior(mut self, behavior: TrackClickBehavior) -> Self { self.track_click_behavior = behavior; self @@ -386,7 +579,18 @@ impl ScrollBar { /// Sets the scroll step used for wheel events. /// - /// The wheel delta is multiplied by this value (in your logical units) and then clamped. + /// The wheel delta is multiplied by this value (in your logical units) and then clamped. A + /// step of 0 is normalized to 1. + /// + /// ```rust + /// use tui_scrollbar::{ScrollBar, ScrollLengths}; + /// + /// let lengths = ScrollLengths { + /// content_len: 120, + /// viewport_len: 40, + /// }; + /// let scrollbar = ScrollBar::vertical(lengths).scroll_step(8); + /// ``` pub fn scroll_step(mut self, step: usize) -> Self { self.scroll_step = step.max(1); self diff --git a/tui-scrollbar/src/scrollbar/render.rs b/tui-scrollbar/src/scrollbar/render.rs index 0868954d..f6cae33a 100644 --- a/tui-scrollbar/src/scrollbar/render.rs +++ b/tui-scrollbar/src/scrollbar/render.rs @@ -141,6 +141,7 @@ impl ScrollBar { mod tests { use ratatui_core::buffer::Buffer; use ratatui_core::layout::Rect; + use ratatui_core::style::{Color, Style}; use super::*; use crate::{GlyphSet, ScrollBarArrows, ScrollLengths}; @@ -208,6 +209,37 @@ mod tests { assert_eq!(buf, expected); } + #[test] + fn render_uses_custom_thumb_style_for_full_and_partial_cells() { + let track_style = Style::new().bg(Color::Rgb(10, 20, 30)); + let thumb_style = Style::new() + .fg(Color::Rgb(255, 158, 100)) + .bg(Color::Rgb(10, 20, 30)); + let arrow_style = Style::new() + .fg(Color::Rgb(158, 206, 106)) + .bg(Color::Rgb(10, 20, 30)); + let scrollbar = ScrollBar::horizontal(ScrollLengths { + content_len: 10, + viewport_len: 3, + }) + .arrows(ScrollBarArrows::Both) + .offset(1) + .track_style(track_style) + .thumb_style(thumb_style) + .arrow_style(arrow_style); + + let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1)); + (&scrollbar).render(buf.area, &mut buf); + + let mut expected = Buffer::with_lines(vec!["◀🮉▌ ▶"]); + expected.set_style(expected.area, track_style); + expected[(0, 0)].set_style(arrow_style); + expected[(1, 0)].set_style(thumb_style); + expected[(2, 0)].set_style(thumb_style); + expected[(5, 0)].set_style(arrow_style); + assert_eq!(buf, expected); + } + #[test] fn render_horizontal_fractional_thumb_box_drawing_track() { let scrollbar = ScrollBar::horizontal(ScrollLengths {