scroll.rs

  1pub mod actions;
  2pub mod autoscroll;
  3pub mod scroll_amount;
  4
  5use std::{
  6    cmp::Ordering,
  7    time::{Duration, Instant},
  8};
  9
 10use gpui::{
 11    geometry::vector::{vec2f, Vector2F},
 12    Axis, MutableAppContext, Task, ViewContext,
 13};
 14use language::Bias;
 15
 16use crate::{
 17    display_map::{DisplaySnapshot, ToDisplayPoint},
 18    hover_popover::hide_hover,
 19    Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint,
 20};
 21
 22use self::{
 23    autoscroll::{Autoscroll, AutoscrollStrategy},
 24    scroll_amount::ScrollAmount,
 25};
 26
 27pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
 28const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
 29
 30#[derive(Default)]
 31pub struct ScrollbarAutoHide(pub bool);
 32
 33#[derive(Clone, Copy, Debug, PartialEq)]
 34pub struct ScrollAnchor {
 35    pub offset: Vector2F,
 36    pub top_anchor: Anchor,
 37}
 38
 39impl ScrollAnchor {
 40    fn new() -> Self {
 41        Self {
 42            offset: Vector2F::zero(),
 43            top_anchor: Anchor::min(),
 44        }
 45    }
 46
 47    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
 48        let mut scroll_position = self.offset;
 49        if self.top_anchor != Anchor::min() {
 50            let scroll_top = self.top_anchor.to_display_point(snapshot).row() as f32;
 51            scroll_position.set_y(scroll_top + scroll_position.y());
 52        } else {
 53            scroll_position.set_y(0.);
 54        }
 55        scroll_position
 56    }
 57
 58    pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
 59        self.top_anchor.to_point(buffer).row
 60    }
 61}
 62
 63#[derive(Clone, Copy, Debug)]
 64pub struct OngoingScroll {
 65    last_event: Instant,
 66    axis: Option<Axis>,
 67}
 68
 69impl OngoingScroll {
 70    fn new() -> Self {
 71        Self {
 72            last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
 73            axis: None,
 74        }
 75    }
 76
 77    pub fn filter(&self, delta: &mut Vector2F) -> Option<Axis> {
 78        const UNLOCK_PERCENT: f32 = 1.9;
 79        const UNLOCK_LOWER_BOUND: f32 = 6.;
 80        let mut axis = self.axis;
 81
 82        let x = delta.x().abs();
 83        let y = delta.y().abs();
 84        let duration = Instant::now().duration_since(self.last_event);
 85        if duration > SCROLL_EVENT_SEPARATION {
 86            //New ongoing scroll will start, determine axis
 87            axis = if x <= y {
 88                Some(Axis::Vertical)
 89            } else {
 90                Some(Axis::Horizontal)
 91            };
 92        } else if x.max(y) >= UNLOCK_LOWER_BOUND {
 93            //Check if the current ongoing will need to unlock
 94            match axis {
 95                Some(Axis::Vertical) => {
 96                    if x > y && x >= y * UNLOCK_PERCENT {
 97                        axis = None;
 98                    }
 99                }
100
101                Some(Axis::Horizontal) => {
102                    if y > x && y >= x * UNLOCK_PERCENT {
103                        axis = None;
104                    }
105                }
106
107                None => {}
108            }
109        }
110
111        match axis {
112            Some(Axis::Vertical) => *delta = vec2f(0., delta.y()),
113            Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
114            None => {}
115        }
116
117        axis
118    }
119}
120
121pub struct ScrollManager {
122    vertical_scroll_margin: f32,
123    anchor: ScrollAnchor,
124    ongoing: OngoingScroll,
125    autoscroll_request: Option<(Autoscroll, bool)>,
126    last_autoscroll: Option<(Vector2F, f32, f32, AutoscrollStrategy)>,
127    show_scrollbars: bool,
128    hide_scrollbar_task: Option<Task<()>>,
129    visible_line_count: Option<f32>,
130}
131
132impl ScrollManager {
133    pub fn new() -> Self {
134        ScrollManager {
135            vertical_scroll_margin: 3.0,
136            anchor: ScrollAnchor::new(),
137            ongoing: OngoingScroll::new(),
138            autoscroll_request: None,
139            show_scrollbars: true,
140            hide_scrollbar_task: None,
141            last_autoscroll: None,
142            visible_line_count: None,
143        }
144    }
145
146    pub fn clone_state(&mut self, other: &Self) {
147        self.anchor = other.anchor;
148        self.ongoing = other.ongoing;
149    }
150
151    pub fn anchor(&self) -> ScrollAnchor {
152        self.anchor
153    }
154
155    pub fn ongoing_scroll(&self) -> OngoingScroll {
156        self.ongoing
157    }
158
159    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
160        self.ongoing.last_event = Instant::now();
161        self.ongoing.axis = axis;
162    }
163
164    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
165        self.anchor.scroll_position(snapshot)
166    }
167
168    fn set_scroll_position(
169        &mut self,
170        scroll_position: Vector2F,
171        map: &DisplaySnapshot,
172        local: bool,
173        cx: &mut ViewContext<Editor>,
174    ) {
175        let new_anchor = if scroll_position.y() <= 0. {
176            ScrollAnchor {
177                top_anchor: Anchor::min(),
178                offset: scroll_position.max(vec2f(0., 0.)),
179            }
180        } else {
181            let scroll_top_buffer_offset =
182                DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right);
183            let top_anchor = map
184                .buffer_snapshot
185                .anchor_at(scroll_top_buffer_offset, Bias::Right);
186
187            ScrollAnchor {
188                top_anchor,
189                offset: vec2f(
190                    scroll_position.x(),
191                    scroll_position.y() - top_anchor.to_display_point(&map).row() as f32,
192                ),
193            }
194        };
195
196        self.set_anchor(new_anchor, local, cx);
197    }
198
199    fn set_anchor(&mut self, anchor: ScrollAnchor, local: bool, cx: &mut ViewContext<Editor>) {
200        self.anchor = anchor;
201        cx.emit(Event::ScrollPositionChanged { local });
202        self.show_scrollbar(cx);
203        self.autoscroll_request.take();
204        cx.notify();
205    }
206
207    pub fn show_scrollbar(&mut self, cx: &mut ViewContext<Editor>) {
208        if !self.show_scrollbars {
209            self.show_scrollbars = true;
210            cx.notify();
211        }
212
213        if cx.default_global::<ScrollbarAutoHide>().0 {
214            self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
215                cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
216                if let Some(editor) = editor.upgrade(&cx) {
217                    editor.update(&mut cx, |editor, cx| {
218                        editor.scroll_manager.show_scrollbars = false;
219                        cx.notify();
220                    });
221                }
222            }));
223        } else {
224            self.hide_scrollbar_task = None;
225        }
226    }
227
228    pub fn scrollbars_visible(&self) -> bool {
229        self.show_scrollbars
230    }
231
232    pub fn has_autoscroll_request(&self) -> bool {
233        self.autoscroll_request.is_some()
234    }
235
236    pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
237        if max < self.anchor.offset.x() {
238            self.anchor.offset.set_x(max);
239            true
240        } else {
241            false
242        }
243    }
244}
245
246impl Editor {
247    pub fn vertical_scroll_margin(&mut self) -> usize {
248        self.scroll_manager.vertical_scroll_margin as usize
249    }
250
251    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
252        self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
253        cx.notify();
254    }
255
256    pub fn visible_line_count(&self) -> Option<f32> {
257        self.scroll_manager.visible_line_count
258    }
259
260    pub(crate) fn set_visible_line_count(&mut self, lines: f32) {
261        self.scroll_manager.visible_line_count = Some(lines)
262    }
263
264    pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
265        self.set_scroll_position_internal(scroll_position, true, cx);
266    }
267
268    pub(crate) fn set_scroll_position_internal(
269        &mut self,
270        scroll_position: Vector2F,
271        local: bool,
272        cx: &mut ViewContext<Self>,
273    ) {
274        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
275
276        hide_hover(self, cx);
277        self.scroll_manager
278            .set_scroll_position(scroll_position, &map, local, cx);
279    }
280
281    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
282        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
283        self.scroll_manager.anchor.scroll_position(&display_map)
284    }
285
286    pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
287        hide_hover(self, cx);
288        self.scroll_manager.set_anchor(scroll_anchor, true, cx);
289    }
290
291    pub(crate) fn set_scroll_anchor_remote(
292        &mut self,
293        scroll_anchor: ScrollAnchor,
294        cx: &mut ViewContext<Self>,
295    ) {
296        hide_hover(self, cx);
297        self.scroll_manager.set_anchor(scroll_anchor, false, cx);
298    }
299
300    pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
301        if matches!(self.mode, EditorMode::SingleLine) {
302            cx.propagate_action();
303            return;
304        }
305
306        if self.take_rename(true, cx).is_some() {
307            return;
308        }
309
310        if amount.move_context_menu_selection(self, cx) {
311            return;
312        }
313
314        let cur_position = self.scroll_position(cx);
315        let new_pos = cur_position + vec2f(0., amount.lines(self) - 1.);
316        self.set_scroll_position(new_pos, cx);
317    }
318
319    /// Returns an ordering. The newest selection is:
320    ///     Ordering::Equal => on screen
321    ///     Ordering::Less => above the screen
322    ///     Ordering::Greater => below the screen
323    pub fn newest_selection_on_screen(&self, cx: &mut MutableAppContext) -> Ordering {
324        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
325        let newest_head = self
326            .selections
327            .newest_anchor()
328            .head()
329            .to_display_point(&snapshot);
330        let screen_top = self
331            .scroll_manager
332            .anchor
333            .top_anchor
334            .to_display_point(&snapshot);
335
336        if screen_top > newest_head {
337            return Ordering::Less;
338        }
339
340        if let Some(visible_lines) = self.visible_line_count() {
341            if newest_head.row() < screen_top.row() + visible_lines as u32 {
342                return Ordering::Equal;
343            }
344        }
345
346        Ordering::Greater
347    }
348}