Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions tui-scrollbar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
//! - [`ScrollMetrics`]: pure math for thumb sizing and hit testing.
//! - [`GlyphSet`]: glyph selection for track and thumb rendering.
//! - [`ScrollBarArrows`]: arrow endcap configuration.
//! - [`ScrollBarStyle`]: style configuration for the scrollbar.
//!
//! ## Enums and events
//!
Expand Down Expand Up @@ -312,5 +313,7 @@ pub use crate::input::{
ScrollEvent, ScrollWheel,
};
pub use crate::lengths::ScrollLengths;
pub use crate::metrics::{CellFill, HitTest, SUBCELL, ScrollMetrics};
pub use crate::scrollbar::{ScrollBar, ScrollBarArrows, ScrollBarOrientation, TrackClickBehavior};
pub use crate::metrics::{CellFill, HitTest, ScrollMetrics, SUBCELL};
pub use crate::scrollbar::{
ScrollBar, ScrollBarArrows, ScrollBarOrientation, ScrollBarStyle, TrackClickBehavior,
};
198 changes: 167 additions & 31 deletions tui-scrollbar/src/scrollbar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,108 @@ struct ArrowLayout {
end: Option<(u16, u16)>,
}

/// Style of the scrollbar (track, thumb, and optional arrow endcaps).
///
/// # Reading and updating
///
/// The `track_style`, `thumb_style`, and `arrow_style` fields are public, so you can read or
/// assign them directly (including in struct update expressions).
///
/// Methods [`Self::track_style`], [`Self::thumb_style`], and [`Self::arrow_style`] that take a
/// [`Style`] and return `Self` are **builders** for chained construction.
///
/// Use [`Self::get_track_style`], [`Self::get_thumb_style`], and [`Self::get_arrow_style`] to read
/// by method call, and [`Self::set_track_style`], [`Self::set_thumb_style`], [`Self::set_arrow_style`],
/// and [`Self::clear_arrow_style`] to update a `ScrollBarStyle` in place without consuming it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScrollBarStyle {
/// Style applied to track glyphs.
pub track_style: Style,
/// Style applied to thumb glyphs.
pub thumb_style: Style,
/// Style applied to arrow glyphs.
pub arrow_style: Option<Style>,
}

impl ScrollBarStyle {
/// Creates a new scrollbar style with default colors.
pub const fn new() -> Self {
ScrollBarStyle {
track_style: Style::new().bg(Color::DarkGray),
thumb_style: Style::new().fg(Color::White).bg(Color::DarkGray),
arrow_style: Some(Style::new().fg(Color::White).bg(Color::DarkGray)),
}
}

/// Sets the style applied to track glyphs.
///
/// For reading or in-place updates, see [`Self::get_track_style`] and [`Self::set_track_style`].
pub const fn track_style(mut self, style: Style) -> Self {
self.track_style = style;
self
}

/// Sets the style applied to thumb glyphs.
///
/// For reading or in-place updates, see [`Self::get_thumb_style`] and [`Self::set_thumb_style`].
pub const fn thumb_style(mut self, style: Style) -> Self {
self.thumb_style = style;
self
}

/// Sets the style applied to arrow glyphs.
///
/// For reading, clearing, or in-place updates, see [`Self::get_arrow_style`],
/// [`Self::set_arrow_style`], and [`Self::clear_arrow_style`].
pub const fn arrow_style(mut self, style: Style) -> Self {
self.arrow_style = Some(style);
self
}

/// Returns the style applied to track glyphs.
pub const fn get_track_style(&self) -> Style {
self.track_style
}

/// Returns the style applied to thumb glyphs.
pub const fn get_thumb_style(&self) -> Style {
self.thumb_style
}

/// Returns the style applied to arrow glyphs, if set.
pub const fn get_arrow_style(&self) -> Option<Style> {
self.arrow_style
}

/// Sets the style applied to track glyphs in place.
pub const fn set_track_style(&mut self, style: Style) {
self.track_style = style;
}

/// Sets the style applied to thumb glyphs in place.
pub const fn set_thumb_style(&mut self, style: Style) {
self.thumb_style = style;
}

/// Sets the style applied to arrow glyphs in place.
///
/// Pass [`None`] so arrow cells use the track style at render time (see [`crate::ScrollBar`]).
pub const fn set_arrow_style(&mut self, style: Option<Style>) {
self.arrow_style = style;
}

/// Clears the dedicated arrow style so arrows use the track style when rendered.
pub const fn clear_arrow_style(&mut self) {
self.arrow_style = None;
}
}

impl Default for ScrollBarStyle {
fn default() -> Self {
Self::new()
}
}

