Skip to main content

egui_cha/
scroll_area.rs

1//! ScrollArea - Scrollable container with configurable options
2//!
3//! Provides a configurable scroll area builder that integrates with ViewCtx.
4//!
5//! # Examples
6//!
7//! ```ignore
8//! use egui_cha::ScrollArea;
9//!
10//! // Vertical scroll (default)
11//! ScrollArea::vertical()
12//!     .max_height(300.0)
13//!     .show_ctx(ctx, |ctx| {
14//!         for i in 0..100 {
15//!             ctx.ui.label(format!("Item {}", i));
16//!         }
17//!     });
18//!
19//! // Horizontal scroll
20//! ScrollArea::horizontal()
21//!     .show_ctx(ctx, |ctx| {
22//!         ctx.horizontal(|ctx| {
23//!             for i in 0..20 {
24//!                 ctx.ui.label(format!("{}", i));
25//!             }
26//!         });
27//!     });
28//!
29//! // Both directions
30//! ScrollArea::both()
31//!     .max_height(400.0)
32//!     .max_width(600.0)
33//!     .show_ctx(ctx, |ctx| {
34//!         // Large content
35//!     });
36//! ```
37
38use egui::scroll_area::{ScrollBarVisibility, ScrollSource};
39use egui::Ui;
40
41use crate::ViewCtx;
42
43/// Scroll direction
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
45pub enum ScrollDirection {
46    #[default]
47    Vertical,
48    Horizontal,
49    Both,
50}
51
52/// A configurable scroll area builder
53#[derive(Clone)]
54pub struct ScrollArea {
55    direction: ScrollDirection,
56    id_salt: Option<egui::Id>,
57    max_height: Option<f32>,
58    max_width: Option<f32>,
59    min_scrolled_height: Option<f32>,
60    min_scrolled_width: Option<f32>,
61    auto_shrink: [bool; 2],
62    scroll_bar_visibility: ScrollBarVisibility,
63    animated: bool,
64    enable_scrolling: bool,
65    scroll_offset: Option<egui::Vec2>,
66}
67
68impl Default for ScrollArea {
69    fn default() -> Self {
70        Self {
71            direction: ScrollDirection::Vertical,
72            id_salt: None,
73            max_height: None,
74            max_width: None,
75            min_scrolled_height: None,
76            min_scrolled_width: None,
77            auto_shrink: [true; 2],
78            scroll_bar_visibility: ScrollBarVisibility::VisibleWhenNeeded,
79            animated: true,
80            enable_scrolling: true,
81            scroll_offset: None,
82        }
83    }
84}
85
86impl ScrollArea {
87    /// Create a new scroll area (vertical by default)
88    pub fn new() -> Self {
89        Self::default()
90    }
91
92    /// Create a vertical scroll area
93    pub fn vertical() -> Self {
94        Self {
95            direction: ScrollDirection::Vertical,
96            ..Default::default()
97        }
98    }
99
100    /// Create a horizontal scroll area
101    pub fn horizontal() -> Self {
102        Self {
103            direction: ScrollDirection::Horizontal,
104            ..Default::default()
105        }
106    }
107
108    /// Create a scroll area that scrolls both directions
109    pub fn both() -> Self {
110        Self {
111            direction: ScrollDirection::Both,
112            ..Default::default()
113        }
114    }
115
116    /// Set a custom ID to avoid ID clashes
117    pub fn id_salt(mut self, id: impl std::hash::Hash) -> Self {
118        self.id_salt = Some(egui::Id::new(id));
119        self
120    }
121
122    /// Set maximum height (pixels)
123    pub fn max_height(mut self, max_height: f32) -> Self {
124        self.max_height = Some(max_height);
125        self
126    }
127
128    /// Set maximum width (pixels)
129    pub fn max_width(mut self, max_width: f32) -> Self {
130        self.max_width = Some(max_width);
131        self
132    }
133
134    /// Set minimum scrolled height
135    pub fn min_scrolled_height(mut self, height: f32) -> Self {
136        self.min_scrolled_height = Some(height);
137        self
138    }
139
140    /// Set minimum scrolled width
141    pub fn min_scrolled_width(mut self, width: f32) -> Self {
142        self.min_scrolled_width = Some(width);
143        self
144    }
145
146    /// Set auto-shrink behavior [horizontal, vertical]
147    ///
148    /// If `true`, the scroll area will shrink to fit its content.
149    /// Default is `[true, true]`.
150    pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self {
151        self.auto_shrink = auto_shrink;
152        self
153    }
154
155    /// Disable auto-shrink (expand to fill available space)
156    pub fn no_shrink(mut self) -> Self {
157        self.auto_shrink = [false, false];
158        self
159    }
160
161    /// Set scroll bar visibility
162    pub fn scroll_bar_visibility(mut self, visibility: ScrollBarVisibility) -> Self {
163        self.scroll_bar_visibility = visibility;
164        self
165    }
166
167    /// Always show scroll bars
168    pub fn always_show_scroll(mut self) -> Self {
169        self.scroll_bar_visibility = ScrollBarVisibility::AlwaysVisible;
170        self
171    }
172
173    /// Never show scroll bars
174    pub fn hide_scroll(mut self) -> Self {
175        self.scroll_bar_visibility = ScrollBarVisibility::AlwaysHidden;
176        self
177    }
178
179    /// Enable or disable animated scrolling
180    pub fn animated(mut self, animated: bool) -> Self {
181        self.animated = animated;
182        self
183    }
184
185    /// Enable or disable scrolling entirely
186    pub fn enable_scrolling(mut self, enable: bool) -> Self {
187        self.enable_scrolling = enable;
188        self
189    }
190
191    /// Set initial scroll offset
192    pub fn scroll_offset(mut self, offset: egui::Vec2) -> Self {
193        self.scroll_offset = Some(offset);
194        self
195    }
196
197    /// Show the scroll area with ViewCtx integration
198    pub fn show_ctx<Msg, R>(
199        self,
200        ctx: &mut ViewCtx<'_, Msg>,
201        f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
202    ) -> R {
203        let area = self.build();
204        ctx.scroll_area_with(|_| area, f)
205    }
206
207    /// Show the scroll area with raw egui::Ui
208    pub fn show<R>(
209        self,
210        ui: &mut Ui,
211        f: impl FnOnce(&mut Ui) -> R,
212    ) -> egui::scroll_area::ScrollAreaOutput<R> {
213        self.build().show(ui, f)
214    }
215
216    /// Build the underlying egui::ScrollArea
217    fn build(self) -> egui::ScrollArea {
218        let mut area = match self.direction {
219            ScrollDirection::Vertical => egui::ScrollArea::vertical(),
220            ScrollDirection::Horizontal => egui::ScrollArea::horizontal(),
221            ScrollDirection::Both => egui::ScrollArea::both(),
222        };
223
224        if let Some(id) = self.id_salt {
225            area = area.id_salt(id);
226        }
227
228        if let Some(h) = self.max_height {
229            area = area.max_height(h);
230        }
231
232        if let Some(w) = self.max_width {
233            area = area.max_width(w);
234        }
235
236        if let Some(h) = self.min_scrolled_height {
237            area = area.min_scrolled_height(h);
238        }
239
240        if let Some(w) = self.min_scrolled_width {
241            area = area.min_scrolled_width(w);
242        }
243
244        area = area.auto_shrink(self.auto_shrink);
245        area = area.scroll_bar_visibility(self.scroll_bar_visibility);
246        area = area.animated(self.animated);
247        area = area.scroll_source(if self.enable_scrolling {
248            ScrollSource::ALL
249        } else {
250            ScrollSource::NONE
251        });
252
253        if let Some(offset) = self.scroll_offset {
254            area = area.vertical_scroll_offset(offset.y);
255            area = area.horizontal_scroll_offset(offset.x);
256        }
257
258        area
259    }
260}