tui_scrollbar/scrollbar/mod.rs
1//! Rendering and interaction for proportional scrollbars.
2//!
3//! This module provides the widget and interaction helpers. Glyph configuration lives in
4//! [`crate::GlyphSet`], and the pure scrollbar geometry lives in [`crate::ScrollMetrics`].
5//!
6//! # Local model
7//!
8//! 1. [`ScrollBar`] stores orientation, logical lengths, offset, styles, glyphs, and interaction
9//! behavior.
10//! 2. [`ScrollMetrics`] converts the current lengths and offset into thumb geometry.
11//! 3. Rendering chooses track, thumb, and arrow glyphs from [`GlyphSet`].
12//! 4. Input helpers return [`ScrollCommand`] values for the application to apply.
13//!
14//! The scrollbar renders only a single row or column. If you provide a larger [`Rect`], it will
15//! still render into the first row/column of that area.
16//!
17//! ## Layout choices
18//!
19//! The widget treats the provided area as the track container. When arrows are enabled, one cell
20//! at each end is reserved for the endcaps and the remaining inner area is used for the thumb.
21//!
22//! Arrow endcaps are optional. When enabled, they consume one cell at the start/end of the track,
23//! and the thumb renders inside the remaining inner area.
24//!
25//! ## Interaction choices
26//!
27//! - The widget is stateless: it renders from inputs and returns commands instead of mutating
28//! scroll offsets. This keeps control with the application.
29//! - Dragging stores a grab offset in subcells so the thumb does not jump under the pointer.
30//! - Arrow endcaps consume track space; the inner track is used for metrics and hit testing so
31//! thumb math stays consistent regardless of arrows.
32//!
33//! Drag operations store a "grab offset" in subcells (1/8 of a cell; see [`crate::SUBCELL`]) so the
34//! thumb does not jump when the pointer starts dragging; subsequent drag events subtract that
35//! offset to keep the grab point stable.
36//!
37//! Wheel events are ignored unless their axis matches the scrollbar orientation. Positive deltas
38//! scroll down/right.
39//!
40//! The example below renders a vertical scrollbar into a buffer. It demonstrates how the widget
41//! uses `content_len`, `viewport_len`, and `offset` to decide the thumb size and position.
42//!
43//! ```rust
44//! use ratatui_core::buffer::Buffer;
45//! use ratatui_core::layout::Rect;
46//! use ratatui_core::widgets::Widget;
47//! use tui_scrollbar::{ScrollBar, ScrollLengths};
48//!
49//! let area = Rect::new(0, 0, 1, 4);
50//! let lengths = ScrollLengths {
51//! content_len: 120,
52//! viewport_len: 40,
53//! };
54//! let scrollbar = ScrollBar::vertical(lengths).offset(20);
55//!
56//! let mut buffer = Buffer::empty(area);
57//! scrollbar.render(area, &mut buffer);
58//! ```
59//!
60//! [`Rect`]: ratatui_core::layout::Rect
61
62use ratatui_core::layout::Rect;
63use ratatui_core::style::{Color, Style};
64
65use crate::glyphs::GlyphSet;
66
67mod interaction;
68mod render;
69
70/// Axis the scrollbar is laid out on.
71///
72/// Orientation determines whether the track length is derived from height or width.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum ScrollBarOrientation {
75 /// A vertical scrollbar that fills a single column.
76 Vertical,
77 /// A horizontal scrollbar that fills a single row.
78 Horizontal,
79}
80
81/// Behavior when the user clicks on the track outside the thumb.
82///
83/// Page clicks move by `viewport_len`. Jump-to-click centers the thumb near the click.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum TrackClickBehavior {
86 /// Move by one viewport length toward the click position.
87 Page,
88 /// Jump the thumb toward the click position.
89 JumpToClick,
90}
91
92/// Which arrow endcaps to render on the track.
93#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
94pub enum ScrollBarArrows {
95 /// Do not render arrow endcaps.
96 #[default]
97 None,
98 /// Render the arrow at the start of the track (top/left).
99 Start,
100 /// Render the arrow at the end of the track (bottom/right).
101 End,
102 /// Render arrows at both ends of the track.
103 Both,
104}
105
106impl ScrollBarArrows {
107 const fn has_start(self) -> bool {
108 matches!(self, Self::Start | Self::Both)
109 }
110
111 const fn has_end(self) -> bool {
112 matches!(self, Self::End | Self::Both)
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117enum ArrowHit {
118 Start,
119 End,
120}
121
122#[derive(Debug, Clone, Copy)]
123struct ArrowLayout {
124 track_area: Rect,
125 start: Option<(u16, u16)>,
126 end: Option<(u16, u16)>,
127}
128
129/// A proportional scrollbar widget with fractional thumb rendering.
130///
131/// # Method map
132///
133/// ## Construction
134///
135/// - [`Self::new`]
136/// - [`Self::orientation`]
137/// - [`Self::vertical`]
138/// - [`Self::horizontal`]
139///
140/// ## Position and lengths
141///
142/// - [`Self::content_len`]
143/// - [`Self::viewport_len`]
144/// - [`Self::offset`]
145///
146/// ## Appearance
147///
148/// - [`Self::track_style`]
149/// - [`Self::thumb_style`]
150/// - [`Self::arrow_style`]
151/// - [`Self::glyph_set`]
152/// - [`Self::arrows`]
153///
154/// ## Interaction
155///
156/// - [`Self::handle_event`]
157/// - [`Self::handle_mouse_event`], when a crossterm feature is enabled
158/// - [`Self::track_click_behavior`]
159/// - [`Self::scroll_step`]
160///
161/// # Important
162///
163/// - `content_len` and `viewport_len` are in logical units.
164/// - Zero values are treated as 1.
165/// - The scrollbar renders into a single row or column.
166///
167/// # Behavior
168///
169/// The thumb length is proportional to `viewport_len / content_len` and clamped to at least one
170/// full cell for usability. When `content_len <= viewport_len`, the thumb fills the track. Areas
171/// with zero width or height render nothing.
172///
173/// Arrow endcaps, when enabled, consume one cell at the start/end of the track. The thumb and
174/// track render in the remaining inner area. Clicking an arrow steps the offset by `scroll_step`.
175///
176/// # Styling
177///
178/// Track glyphs use `track_style`. Thumb glyphs use `thumb_style`. Arrow endcaps use
179/// `arrow_style`, which defaults to white on dark gray.
180///
181/// Scrollbar glyphs are terminal characters. For visible track glyphs, thumb blocks, and arrow
182/// symbols, `Style::fg` colors the glyph itself and `Style::bg` colors the cell behind it. The
183/// default [`GlyphSet::minimal`] track renders spaces, so only the track background is visible in
184/// empty track cells. Visible track glyph sets, such as [`GlyphSet::box_drawing`] and
185/// [`GlyphSet::unicode`], can use foreground color for the track line. Thumb glyphs are block
186/// characters, so `Style::fg` is usually the useful knob for thumb color; `Style::bg` still colors
187/// the rest of the cell. With partial thumb glyphs, especially on a visible line track such as
188/// [`GlyphSet::box_drawing`], that background can show at the ends of the thumb. Match the thumb
189/// background to the track background unless that contrast is intentional.
190///
191/// ```rust
192/// use ratatui_core::style::{Color, Style};
193/// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
194///
195/// let lengths = ScrollLengths {
196/// content_len: 120,
197/// viewport_len: 30,
198/// };
199/// let scrollbar = ScrollBar::vertical(lengths)
200/// .arrows(ScrollBarArrows::Both)
201/// .track_style(Style::new().bg(Color::Black))
202/// .thumb_style(Style::new().fg(Color::Rgb(255, 158, 100)))
203/// .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black));
204/// ```
205///
206/// # State
207///
208/// This widget is stateless. Pointer drag state lives in [`crate::ScrollBarInteraction`].
209///
210/// # Examples
211///
212/// Minimal rendering only needs an area, lengths, an offset, and a buffer.
213///
214/// ```rust
215/// use ratatui_core::buffer::Buffer;
216/// use ratatui_core::layout::Rect;
217/// use ratatui_core::widgets::Widget;
218/// use tui_scrollbar::{ScrollBar, ScrollLengths};
219///
220/// let area = Rect::new(0, 0, 1, 5);
221/// let lengths = ScrollLengths {
222/// content_len: 200,
223/// viewport_len: 40,
224/// };
225/// let scrollbar = ScrollBar::vertical(lengths).offset(60);
226///
227/// let mut buffer = Buffer::empty(area);
228/// scrollbar.render(area, &mut buffer);
229/// ```
230///
231/// ## Updating offsets on input
232///
233/// This is the typical pattern for pointer handling: feed events to the scrollbar and apply the
234/// returned command to your stored offset.
235///
236/// ```rust,no_run
237/// use ratatui_core::layout::Rect;
238/// use tui_scrollbar::{
239/// PointerButton, PointerEvent, PointerEventKind, ScrollBar, ScrollBarInteraction,
240/// ScrollCommand, ScrollEvent, ScrollLengths,
241/// };
242///
243/// let area = Rect::new(0, 0, 1, 10);
244/// let lengths = ScrollLengths {
245/// content_len: 400,
246/// viewport_len: 80,
247/// };
248/// let scrollbar = ScrollBar::vertical(lengths).offset(0);
249/// let mut interaction = ScrollBarInteraction::new();
250/// let mut offset = 0;
251///
252/// let event = ScrollEvent::Pointer(PointerEvent {
253/// column: 0,
254/// row: 3,
255/// kind: PointerEventKind::Down,
256/// button: PointerButton::Primary,
257/// });
258///
259/// if let Some(ScrollCommand::SetOffset(next)) =
260/// scrollbar.handle_event(area, event, &mut interaction)
261/// {
262/// offset = next;
263/// }
264/// # let _ = offset;
265/// ```
266///
267/// ## Track click behavior
268///
269/// Choose between classic page jumps or jump-to-click behavior.
270///
271/// ```rust
272/// use tui_scrollbar::{ScrollBar, ScrollLengths, TrackClickBehavior};
273///
274/// let lengths = ScrollLengths {
275/// content_len: 10,
276/// viewport_len: 5,
277/// };
278/// let scrollbar =
279/// ScrollBar::vertical(lengths).track_click_behavior(TrackClickBehavior::JumpToClick);
280/// ```
281///
282/// ## Arrow endcaps
283///
284/// Arrow endcaps are optional. When enabled, they reserve one cell at each end of the track.
285///
286/// ```rust
287/// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
288///
289/// let lengths = ScrollLengths {
290/// content_len: 120,
291/// viewport_len: 24,
292/// };
293/// let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both);
294/// ```
295#[derive(Debug, Clone, PartialEq, Eq)]
296pub struct ScrollBar {
297 orientation: ScrollBarOrientation,
298 content_len: usize,
299 viewport_len: usize,
300 offset: usize,
301 track_style: Style,
302 thumb_style: Style,
303 arrow_style: Option<Style>,
304 glyph_set: GlyphSet,
305 arrows: ScrollBarArrows,
306 track_click_behavior: TrackClickBehavior,
307 scroll_step: usize,
308}
309
310impl ScrollBar {
311 /// Creates a scrollbar with the given orientation and lengths.
312 ///
313 /// Use [`Self::vertical`] or [`Self::horizontal`] when the orientation is known at the call
314 /// site.
315 ///
316 /// Zero lengths are treated as 1.
317 ///
318 /// ```rust
319 /// use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths};
320 ///
321 /// let lengths = ScrollLengths {
322 /// content_len: 120,
323 /// viewport_len: 40,
324 /// };
325 /// let scrollbar = ScrollBar::new(ScrollBarOrientation::Vertical, lengths);
326 /// ```
327 pub fn new(orientation: ScrollBarOrientation, lengths: crate::ScrollLengths) -> Self {
328 Self {
329 orientation,
330 content_len: lengths.content_len,
331 viewport_len: lengths.viewport_len,
332 offset: 0,
333 track_style: Style::new().bg(Color::DarkGray),
334 thumb_style: Style::new().fg(Color::White).bg(Color::DarkGray),
335 arrow_style: Some(Style::new().fg(Color::White).bg(Color::DarkGray)),
336 glyph_set: GlyphSet::default(),
337 arrows: ScrollBarArrows::default(),
338 track_click_behavior: TrackClickBehavior::Page,
339 scroll_step: 1,
340 }
341 }
342
343 /// Creates a vertical scrollbar with the given content and viewport lengths.
344 ///
345 /// The track length is derived from the render area's height.
346 ///
347 /// ```rust
348 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
349 ///
350 /// let lengths = ScrollLengths {
351 /// content_len: 120,
352 /// viewport_len: 40,
353 /// };
354 /// let scrollbar = ScrollBar::vertical(lengths);
355 /// ```
356 pub fn vertical(lengths: crate::ScrollLengths) -> Self {
357 Self::new(ScrollBarOrientation::Vertical, lengths)
358 }
359
360 /// Creates a horizontal scrollbar with the given content and viewport lengths.
361 ///
362 /// The track length is derived from the render area's width.
363 ///
364 /// ```rust
365 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
366 ///
367 /// let lengths = ScrollLengths {
368 /// content_len: 120,
369 /// viewport_len: 40,
370 /// };
371 /// let scrollbar = ScrollBar::horizontal(lengths);
372 /// ```
373 pub fn horizontal(lengths: crate::ScrollLengths) -> Self {
374 Self::new(ScrollBarOrientation::Horizontal, lengths)
375 }
376
377 /// Sets the scrollbar orientation.
378 ///
379 /// This is mostly useful when sharing a builder chain and choosing the orientation later.
380 ///
381 /// ```rust
382 /// use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths};
383 ///
384 /// let lengths = ScrollLengths {
385 /// content_len: 120,
386 /// viewport_len: 40,
387 /// };
388 /// let scrollbar = ScrollBar::vertical(lengths).orientation(ScrollBarOrientation::Horizontal);
389 /// ```
390 pub const fn orientation(mut self, orientation: ScrollBarOrientation) -> Self {
391 self.orientation = orientation;
392 self
393 }
394
395 /// Sets the total scrollable content length in logical units.
396 ///
397 /// Larger values shrink the thumb, while smaller values enlarge it.
398 ///
399 /// Zero values are treated as 1.
400 ///
401 /// ```rust
402 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
403 ///
404 /// let lengths = ScrollLengths {
405 /// content_len: 120,
406 /// viewport_len: 40,
407 /// };
408 /// let scrollbar = ScrollBar::vertical(lengths).content_len(240);
409 /// ```
410 pub const fn content_len(mut self, content_len: usize) -> Self {
411 self.content_len = content_len;
412 self
413 }
414
415 /// Sets the visible viewport length in logical units.
416 ///
417 /// When `viewport_len >= content_len`, the thumb fills the track.
418 ///
419 /// Zero values are treated as 1.
420 ///
421 /// ```rust
422 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
423 ///
424 /// let lengths = ScrollLengths {
425 /// content_len: 120,
426 /// viewport_len: 40,
427 /// };
428 /// let scrollbar = ScrollBar::vertical(lengths).viewport_len(60);
429 /// ```
430 pub const fn viewport_len(mut self, viewport_len: usize) -> Self {
431 self.viewport_len = viewport_len;
432 self
433 }
434
435 /// Sets the current scroll offset in logical units.
436 ///
437 /// Offsets are clamped to `content_len - viewport_len` during rendering and input handling,
438 /// not when this builder is called.
439 ///
440 /// ```rust
441 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
442 ///
443 /// let lengths = ScrollLengths {
444 /// content_len: 120,
445 /// viewport_len: 40,
446 /// };
447 /// let scrollbar = ScrollBar::vertical(lengths).offset(30);
448 /// ```
449 pub const fn offset(mut self, offset: usize) -> Self {
450 self.offset = offset;
451 self
452 }
453
454 /// Sets the style applied to track glyphs.
455 ///
456 /// Track styling applies only to cells where the thumb is not rendered.
457 ///
458 /// ```rust
459 /// use ratatui_core::style::{Color, Style};
460 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
461 ///
462 /// let lengths = ScrollLengths {
463 /// content_len: 120,
464 /// viewport_len: 40,
465 /// };
466 /// let scrollbar = ScrollBar::vertical(lengths).track_style(Style::new().bg(Color::Black));
467 /// ```
468 pub const fn track_style(mut self, style: Style) -> Self {
469 self.track_style = style;
470 self
471 }
472
473 /// Sets the style applied to thumb glyphs.
474 ///
475 /// Thumb styling applies to full and partial thumb cells. Thumb glyphs are block characters,
476 /// so `Style::fg` usually controls the visible thumb color. Use `Style::bg` only when the
477 /// cell behind the glyph should differ from the track. On partial thumb cells, the background
478 /// can show at the thumb ends.
479 ///
480 /// ```rust
481 /// use ratatui_core::style::{Color, Style};
482 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
483 ///
484 /// let lengths = ScrollLengths {
485 /// content_len: 120,
486 /// viewport_len: 40,
487 /// };
488 /// let scrollbar =
489 /// ScrollBar::vertical(lengths).thumb_style(Style::new().fg(Color::Rgb(255, 158, 100)));
490 /// ```
491 pub const fn thumb_style(mut self, style: Style) -> Self {
492 self.thumb_style = style;
493 self
494 }
495
496 /// Sets the style applied to arrow glyphs.
497 ///
498 /// Arrow endcaps render only when enabled with [`Self::arrows`]. If no arrow style is
499 /// configured internally, arrows fall back to the track style.
500 ///
501 /// ```rust
502 /// use ratatui_core::style::{Color, Style};
503 /// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
504 ///
505 /// let lengths = ScrollLengths {
506 /// content_len: 120,
507 /// viewport_len: 40,
508 /// };
509 /// let scrollbar = ScrollBar::vertical(lengths)
510 /// .arrows(ScrollBarArrows::Both)
511 /// .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black));
512 /// ```
513 pub const fn arrow_style(mut self, style: Style) -> Self {
514 self.arrow_style = Some(style);
515 self
516 }
517
518 /// Selects the glyph set used to render the track and thumb.
519 ///
520 /// [`GlyphSet::symbols_for_legacy_computing`] uses [Symbols for Legacy Computing] for 1/8th
521 /// upper/right fills. Use [`GlyphSet::unicode`] if you want to avoid the legacy supplement, or
522 /// [`GlyphSet::box_drawing`] when you want a visible line track.
523 ///
524 /// [Symbols for Legacy Computing]: https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing
525 ///
526 /// ```rust
527 /// use tui_scrollbar::{GlyphSet, ScrollBar, ScrollLengths};
528 ///
529 /// let lengths = ScrollLengths {
530 /// content_len: 120,
531 /// viewport_len: 40,
532 /// };
533 /// let scrollbar = ScrollBar::vertical(lengths).glyph_set(GlyphSet::unicode());
534 /// ```
535 pub const fn glyph_set(mut self, glyph_set: GlyphSet) -> Self {
536 self.glyph_set = glyph_set;
537 self
538 }
539
540 /// Sets which arrow endcaps are rendered.
541 ///
542 /// Each enabled arrow reserves one cell at the start or end of the track.
543 ///
544 /// ```rust
545 /// use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
546 ///
547 /// let lengths = ScrollLengths {
548 /// content_len: 120,
549 /// viewport_len: 40,
550 /// };
551 /// let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both);
552 /// ```
553 pub const fn arrows(mut self, arrows: ScrollBarArrows) -> Self {
554 self.arrows = arrows;
555 self
556 }
557
558 /// Sets behavior for clicks on the track outside the thumb.
559 ///
560 /// Use [`TrackClickBehavior::Page`] for classic page-up/down behavior, or
561 /// [`TrackClickBehavior::JumpToClick`] to move the thumb toward the click.
562 ///
563 /// This does not affect clicks on the thumb or arrow endcaps.
564 ///
565 /// ```rust
566 /// use tui_scrollbar::{ScrollBar, ScrollLengths, TrackClickBehavior};
567 ///
568 /// let lengths = ScrollLengths {
569 /// content_len: 120,
570 /// viewport_len: 40,
571 /// };
572 /// let scrollbar =
573 /// ScrollBar::vertical(lengths).track_click_behavior(TrackClickBehavior::JumpToClick);
574 /// ```
575 pub const fn track_click_behavior(mut self, behavior: TrackClickBehavior) -> Self {
576 self.track_click_behavior = behavior;
577 self
578 }
579
580 /// Sets the scroll step used for wheel events.
581 ///
582 /// The wheel delta is multiplied by this value (in your logical units) and then clamped. A
583 /// step of 0 is normalized to 1.
584 ///
585 /// ```rust
586 /// use tui_scrollbar::{ScrollBar, ScrollLengths};
587 ///
588 /// let lengths = ScrollLengths {
589 /// content_len: 120,
590 /// viewport_len: 40,
591 /// };
592 /// let scrollbar = ScrollBar::vertical(lengths).scroll_step(8);
593 /// ```
594 pub fn scroll_step(mut self, step: usize) -> Self {
595 self.scroll_step = step.max(1);
596 self
597 }
598
599 /// Computes the inner track area and arrow cell positions for this orientation.
600 fn arrow_layout(&self, area: Rect) -> ArrowLayout {
601 let mut track_area = area;
602 let (start, end) = match self.orientation {
603 ScrollBarOrientation::Vertical => {
604 let start_enabled = self.arrows.has_start() && area.height > 0;
605 let end_enabled = self.arrows.has_end() && area.height > start_enabled as u16;
606 let start = start_enabled.then_some((area.x, area.y));
607 let end = end_enabled
608 .then_some((area.x, area.y.saturating_add(area.height).saturating_sub(1)));
609 if start_enabled {
610 track_area.y = track_area.y.saturating_add(1);
611 track_area.height = track_area.height.saturating_sub(1);
612 }
613 if end_enabled {
614 track_area.height = track_area.height.saturating_sub(1);
615 }
616 (start, end)
617 }
618 ScrollBarOrientation::Horizontal => {
619 let start_enabled = self.arrows.has_start() && area.width > 0;
620 let end_enabled = self.arrows.has_end() && area.width > start_enabled as u16;
621 let start = start_enabled.then_some((area.x, area.y));
622 let end = end_enabled
623 .then_some((area.x.saturating_add(area.width).saturating_sub(1), area.y));
624 if start_enabled {
625 track_area.x = track_area.x.saturating_add(1);
626 track_area.width = track_area.width.saturating_sub(1);
627 }
628 if end_enabled {
629 track_area.width = track_area.width.saturating_sub(1);
630 }
631 (start, end)
632 }
633 };
634
635 ArrowLayout {
636 track_area,
637 start,
638 end,
639 }
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use ratatui_core::style::{Color, Style};
646
647 use super::*;
648 use crate::ScrollLengths;
649 use crate::glyphs::GlyphSet;
650
651 #[test]
652 fn builder_methods_update_fields() {
653 let lengths = ScrollLengths {
654 content_len: 10,
655 viewport_len: 4,
656 };
657 let track_style = Style::new().fg(Color::Red);
658 let thumb_style = Style::new().bg(Color::Blue);
659 let arrow_style = Style::new().fg(Color::Green);
660 let glyphs = GlyphSet::unicode();
661
662 let scrollbar = ScrollBar::new(ScrollBarOrientation::Vertical, lengths)
663 .orientation(ScrollBarOrientation::Horizontal)
664 .content_len(20)
665 .viewport_len(5)
666 .offset(3)
667 .track_style(track_style)
668 .thumb_style(thumb_style)
669 .arrow_style(arrow_style)
670 .glyph_set(glyphs.clone())
671 .arrows(ScrollBarArrows::End)
672 .track_click_behavior(TrackClickBehavior::JumpToClick)
673 .scroll_step(0);
674
675 assert_eq!(scrollbar.orientation, ScrollBarOrientation::Horizontal);
676 assert_eq!(scrollbar.content_len, 20);
677 assert_eq!(scrollbar.viewport_len, 5);
678 assert_eq!(scrollbar.offset, 3);
679 assert_eq!(scrollbar.track_style, track_style);
680 assert_eq!(scrollbar.thumb_style, thumb_style);
681 assert_eq!(scrollbar.arrow_style, Some(arrow_style));
682 assert_eq!(scrollbar.glyph_set, glyphs);
683 assert_eq!(scrollbar.arrows, ScrollBarArrows::End);
684 assert_eq!(
685 scrollbar.track_click_behavior,
686 TrackClickBehavior::JumpToClick
687 );
688 assert_eq!(scrollbar.scroll_step, 1);
689 }
690
691 #[test]
692 fn constructors_set_orientation() {
693 let lengths = ScrollLengths {
694 content_len: 10,
695 viewport_len: 4,
696 };
697 let vertical = ScrollBar::vertical(lengths);
698 let horizontal = ScrollBar::horizontal(lengths);
699
700 assert_eq!(vertical.orientation, ScrollBarOrientation::Vertical);
701 assert_eq!(horizontal.orientation, ScrollBarOrientation::Horizontal);
702 }
703
704 #[test]
705 fn reserves_track_cells_for_arrows() {
706 let lengths = ScrollLengths {
707 content_len: 10,
708 viewport_len: 4,
709 };
710 let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both);
711 let area = Rect::new(0, 0, 1, 5);
712 let layout = scrollbar.arrow_layout(area);
713
714 assert_eq!(layout.track_area.height, 3);
715 assert_eq!(layout.start, Some((area.x, area.y)));
716 assert_eq!(
717 layout.end,
718 Some((area.x, area.y.saturating_add(area.height).saturating_sub(1)))
719 );
720 }
721
722 #[test]
723 fn reserves_track_cells_for_horizontal_arrows() {
724 let lengths = ScrollLengths {
725 content_len: 10,
726 viewport_len: 4,
727 };
728 let scrollbar = ScrollBar::horizontal(lengths).arrows(ScrollBarArrows::Both);
729 let area = Rect::new(0, 0, 5, 1);
730 let layout = scrollbar.arrow_layout(area);
731
732 assert_eq!(layout.track_area.width, 3);
733 assert_eq!(layout.start, Some((area.x, area.y)));
734 assert_eq!(
735 layout.end,
736 Some((area.x.saturating_add(area.width).saturating_sub(1), area.y))
737 );
738 }
739}