pub struct ScrollBar { /* private fields */ }Expand description
A proportional scrollbar widget with fractional thumb rendering.
§Method map
§Construction
§Position and lengths
§Appearance
§Interaction
Self::handle_eventSelf::handle_mouse_event, when a crossterm feature is enabledSelf::track_click_behaviorSelf::scroll_step
§Important
content_lenandviewport_lenare in logical units.- Zero values are treated as 1.
- The scrollbar renders into a single row or column.
§Behavior
The thumb length is proportional to viewport_len / content_len and clamped to at least one
full cell for usability. When content_len <= viewport_len, the thumb fills the track. Areas
with zero width or height render nothing.
Arrow endcaps, when enabled, consume one cell at the start/end of the track. The thumb and
track render in the remaining inner area. Clicking an arrow steps the offset by scroll_step.
§Styling
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.
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.
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::Rect;
use ratatui_core::widgets::Widget;
use tui_scrollbar::{ScrollBar, ScrollLengths};
let area = Rect::new(0, 0, 1, 5);
let lengths = ScrollLengths {
content_len: 200,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).offset(60);
let mut buffer = Buffer::empty(area);
scrollbar.render(area, &mut buffer);§Updating offsets on input
This is the typical pattern for pointer handling: feed events to the scrollbar and apply the returned command to your stored offset.
use ratatui_core::layout::Rect;
use tui_scrollbar::{
PointerButton, PointerEvent, PointerEventKind, ScrollBar, ScrollBarInteraction,
ScrollCommand, ScrollEvent, ScrollLengths,
};
let area = Rect::new(0, 0, 1, 10);
let lengths = ScrollLengths {
content_len: 400,
viewport_len: 80,
};
let scrollbar = ScrollBar::vertical(lengths).offset(0);
let mut interaction = ScrollBarInteraction::new();
let mut offset = 0;
let event = ScrollEvent::Pointer(PointerEvent {
column: 0,
row: 3,
kind: PointerEventKind::Down,
button: PointerButton::Primary,
});
if let Some(ScrollCommand::SetOffset(next)) =
scrollbar.handle_event(area, event, &mut interaction)
{
offset = next;
}§Track click behavior
Choose between classic page jumps or jump-to-click behavior.
use tui_scrollbar::{ScrollBar, ScrollLengths, TrackClickBehavior};
let lengths = ScrollLengths {
content_len: 10,
viewport_len: 5,
};
let scrollbar =
ScrollBar::vertical(lengths).track_click_behavior(TrackClickBehavior::JumpToClick);§Arrow endcaps
Arrow endcaps are optional. When enabled, they reserve one cell at each end of the track.
use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 24,
};
let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both);Implementations§
Source§impl ScrollBar
impl ScrollBar
Sourcepub fn handle_event(
&self,
area: Rect,
event: ScrollEvent,
interaction: &mut ScrollBarInteraction,
) -> Option<ScrollCommand>
pub fn handle_event( &self, area: Rect, event: ScrollEvent, interaction: &mut ScrollBarInteraction, ) -> Option<ScrollCommand>
Handles a backend-agnostic scrollbar event.
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.
use ratatui_core::layout::Rect;
use tui_scrollbar::{
PointerButton, PointerEvent, PointerEventKind, ScrollBar, ScrollBarInteraction,
ScrollEvent, ScrollLengths,
};
let area = Rect::new(0, 0, 1, 6);
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 24,
};
let scrollbar = ScrollBar::vertical(lengths).offset(0);
let mut interaction = ScrollBarInteraction::new();
let event = ScrollEvent::Pointer(PointerEvent {
column: 0,
row: 2,
kind: PointerEventKind::Down,
button: PointerButton::Primary,
});
let _ = scrollbar.handle_event(area, event, &mut interaction);Sourcepub fn handle_mouse_event(
&self,
area: Rect,
event: MouseEvent,
interaction: &mut ScrollBarInteraction,
) -> Option<ScrollCommand>
pub fn handle_mouse_event( &self, area: Rect, event: MouseEvent, interaction: &mut ScrollBarInteraction, ) -> Option<ScrollCommand>
Handles crossterm mouse events for this scrollbar.
This helper converts crossterm events into ScrollEvent values before delegating to
Self::handle_event. See the scrollbar_mouse example for a complete terminal event
loop with mouse capture.
Examples found in repository?
262 fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263 let Some(layout) = self.layout else {
264 return;
265 };
266 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267 let horizontal = self.horizontal_scrollbar(h_metrics);
268 let vertical = self.vertical_scrollbar(v_metrics);
269
270 if let Some(command) = horizontal.handle_mouse_event(
271 layout.horizontal_bar,
272 event,
273 &mut self.horizontal_interaction,
274 ) {
275 self.apply_command(command, true);
276 }
277 if let Some(command) =
278 vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279 {
280 self.apply_command(command, false);
281 }
282 }Source§impl ScrollBar
impl ScrollBar
Sourcepub fn new(orientation: ScrollBarOrientation, lengths: ScrollLengths) -> Self
pub fn new(orientation: ScrollBarOrientation, lengths: ScrollLengths) -> Self
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.
use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::new(ScrollBarOrientation::Vertical, lengths);Sourcepub fn vertical(lengths: ScrollLengths) -> Self
pub fn vertical(lengths: ScrollLengths) -> Self
Creates a vertical scrollbar with the given content and viewport lengths.
The track length is derived from the render area’s height.
use tui_scrollbar::{ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths);Examples found in repository?
153fn render_vertical_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
154 for (index, area) in cells.iter().enumerate() {
155 let [label_area, bar_area] = area.layout(&Layout::vertical([
156 Constraint::Length(1),
157 Constraint::Fill(1),
158 ]));
159 if bar_area.height == 0 {
160 continue;
161 }
162 let metrics = build_metrics(bar_area.height as usize, 3);
163 let (label, thumb_start) = step_entry(&metrics, index);
164 let label = (label % 8).to_string();
165 let offset = metrics.offset_for_thumb_start(thumb_start);
166 let lengths = ScrollLengths {
167 content_len: metrics.content_len(),
168 viewport_len: metrics.viewport_len(),
169 };
170 let scrollbar = ScrollBar::vertical(lengths)
171 .arrows(ScrollBarArrows::Both)
172 .offset(offset);
173 render_label(frame, label_area, &label);
174 frame.render_widget(&scrollbar, bar_area);
175 }
176}More examples
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }
221
222 /// Handles keyboard and mouse events, updating offsets as needed.
223 fn handle_events(&mut self) -> Result<()> {
224 match event::read()? {
225 Event::Key(key) => {
226 if key.is_press() {
227 self.handle_key_event(key.code);
228 }
229 }
230 Event::Mouse(event) => {
231 self.handle_mouse_event(event);
232 }
233 _ => {}
234 }
235 Ok(())
236 }
237
238 /// Handles keyboard input, updating offsets or exiting as needed.
239 fn handle_key_event(&mut self, code: KeyCode) {
240 match code {
241 KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242 KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243 KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244 KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245 KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246 _ => {}
247 }
248 }
249
250 /// Applies a keyboard delta to the scrollbar offsets.
251 fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252 let Some(layout) = self.layout else {
253 return;
254 };
255 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256 self.horizontal_offset =
257 Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258 self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259 }
260
261 /// Handles crossterm mouse events using the scrollbar helpers.
262 fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263 let Some(layout) = self.layout else {
264 return;
265 };
266 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267 let horizontal = self.horizontal_scrollbar(h_metrics);
268 let vertical = self.vertical_scrollbar(v_metrics);
269
270 if let Some(command) = horizontal.handle_mouse_event(
271 layout.horizontal_bar,
272 event,
273 &mut self.horizontal_interaction,
274 ) {
275 self.apply_command(command, true);
276 }
277 if let Some(command) =
278 vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279 {
280 self.apply_command(command, false);
281 }
282 }
283
284 /// Applies a scroll command to the current axis offset.
285 fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286 let ScrollCommand::SetOffset(offset) = command;
287 if is_horizontal {
288 self.horizontal_offset = offset;
289 } else {
290 self.vertical_offset = offset;
291 }
292 }
293
294 /// Builds a horizontal scrollbar from the current metrics.
295 fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296 let lengths = ScrollLengths {
297 content_len: metrics.content_len(),
298 viewport_len: metrics.viewport_len(),
299 };
300 ScrollBar::horizontal(lengths)
301 .arrows(ScrollBarArrows::Both)
302 .offset(self.horizontal_offset)
303 .scroll_step(SUBCELL)
304 }
305
306 /// Builds a vertical scrollbar from the current metrics.
307 fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308 let lengths = ScrollLengths {
309 content_len: metrics.content_len(),
310 viewport_len: metrics.viewport_len(),
311 };
312 ScrollBar::vertical(lengths)
313 .arrows(ScrollBarArrows::Both)
314 .offset(self.vertical_offset)
315 .scroll_step(SUBCELL)
316 }Sourcepub fn horizontal(lengths: ScrollLengths) -> Self
pub fn horizontal(lengths: ScrollLengths) -> Self
Creates a horizontal scrollbar with the given content and viewport lengths.
The track length is derived from the render area’s width.
use tui_scrollbar::{ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::horizontal(lengths);Examples found in repository?
127fn render_horizontal_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
128 for (index, area) in cells.iter().enumerate() {
129 let [label_area, bar_area] = area.layout(&Layout::horizontal([
130 Constraint::Length(2),
131 Constraint::Fill(1),
132 ]));
133 if bar_area.width == 0 {
134 continue;
135 }
136 let metrics = build_metrics(bar_area.width as usize, 6);
137 let (label, thumb_start) = step_entry(&metrics, index);
138 let label = (label % 8).to_string();
139 let offset = metrics.offset_for_thumb_start(thumb_start);
140 let lengths = ScrollLengths {
141 content_len: metrics.content_len(),
142 viewport_len: metrics.viewport_len(),
143 };
144 let scrollbar = ScrollBar::horizontal(lengths)
145 .arrows(ScrollBarArrows::Both)
146 .offset(offset);
147 render_label(frame, label_area, &label);
148 frame.render_widget(&scrollbar, bar_area);
149 }
150}More examples
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }
221
222 /// Handles keyboard and mouse events, updating offsets as needed.
223 fn handle_events(&mut self) -> Result<()> {
224 match event::read()? {
225 Event::Key(key) => {
226 if key.is_press() {
227 self.handle_key_event(key.code);
228 }
229 }
230 Event::Mouse(event) => {
231 self.handle_mouse_event(event);
232 }
233 _ => {}
234 }
235 Ok(())
236 }
237
238 /// Handles keyboard input, updating offsets or exiting as needed.
239 fn handle_key_event(&mut self, code: KeyCode) {
240 match code {
241 KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242 KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243 KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244 KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245 KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246 _ => {}
247 }
248 }
249
250 /// Applies a keyboard delta to the scrollbar offsets.
251 fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252 let Some(layout) = self.layout else {
253 return;
254 };
255 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256 self.horizontal_offset =
257 Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258 self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259 }
260
261 /// Handles crossterm mouse events using the scrollbar helpers.
262 fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263 let Some(layout) = self.layout else {
264 return;
265 };
266 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267 let horizontal = self.horizontal_scrollbar(h_metrics);
268 let vertical = self.vertical_scrollbar(v_metrics);
269
270 if let Some(command) = horizontal.handle_mouse_event(
271 layout.horizontal_bar,
272 event,
273 &mut self.horizontal_interaction,
274 ) {
275 self.apply_command(command, true);
276 }
277 if let Some(command) =
278 vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279 {
280 self.apply_command(command, false);
281 }
282 }
283
284 /// Applies a scroll command to the current axis offset.
285 fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286 let ScrollCommand::SetOffset(offset) = command;
287 if is_horizontal {
288 self.horizontal_offset = offset;
289 } else {
290 self.vertical_offset = offset;
291 }
292 }
293
294 /// Builds a horizontal scrollbar from the current metrics.
295 fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296 let lengths = ScrollLengths {
297 content_len: metrics.content_len(),
298 viewport_len: metrics.viewport_len(),
299 };
300 ScrollBar::horizontal(lengths)
301 .arrows(ScrollBarArrows::Both)
302 .offset(self.horizontal_offset)
303 .scroll_step(SUBCELL)
304 }Sourcepub const fn orientation(self, orientation: ScrollBarOrientation) -> Self
pub const fn orientation(self, orientation: ScrollBarOrientation) -> Self
Sets the scrollbar orientation.
This is mostly useful when sharing a builder chain and choosing the orientation later.
use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).orientation(ScrollBarOrientation::Horizontal);Sourcepub const fn content_len(self, content_len: usize) -> Self
pub const fn content_len(self, content_len: usize) -> Self
Sets the total scrollable content length in logical units.
Larger values shrink the thumb, while smaller values enlarge it.
Zero values are treated as 1.
use tui_scrollbar::{ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).content_len(240);Sourcepub const fn viewport_len(self, viewport_len: usize) -> Self
pub const fn viewport_len(self, viewport_len: usize) -> Self
Sets the visible viewport length in logical units.
When viewport_len >= content_len, the thumb fills the track.
Zero values are treated as 1.
use tui_scrollbar::{ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).viewport_len(60);Sourcepub const fn offset(self, offset: usize) -> Self
pub const fn offset(self, offset: usize) -> Self
Sets the current scroll offset in logical units.
Offsets are clamped to content_len - viewport_len during rendering and input handling,
not when this builder is called.
use tui_scrollbar::{ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).offset(30);Examples found in repository?
127fn render_horizontal_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
128 for (index, area) in cells.iter().enumerate() {
129 let [label_area, bar_area] = area.layout(&Layout::horizontal([
130 Constraint::Length(2),
131 Constraint::Fill(1),
132 ]));
133 if bar_area.width == 0 {
134 continue;
135 }
136 let metrics = build_metrics(bar_area.width as usize, 6);
137 let (label, thumb_start) = step_entry(&metrics, index);
138 let label = (label % 8).to_string();
139 let offset = metrics.offset_for_thumb_start(thumb_start);
140 let lengths = ScrollLengths {
141 content_len: metrics.content_len(),
142 viewport_len: metrics.viewport_len(),
143 };
144 let scrollbar = ScrollBar::horizontal(lengths)
145 .arrows(ScrollBarArrows::Both)
146 .offset(offset);
147 render_label(frame, label_area, &label);
148 frame.render_widget(&scrollbar, bar_area);
149 }
150}
151
152/// Draws vertical scrollbars that sweep every 1/8th thumb position, left to right.
153fn render_vertical_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
154 for (index, area) in cells.iter().enumerate() {
155 let [label_area, bar_area] = area.layout(&Layout::vertical([
156 Constraint::Length(1),
157 Constraint::Fill(1),
158 ]));
159 if bar_area.height == 0 {
160 continue;
161 }
162 let metrics = build_metrics(bar_area.height as usize, 3);
163 let (label, thumb_start) = step_entry(&metrics, index);
164 let label = (label % 8).to_string();
165 let offset = metrics.offset_for_thumb_start(thumb_start);
166 let lengths = ScrollLengths {
167 content_len: metrics.content_len(),
168 viewport_len: metrics.viewport_len(),
169 };
170 let scrollbar = ScrollBar::vertical(lengths)
171 .arrows(ScrollBarArrows::Both)
172 .offset(offset);
173 render_label(frame, label_area, &label);
174 frame.render_widget(&scrollbar, bar_area);
175 }
176}More examples
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }
221
222 /// Handles keyboard and mouse events, updating offsets as needed.
223 fn handle_events(&mut self) -> Result<()> {
224 match event::read()? {
225 Event::Key(key) => {
226 if key.is_press() {
227 self.handle_key_event(key.code);
228 }
229 }
230 Event::Mouse(event) => {
231 self.handle_mouse_event(event);
232 }
233 _ => {}
234 }
235 Ok(())
236 }
237
238 /// Handles keyboard input, updating offsets or exiting as needed.
239 fn handle_key_event(&mut self, code: KeyCode) {
240 match code {
241 KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242 KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243 KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244 KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245 KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246 _ => {}
247 }
248 }
249
250 /// Applies a keyboard delta to the scrollbar offsets.
251 fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252 let Some(layout) = self.layout else {
253 return;
254 };
255 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256 self.horizontal_offset =
257 Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258 self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259 }
260
261 /// Handles crossterm mouse events using the scrollbar helpers.
262 fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263 let Some(layout) = self.layout else {
264 return;
265 };
266 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267 let horizontal = self.horizontal_scrollbar(h_metrics);
268 let vertical = self.vertical_scrollbar(v_metrics);
269
270 if let Some(command) = horizontal.handle_mouse_event(
271 layout.horizontal_bar,
272 event,
273 &mut self.horizontal_interaction,
274 ) {
275 self.apply_command(command, true);
276 }
277 if let Some(command) =
278 vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279 {
280 self.apply_command(command, false);
281 }
282 }
283
284 /// Applies a scroll command to the current axis offset.
285 fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286 let ScrollCommand::SetOffset(offset) = command;
287 if is_horizontal {
288 self.horizontal_offset = offset;
289 } else {
290 self.vertical_offset = offset;
291 }
292 }
293
294 /// Builds a horizontal scrollbar from the current metrics.
295 fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296 let lengths = ScrollLengths {
297 content_len: metrics.content_len(),
298 viewport_len: metrics.viewport_len(),
299 };
300 ScrollBar::horizontal(lengths)
301 .arrows(ScrollBarArrows::Both)
302 .offset(self.horizontal_offset)
303 .scroll_step(SUBCELL)
304 }
305
306 /// Builds a vertical scrollbar from the current metrics.
307 fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308 let lengths = ScrollLengths {
309 content_len: metrics.content_len(),
310 viewport_len: metrics.viewport_len(),
311 };
312 ScrollBar::vertical(lengths)
313 .arrows(ScrollBarArrows::Both)
314 .offset(self.vertical_offset)
315 .scroll_step(SUBCELL)
316 }Sourcepub const fn track_style(self, style: Style) -> Self
pub const fn track_style(self, style: Style) -> Self
Sets the style applied to track glyphs.
Track styling applies only to cells where the thumb is not rendered.
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));Examples found in repository?
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}More examples
123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }Sourcepub const fn thumb_style(self, style: Style) -> Self
pub const fn thumb_style(self, style: Style) -> Self
Sets the style applied to thumb glyphs.
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.
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)));Examples found in repository?
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}More examples
123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }Sourcepub const fn arrow_style(self, style: Style) -> Self
pub const fn arrow_style(self, style: Style) -> Self
Sets the style applied to arrow glyphs.
Arrow endcaps render only when enabled with Self::arrows. If no arrow style is
configured internally, arrows fall back to the track style.
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));Examples found in repository?
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}More examples
123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }Sourcepub const fn glyph_set(self, glyph_set: GlyphSet) -> Self
pub const fn glyph_set(self, glyph_set: GlyphSet) -> Self
Selects the glyph set used to render the track and thumb.
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.
use tui_scrollbar::{GlyphSet, ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).glyph_set(GlyphSet::unicode());Examples found in repository?
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}Sourcepub const fn arrows(self, arrows: ScrollBarArrows) -> Self
pub const fn arrows(self, arrows: ScrollBarArrows) -> Self
Sets which arrow endcaps are rendered.
Each enabled arrow reserves one cell at the start or end of the track.
use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both);Examples found in repository?
127fn render_horizontal_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
128 for (index, area) in cells.iter().enumerate() {
129 let [label_area, bar_area] = area.layout(&Layout::horizontal([
130 Constraint::Length(2),
131 Constraint::Fill(1),
132 ]));
133 if bar_area.width == 0 {
134 continue;
135 }
136 let metrics = build_metrics(bar_area.width as usize, 6);
137 let (label, thumb_start) = step_entry(&metrics, index);
138 let label = (label % 8).to_string();
139 let offset = metrics.offset_for_thumb_start(thumb_start);
140 let lengths = ScrollLengths {
141 content_len: metrics.content_len(),
142 viewport_len: metrics.viewport_len(),
143 };
144 let scrollbar = ScrollBar::horizontal(lengths)
145 .arrows(ScrollBarArrows::Both)
146 .offset(offset);
147 render_label(frame, label_area, &label);
148 frame.render_widget(&scrollbar, bar_area);
149 }
150}
151
152/// Draws vertical scrollbars that sweep every 1/8th thumb position, left to right.
153fn render_vertical_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
154 for (index, area) in cells.iter().enumerate() {
155 let [label_area, bar_area] = area.layout(&Layout::vertical([
156 Constraint::Length(1),
157 Constraint::Fill(1),
158 ]));
159 if bar_area.height == 0 {
160 continue;
161 }
162 let metrics = build_metrics(bar_area.height as usize, 3);
163 let (label, thumb_start) = step_entry(&metrics, index);
164 let label = (label % 8).to_string();
165 let offset = metrics.offset_for_thumb_start(thumb_start);
166 let lengths = ScrollLengths {
167 content_len: metrics.content_len(),
168 viewport_len: metrics.viewport_len(),
169 };
170 let scrollbar = ScrollBar::vertical(lengths)
171 .arrows(ScrollBarArrows::Both)
172 .offset(offset);
173 render_label(frame, label_area, &label);
174 frame.render_widget(&scrollbar, bar_area);
175 }
176}More examples
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25 if area.width < 2 || area.height < 2 {
26 return;
27 }
28
29 let horizontal_bar = area
30 .rows()
31 .next_back()
32 .unwrap_or(area)
33 .inner(Margin::new(1, 0));
34 let vertical_bar = area
35 .columns()
36 .next_back()
37 .unwrap_or(area)
38 .inner(Margin::new(0, 1));
39
40 let block = Block::new()
41 .borders(Borders::ALL)
42 .title("styled scrollbars")
43 .border_style((Color::LightBlue, Color::Black))
44 .style((Color::Gray, Color::Black));
45 let content = block.inner(area).inner(Margin::new(2, 1));
46 frame.render_widget(block, area);
47
48 frame.render_widget(
49 Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50 .style((Color::Gray, Color::Black)),
51 content,
52 );
53
54 let lengths = ScrollLengths {
55 content_len: 160,
56 viewport_len: 40,
57 };
58 let horizontal = ScrollBar::horizontal(lengths)
59 .offset(48)
60 .arrows(ScrollBarArrows::Both)
61 .glyph_set(GlyphSet::box_drawing())
62 .track_style((Color::Blue, Color::Black).into())
63 .thumb_style((Color::Yellow, Modifier::BOLD).into())
64 .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65 let vertical = ScrollBar::vertical(lengths)
66 .offset(80)
67 .arrows(ScrollBarArrows::Both)
68 .glyph_set(GlyphSet::box_drawing())
69 .track_style((Color::Magenta, Color::Black).into())
70 .thumb_style((Color::Cyan, Modifier::BOLD).into())
71 .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73 frame.render_widget(&horizontal, horizontal_bar);
74 frame.render_widget(&vertical, vertical_bar);
75}123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }
221
222 /// Handles keyboard and mouse events, updating offsets as needed.
223 fn handle_events(&mut self) -> Result<()> {
224 match event::read()? {
225 Event::Key(key) => {
226 if key.is_press() {
227 self.handle_key_event(key.code);
228 }
229 }
230 Event::Mouse(event) => {
231 self.handle_mouse_event(event);
232 }
233 _ => {}
234 }
235 Ok(())
236 }
237
238 /// Handles keyboard input, updating offsets or exiting as needed.
239 fn handle_key_event(&mut self, code: KeyCode) {
240 match code {
241 KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242 KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243 KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244 KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245 KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246 _ => {}
247 }
248 }
249
250 /// Applies a keyboard delta to the scrollbar offsets.
251 fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252 let Some(layout) = self.layout else {
253 return;
254 };
255 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256 self.horizontal_offset =
257 Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258 self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259 }
260
261 /// Handles crossterm mouse events using the scrollbar helpers.
262 fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263 let Some(layout) = self.layout else {
264 return;
265 };
266 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267 let horizontal = self.horizontal_scrollbar(h_metrics);
268 let vertical = self.vertical_scrollbar(v_metrics);
269
270 if let Some(command) = horizontal.handle_mouse_event(
271 layout.horizontal_bar,
272 event,
273 &mut self.horizontal_interaction,
274 ) {
275 self.apply_command(command, true);
276 }
277 if let Some(command) =
278 vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279 {
280 self.apply_command(command, false);
281 }
282 }
283
284 /// Applies a scroll command to the current axis offset.
285 fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286 let ScrollCommand::SetOffset(offset) = command;
287 if is_horizontal {
288 self.horizontal_offset = offset;
289 } else {
290 self.vertical_offset = offset;
291 }
292 }
293
294 /// Builds a horizontal scrollbar from the current metrics.
295 fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296 let lengths = ScrollLengths {
297 content_len: metrics.content_len(),
298 viewport_len: metrics.viewport_len(),
299 };
300 ScrollBar::horizontal(lengths)
301 .arrows(ScrollBarArrows::Both)
302 .offset(self.horizontal_offset)
303 .scroll_step(SUBCELL)
304 }
305
306 /// Builds a vertical scrollbar from the current metrics.
307 fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308 let lengths = ScrollLengths {
309 content_len: metrics.content_len(),
310 viewport_len: metrics.viewport_len(),
311 };
312 ScrollBar::vertical(lengths)
313 .arrows(ScrollBarArrows::Both)
314 .offset(self.vertical_offset)
315 .scroll_step(SUBCELL)
316 }Sourcepub const fn track_click_behavior(self, behavior: TrackClickBehavior) -> Self
pub const fn track_click_behavior(self, behavior: TrackClickBehavior) -> Self
Sets behavior for clicks on the track outside the thumb.
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.
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);Sourcepub fn scroll_step(self, step: usize) -> Self
pub fn scroll_step(self, step: usize) -> Self
Sets the scroll step used for wheel events.
The wheel delta is multiplied by this value (in your logical units) and then clamped. A step of 0 is normalized to 1.
use tui_scrollbar::{ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).scroll_step(8);Examples found in repository?
123 fn render(&mut self, frame: &mut ratatui::Frame) {
124 let area = frame.area();
125 if area.width < 2 || area.height < 2 {
126 return;
127 }
128
129 let title = "tui-scrollbar - mouse scroll demo";
130 let block = Block::new()
131 .borders(Borders::TOP)
132 .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133 .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134 .title(
135 Line::from(title)
136 .centered()
137 .fg(TITLE_FG)
138 .bg(TITLE_BG)
139 .bold(),
140 );
141 frame.render_widget(&block, area);
142
143 let content_area = Rect {
144 y: area.y.saturating_add(1),
145 height: area.height.saturating_sub(1),
146 ..area
147 };
148 let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149 let help_area = Rect {
150 x: content_area.x.saturating_add(1),
151 y: content_area.y,
152 width: content_area.width.saturating_sub(1),
153 height: 1,
154 };
155 if help_area.width > 0 {
156 frame.render_widget(
157 Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158 help_area,
159 );
160 }
161 let content_area = Rect {
162 y: content_area.y.saturating_add(1),
163 height: content_area.height.saturating_sub(1),
164 ..content_area
165 };
166
167 // Split out the bottom row and right column for the scrollbars.
168 let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169 Constraint::Fill(1),
170 Constraint::Length(1),
171 ]));
172 let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173 Constraint::Fill(1),
174 Constraint::Length(1),
175 ]));
176 let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177 Constraint::Fill(1),
178 Constraint::Length(1),
179 ]));
180
181 self.layout = Some(LayoutState {
182 content,
183 vertical_bar,
184 horizontal_bar,
185 });
186
187 // Keep offsets valid when the terminal is resized.
188 let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189 self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190 self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192 let horizontal_lengths = ScrollLengths {
193 content_len: h_metrics.content_len(),
194 viewport_len: h_metrics.viewport_len(),
195 };
196 let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197 let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198 let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199 let horizontal = ScrollBar::horizontal(horizontal_lengths)
200 .arrows(ScrollBarArrows::Both)
201 .offset(self.horizontal_offset)
202 .scroll_step(SUBCELL)
203 .track_style(track_style)
204 .thumb_style(thumb_style)
205 .arrow_style(arrow_style);
206 let vertical_lengths = ScrollLengths {
207 content_len: v_metrics.content_len(),
208 viewport_len: v_metrics.viewport_len(),
209 };
210 let vertical = ScrollBar::vertical(vertical_lengths)
211 .arrows(ScrollBarArrows::Both)
212 .offset(self.vertical_offset)
213 .scroll_step(SUBCELL)
214 .track_style(track_style)
215 .thumb_style(thumb_style)
216 .arrow_style(arrow_style);
217
218 frame.render_widget(&horizontal, horizontal_bar);
219 frame.render_widget(&vertical, vertical_bar);
220 }
221
222 /// Handles keyboard and mouse events, updating offsets as needed.
223 fn handle_events(&mut self) -> Result<()> {
224 match event::read()? {
225 Event::Key(key) => {
226 if key.is_press() {
227 self.handle_key_event(key.code);
228 }
229 }
230 Event::Mouse(event) => {
231 self.handle_mouse_event(event);
232 }
233 _ => {}
234 }
235 Ok(())
236 }
237
238 /// Handles keyboard input, updating offsets or exiting as needed.
239 fn handle_key_event(&mut self, code: KeyCode) {
240 match code {
241 KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242 KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243 KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244 KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245 KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246 _ => {}
247 }
248 }
249
250 /// Applies a keyboard delta to the scrollbar offsets.
251 fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252 let Some(layout) = self.layout else {
253 return;
254 };
255 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256 self.horizontal_offset =
257 Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258 self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259 }
260
261 /// Handles crossterm mouse events using the scrollbar helpers.
262 fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263 let Some(layout) = self.layout else {
264 return;
265 };
266 let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267 let horizontal = self.horizontal_scrollbar(h_metrics);
268 let vertical = self.vertical_scrollbar(v_metrics);
269
270 if let Some(command) = horizontal.handle_mouse_event(
271 layout.horizontal_bar,
272 event,
273 &mut self.horizontal_interaction,
274 ) {
275 self.apply_command(command, true);
276 }
277 if let Some(command) =
278 vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279 {
280 self.apply_command(command, false);
281 }
282 }
283
284 /// Applies a scroll command to the current axis offset.
285 fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286 let ScrollCommand::SetOffset(offset) = command;
287 if is_horizontal {
288 self.horizontal_offset = offset;
289 } else {
290 self.vertical_offset = offset;
291 }
292 }
293
294 /// Builds a horizontal scrollbar from the current metrics.
295 fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296 let lengths = ScrollLengths {
297 content_len: metrics.content_len(),
298 viewport_len: metrics.viewport_len(),
299 };
300 ScrollBar::horizontal(lengths)
301 .arrows(ScrollBarArrows::Both)
302 .offset(self.horizontal_offset)
303 .scroll_step(SUBCELL)
304 }
305
306 /// Builds a vertical scrollbar from the current metrics.
307 fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308 let lengths = ScrollLengths {
309 content_len: metrics.content_len(),
310 viewport_len: metrics.viewport_len(),
311 };
312 ScrollBar::vertical(lengths)
313 .arrows(ScrollBarArrows::Both)
314 .offset(self.vertical_offset)
315 .scroll_step(SUBCELL)
316 }Trait Implementations§
impl Eq for ScrollBar
impl StructuralPartialEq for ScrollBar
Auto Trait Implementations§
impl Freeze for ScrollBar
impl RefUnwindSafe for ScrollBar
impl Send for ScrollBar
impl Sync for ScrollBar
impl Unpin for ScrollBar
impl UnsafeUnpin for ScrollBar
impl UnwindSafe for ScrollBar
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more