1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
#![doc = include_str!("../readme.md")]
mod inner;
mod scrolled;
mod util;
mod view;
mod viewport;
use ratatui::layout::Rect;
use std::cmp::{max, min};
pub use scrolled::{
HScrollPosition, Inner, ScrollbarPolicy, Scrolled, ScrolledState, ScrolledStyle,
VScrollPosition,
};
pub use view::{View, ViewState};
pub use viewport::{Viewport, ViewportState};
/// Trait for the widget struct of a scrollable widget.
pub trait ScrollingWidget<State> {
/// Widget wants a (horizontal, vertical) scrollbar.
/// This gets combined with the [ScrollbarPolicy].
fn need_scroll(&self, area: Rect, state: &mut State) -> (bool, bool);
}
/// Trait for the widget-state of a scrollable widget.
///
/// This trait works purely in item-space, none of the values
/// correspond to screen coordinates.
///
/// The current visible page is represented as the pair (offset, page_len).
/// The limit for scrolling is given as max_offset, which is the maximum offset
/// where a full page can still be displayed. Note that the total length of
/// the widgets data is NOT max_offset + page_len. The page_len can be different for
/// every offset selected. Only if the offset is set to max_offset and after
/// the next round of rendering len == max_offset + page_len will hold true.
///
/// The offset can be set to any value possible for usize. It's the widgets job
/// to limit the value if necessary. Of course, it's possible for a user of this
/// trait to set their own limits.
pub trait ScrollingState {
/// Maximum offset that is accessible with scrolling.
///
/// This is shorter than the length of the content by whatever fills the last page.
/// This is the base for the scrollbar content_length.
fn vertical_max_offset(&self) -> usize;
/// Current vertical offset.
fn vertical_offset(&self) -> usize;
/// Vertical page-size at the current offset.
fn vertical_page(&self) -> usize;
/// Suggested scroll per scroll-event.
fn vertical_scroll(&self) -> usize {
max(self.vertical_page() / 10, 1)
}
/// Maximum offset that is accessible with scrolling.
///
/// This is shorter than the length of the content by whatever fills the last page.
/// This is the base for the scrollbar content_length.
fn horizontal_max_offset(&self) -> usize;
/// Current horizontal offset.
fn horizontal_offset(&self) -> usize;
/// Horizontal page-size at the current offset.
fn horizontal_page(&self) -> usize;
/// Suggested scroll per scroll-event.
fn horizontal_scroll(&self) -> usize {
max(self.horizontal_page() / 10, 1)
}
/// Change the vertical offset.
///
/// Due to overscroll it's possible that this is an invalid offset for the widget.
/// The widget must deal with this situation.
///
/// The widget returns true if the offset changed at all.
fn set_vertical_offset(&mut self, offset: usize) -> bool;
/// Change the horizontal offset.
///
/// Due to overscroll it's possible that this is an invalid offset for the widget.
/// The widget must deal with this situation.
///
/// The widget returns true if the offset changed at all.
fn set_horizontal_offset(&mut self, offset: usize) -> bool;
/// Scroll up by n items.
/// The widget returns true if the offset changed at all.
fn scroll_up(&mut self, n: usize) -> bool {
self.set_vertical_offset(self.vertical_offset().saturating_sub(n))
}
/// Scroll down by n items.
/// The widget returns true if the offset changed at all.
fn scroll_down(&mut self, n: usize) -> bool {
self.set_vertical_offset(min(self.vertical_offset() + n, self.vertical_max_offset()))
}
/// Scroll up by n items.
/// The widget returns true if the offset changed at all.
fn scroll_left(&mut self, n: usize) -> bool {
self.set_horizontal_offset(self.horizontal_offset().saturating_sub(n))
}
/// Scroll down by n items.
/// The widget returns true if the offset changed at all.
fn scroll_right(&mut self, n: usize) -> bool {
self.set_horizontal_offset(min(
self.horizontal_offset() + n,
self.horizontal_max_offset(),
))
}
}
// /// A widget that can differentiate between these states can use this as a flag.
// /// It's the job of the widget to implement the difference.
// ///
// /// But in the end this is probably to many choices for most widgets, so this is
// /// pretty useless. A widget will better signal its capabilities in its
// /// own terminology.
// ///
// #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
// pub enum ScrollingPolicy {
// Selection,
// #[default]
// ItemOffset,
// LineOffset,
// }
pub mod event {
pub use rat_event::{
crossterm, ct_event, flow, flow_ok, util, ConsumedEvent, FocusKeys, HandleEvent, MouseOnly,
Outcome,
};
/// Result value for event-handling. Used widgets in this crate.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollOutcome<R> {
/// The given event was not handled at all.
NotUsed,
/// The event was handled, no repaint necessary.
Unchanged,
/// The event was handled, repaint necessary.
Changed,
/// Outcome of the inner widget.
Inner(R),
}
impl<T> From<ScrollOutcome<T>> for Outcome {
fn from(value: ScrollOutcome<T>) -> Self {
match value {
ScrollOutcome::NotUsed => Outcome::NotUsed,
ScrollOutcome::Unchanged => Outcome::Unchanged,
ScrollOutcome::Changed => Outcome::Changed,
ScrollOutcome::Inner(_) => Outcome::Changed,
}
}
}
impl<R> ScrollOutcome<ScrollOutcome<R>> {
/// Compact two layers of Outcome to one.
pub fn flatten(self) -> ScrollOutcome<R> {
match self {
ScrollOutcome::Inner(i) => match i {
ScrollOutcome::Inner(i) => ScrollOutcome::Inner(i),
ScrollOutcome::NotUsed => ScrollOutcome::NotUsed,
ScrollOutcome::Unchanged => ScrollOutcome::Unchanged,
ScrollOutcome::Changed => ScrollOutcome::Changed,
},
ScrollOutcome::NotUsed => ScrollOutcome::NotUsed,
ScrollOutcome::Unchanged => ScrollOutcome::Unchanged,
ScrollOutcome::Changed => ScrollOutcome::Changed,
}
}
}
impl<R> ScrollOutcome<ScrollOutcome<ScrollOutcome<R>>> {
/// Compact three layers of Outcome to one.
pub fn flatten2(self) -> ScrollOutcome<R> {
match self {
ScrollOutcome::Inner(i) => match i {
ScrollOutcome::Inner(i) => match i {
ScrollOutcome::Inner(i) => ScrollOutcome::Inner(i),
ScrollOutcome::NotUsed => ScrollOutcome::NotUsed,
ScrollOutcome::Unchanged => ScrollOutcome::Unchanged,
ScrollOutcome::Changed => ScrollOutcome::Changed,
},
ScrollOutcome::NotUsed => ScrollOutcome::NotUsed,
ScrollOutcome::Unchanged => ScrollOutcome::Unchanged,
ScrollOutcome::Changed => ScrollOutcome::Changed,
},
ScrollOutcome::NotUsed => ScrollOutcome::NotUsed,
ScrollOutcome::Unchanged => ScrollOutcome::Unchanged,
ScrollOutcome::Changed => ScrollOutcome::Changed,
}
}
}
impl<R> ConsumedEvent for ScrollOutcome<R>
where
R: ConsumedEvent,
{
fn is_consumed(&self) -> bool {
match self {
ScrollOutcome::Inner(v) => v.is_consumed(),
ScrollOutcome::NotUsed => false,
ScrollOutcome::Unchanged => true,
ScrollOutcome::Changed => true,
}
}
}
}
mod _private {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonExhaustive;
}