/// A proportional scrollbar widget with fractional thumb rendering.
///
/// # Method map
Expand All @@ -142,6 +244,7 @@ struct ArrowLayout {
/// - [`Self::content_len`]
/// - [`Self::viewport_len`]
/// - [`Self::offset`]
/// - [`Self::styles`]
///
/// ## Appearance
///
Expand Down Expand Up @@ -178,29 +281,23 @@ 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.
/// You can either chain style methods directly on the `ScrollBar` or pass a custom
/// [`ScrollBarStyle`] constructed using [`ScrollBarStyle::new()`].
///
/// The methods [`ScrollBar::track_style`], [`ScrollBar::thumb_style`], and
/// [`ScrollBar::arrow_style`] are **builders**: they take a [`Style`] and return a new scrollbar.
/// To read the active styles from an existing scrollbar, use [`ScrollBar::styles`].
///
/// ```rust
/// use ratatui_core::style::{Color, Style};
/// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
/// use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths, ScrollBarStyle};
///
/// 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));
/// const CUSTOM_STYLE: ScrollBarStyle = ScrollBarStyle::new()
/// .track_style(Style::new().bg(Color::Blue))
/// .thumb_style(Style::new().bg(Color::Red));
///
/// let lengths = ScrollLengths { content_len: 120, viewport_len: 40 };
/// let scrollbar = ScrollBar::vertical(lengths).style(CUSTOM_STYLE);
/// ```
///
/// # State
Expand Down Expand Up @@ -298,9 +395,7 @@ pub struct ScrollBar {
content_len: usize,
viewport_len: usize,
offset: usize,
track_style: Style,
thumb_style: Style,
arrow_style: Option<Style>,
style: ScrollBarStyle,
glyph_set: GlyphSet,
arrows: ScrollBarArrows,
track_click_behavior: TrackClickBehavior,
Expand Down Expand Up @@ -330,9 +425,7 @@ impl ScrollBar {
content_len: lengths.content_len,
viewport_len: lengths.viewport_len,
offset: 0,
track_style: Style::new().bg(Color::DarkGray),
thumb_style: Style::new().fg(Color::White).bg(Color::DarkGray),
arrow_style: Some(Style::new().fg(Color::White).bg(Color::DarkGray)),
style: ScrollBarStyle::default(),
glyph_set: GlyphSet::default(),
arrows: ScrollBarArrows::default(),
track_click_behavior: TrackClickBehavior::Page,
Expand Down Expand Up @@ -466,7 +559,7 @@ impl ScrollBar {
/// 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.style = self.style.track_style(style);
self
}

Expand All @@ -489,7 +582,7 @@ impl 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.style = self.style.thumb_style(style);
self
}

Expand All @@ -511,10 +604,29 @@ impl ScrollBar {
/// .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.style = self.style.arrow_style(style);
self
}

/// Sets the style of the scrollbar components.
///
/// Defaults to [`ScrollBarStyle::default()`].
///
/// For the configured style, see [`Self::styles`].
pub const fn style(mut self, style: ScrollBarStyle) -> Self {
self.style = style;
self
}

/// Returns the scrollbar’s configured [`ScrollBarStyle`] (track, thumb, and arrow styles).
///
/// This is the supported way to **read** styles from a [`ScrollBar`]. Builder methods named
/// [`Self::track_style`], [`Self::thumb_style`], and [`Self::arrow_style`] take a [`Style`]
/// argument and return an updated scrollbar; they are not getters.
pub const fn styles(&self) -> &ScrollBarStyle {
&self.style
}

/// Selects the glyph set used to render the track and thumb.
///
/// [`GlyphSet::symbols_for_legacy_computing`] uses [Symbols for Legacy Computing] for 1/8th
Expand Down Expand Up @@ -667,6 +779,12 @@ mod tests {
.track_style(track_style)
.thumb_style(thumb_style)
.arrow_style(arrow_style)
.style(
ScrollBarStyle::new()
.track_style(track_style)
.thumb_style(thumb_style)
.arrow_style(arrow_style),
)
.glyph_set(glyphs.clone())
.arrows(ScrollBarArrows::End)
.track_click_behavior(TrackClickBehavior::JumpToClick)
Expand All @@ -676,9 +794,9 @@ mod tests {
assert_eq!(scrollbar.content_len, 20);
assert_eq!(scrollbar.viewport_len, 5);
assert_eq!(scrollbar.offset, 3);
assert_eq!(scrollbar.track_style, track_style);
assert_eq!(scrollbar.thumb_style, thumb_style);
assert_eq!(scrollbar.arrow_style, Some(arrow_style));
assert_eq!(scrollbar.styles().track_style, track_style);
assert_eq!(scrollbar.styles().thumb_style, thumb_style);
assert_eq!(scrollbar.styles().arrow_style, Some(arrow_style));
assert_eq!(scrollbar.glyph_set, glyphs);
assert_eq!(scrollbar.arrows, ScrollBarArrows::End);
assert_eq!(
Expand All @@ -688,6 +806,24 @@ mod tests {
assert_eq!(scrollbar.scroll_step, 1);
}

#[test]
fn scroll_bar_style_getters_and_setters() {
let mut style = ScrollBarStyle::new()
.track_style(Style::new().fg(Color::Yellow))
.thumb_style(Style::new().bg(Color::Cyan));
style.set_arrow_style(Some(Style::new().fg(Color::Magenta)));

assert_eq!(style.get_track_style(), style.track_style);
assert_eq!(style.get_thumb_style(), style.thumb_style);
assert_eq!(style.get_arrow_style(), style.arrow_style);

style.clear_arrow_style();
assert_eq!(style.get_arrow_style(), None);

style.set_track_style(Style::new().fg(Color::Red));
assert_eq!(style.get_track_style(), Style::new().fg(Color::Red));
}

#[test]
fn constructors_set_orientation() {
let lengths = ScrollLengths {
Expand Down
Loading