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    AppContext, Axis, Task, ViewContext,
 13};
 14use language::{Bias, Point};
 15use util::ResultExt;
 16use workspace::{item::Item, WorkspaceId};
 17
 18use crate::{
 19    display_map::{DisplaySnapshot, ToDisplayPoint},
 20    hover_popover::hide_hover,
 21    persistence::DB,
 22    Anchor, DisplayPoint, Editor, EditorMode, Event, InlayRefreshReason, MultiBufferSnapshot,
 23    ToPoint,
 24};
 25
 26use self::{
 27    autoscroll::{Autoscroll, AutoscrollStrategy},
 28    scroll_amount::ScrollAmount,
 29};
 30
 31pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
 32const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
 33
 34#[derive(Default)]
 35pub struct ScrollbarAutoHide(pub bool);
 36
 37#[derive(Clone, Copy, Debug, PartialEq)]
 38pub struct ScrollAnchor {
 39    pub offset: Vector2F,
 40    pub anchor: Anchor,
 41}
 42
 43impl ScrollAnchor {
 44    fn new() -> Self {
 45        Self {
 46            offset: Vector2F::zero(),
 47            anchor: Anchor::min(),
 48        }
 49    }
 50
 51    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
 52        let mut scroll_position = self.offset;
 53        if self.anchor != Anchor::min() {
 54            let scroll_top = self.anchor.to_display_point(snapshot).row() as f32;
 55            scroll_position.set_y(scroll_top + scroll_position.y());
 56        } else {
 57            scroll_position.set_y(0.);
 58        }
 59        scroll_position
 60    }
 61
 62    pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
 63        self.anchor.to_point(buffer).row
 64    }
 65}
 66
 67#[derive(Clone, Copy, Debug)]
 68pub struct OngoingScroll {
 69    last_event: Instant,
 70    axis: Option<Axis>,
 71}
 72
 73impl OngoingScroll {
 74    fn new() -> Self {
 75        Self {
 76            last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
 77            axis: None,
 78        }
 79    }
 80
 81    pub fn filter(&self, delta: &mut Vector2F) -> Option<Axis> {
 82        const UNLOCK_PERCENT: f32 = 1.9;
 83        const UNLOCK_LOWER_BOUND: f32 = 6.;
 84        let mut axis = self.axis;
 85
 86        let x = delta.x().abs();
 87        let y = delta.y().abs();
 88        let duration = Instant::now().duration_since(self.last_event);
 89        if duration > SCROLL_EVENT_SEPARATION {
 90            //New ongoing scroll will start, determine axis
 91            axis = if x <= y {
 92                Some(Axis::Vertical)
 93            } else {
 94                Some(Axis::Horizontal)
 95            };
 96        } else if x.max(y) >= UNLOCK_LOWER_BOUND {
 97            //Check if the current ongoing will need to unlock
 98            match axis {
 99                Some(Axis::Vertical) => {
100                    if x > y && x >= y * UNLOCK_PERCENT {
101                        axis = None;
102                    }
103                }
104
105                Some(Axis::Horizontal) => {
106                    if y > x && y >= x * UNLOCK_PERCENT {
107                        axis = None;
108                    }
109                }
110
111                None => {}
112            }
113        }
114
115        match axis {
116            Some(Axis::Vertical) => *delta = vec2f(0., delta.y()),
117            Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
118            None => {}
119        }
120
121        axis
122    }
123}
124
125pub struct ScrollManager {
126    vertical_scroll_margin: f32,
127    anchor: ScrollAnchor,
128    ongoing: OngoingScroll,
129    autoscroll_request: Option<(Autoscroll, bool)>,
130    last_autoscroll: Option<(Vector2F, f32, f32, AutoscrollStrategy)>,
131    show_scrollbars: bool,
132    hide_scrollbar_task: Option<Task<()>>,
133    visible_line_count: Option<f32>,
134}
135
136impl ScrollManager {
137    pub fn new() -> Self {
138        ScrollManager {
139            vertical_scroll_margin: 3.0,
140            anchor: ScrollAnchor::new(),
141            ongoing: OngoingScroll::new(),
142            autoscroll_request: None,
143            show_scrollbars: true,
144            hide_scrollbar_task: None,
145            last_autoscroll: None,
146            visible_line_count: None,
147        }
148    }
149
150    pub fn clone_state(&mut self, other: &Self) {
151        self.anchor = other.anchor;
152        self.ongoing = other.ongoing;
153    }
154
155    pub fn anchor(&self) -> ScrollAnchor {
156        self.anchor
157    }
158
159    pub fn ongoing_scroll(&self) -> OngoingScroll {
160        self.ongoing
161    }
162
163    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
164        self.ongoing.last_event = Instant::now();
165        self.ongoing.axis = axis;
166    }
167
168    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
169        self.anchor.scroll_position(snapshot)
170    }
171
172    fn set_scroll_position(
173        &mut self,
174        scroll_position: Vector2F,
175        map: &DisplaySnapshot,
176        local: bool,
177        autoscroll: bool,
178        workspace_id: Option<i64>,
179        cx: &mut ViewContext<Editor>,
180    ) {
181        let (new_anchor, top_row) = if scroll_position.y() <= 0. {
182            (
183                ScrollAnchor {
184                    anchor: Anchor::min(),
185                    offset: scroll_position.max(vec2f(0., 0.)),
186                },
187                0,
188            )
189        } else {
190            let scroll_top_buffer_point =
191                DisplayPoint::new(scroll_position.y() as u32, 0).to_point(&map);
192            let top_anchor = map
193                .buffer_snapshot
194                .anchor_at(scroll_top_buffer_point, Bias::Right);
195
196            (
197                ScrollAnchor {
198                    anchor: top_anchor,
199                    offset: vec2f(
200                        scroll_position.x(),
201                        scroll_position.y() - top_anchor.to_display_point(&map).row() as f32,
202                    ),
203                },
204                scroll_top_buffer_point.row,
205            )
206        };
207
208        self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx);
209    }
210
211    fn set_anchor(
212        &mut self,
213        anchor: ScrollAnchor,
214        top_row: u32,
215        local: bool,
216        autoscroll: bool,
217        workspace_id: Option<i64>,
218        cx: &mut ViewContext<Editor>,
219    ) {
220        self.anchor = anchor;
221        cx.emit(Event::ScrollPositionChanged { local, autoscroll });
222        self.show_scrollbar(cx);
223        self.autoscroll_request.take();
224        if let Some(workspace_id) = workspace_id {
225            let item_id = cx.view_id();
226
227            cx.background()
228                .spawn(async move {
229                    DB.save_scroll_position(
230                        item_id,
231                        workspace_id,
232                        top_row,
233                        anchor.offset.x(),
234                        anchor.offset.y(),
235                    )
236                    .await
237                    .log_err()
238                })
239                .detach()
240        }
241        cx.notify();
242    }
243
244    pub fn show_scrollbar(&mut self, cx: &mut ViewContext<Editor>) {
245        if !self.show_scrollbars {
246            self.show_scrollbars = true;
247            cx.notify();
248        }
249
250        if cx.default_global::<ScrollbarAutoHide>().0 {
251            self.hide_scrollbar_task = Some(cx.spawn(|editor, mut cx| async move {
252                cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
253                editor
254                    .update(&mut cx, |editor, cx| {
255                        editor.scroll_manager.show_scrollbars = false;
256                        cx.notify();
257                    })
258                    .log_err();
259            }));
260        } else {
261            self.hide_scrollbar_task = None;
262        }
263    }
264
265    pub fn scrollbars_visible(&self) -> bool {
266        self.show_scrollbars
267    }
268
269    pub fn has_autoscroll_request(&self) -> bool {
270        self.autoscroll_request.is_some()
271    }
272
273    pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
274        if max < self.anchor.offset.x() {
275            self.anchor.offset.set_x(max);
276            true
277        } else {
278            false
279        }
280    }
281}
282
283impl Editor {
284    pub fn vertical_scroll_margin(&mut self) -> usize {
285        self.scroll_manager.vertical_scroll_margin as usize
286    }
287
288    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
289        self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
290        cx.notify();
291    }
292
293    pub fn visible_line_count(&self) -> Option<f32> {
294        self.scroll_manager.visible_line_count
295    }
296
297    pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
298        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
299        self.scroll_manager.visible_line_count = Some(lines);
300        if opened_first_time {
301            cx.spawn(|editor, mut cx| async move {
302                editor
303                    .update(&mut cx, |editor, cx| {
304                        editor.refresh_inlays(InlayRefreshReason::NewLinesShown, cx)
305                    })
306                    .ok()
307            })
308            .detach()
309        }
310    }
311
312    pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
313        self.set_scroll_position_internal(scroll_position, true, false, cx);
314    }
315
316    pub(crate) fn set_scroll_position_internal(
317        &mut self,
318        scroll_position: Vector2F,
319        local: bool,
320        autoscroll: bool,
321        cx: &mut ViewContext<Self>,
322    ) {
323        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
324
325        hide_hover(self, cx);
326        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
327        self.scroll_manager.set_scroll_position(
328            scroll_position,
329            &map,
330            local,
331            autoscroll,
332            workspace_id,
333            cx,
334        );
335
336        if !self.is_singleton(cx) {
337            self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
338        }
339    }
340
341    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
342        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
343        self.scroll_manager.anchor.scroll_position(&display_map)
344    }
345
346    pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
347        hide_hover(self, cx);
348        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
349        let top_row = scroll_anchor
350            .anchor
351            .to_point(&self.buffer().read(cx).snapshot(cx))
352            .row;
353        self.scroll_manager
354            .set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx);
355    }
356
357    pub(crate) fn set_scroll_anchor_remote(
358        &mut self,
359        scroll_anchor: ScrollAnchor,
360        cx: &mut ViewContext<Self>,
361    ) {
362        hide_hover(self, cx);
363        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
364        let top_row = scroll_anchor
365            .anchor
366            .to_point(&self.buffer().read(cx).snapshot(cx))
367            .row;
368        self.scroll_manager
369            .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
370    }
371
372    pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
373        if matches!(self.mode, EditorMode::SingleLine) {
374            cx.propagate_action();
375            return;
376        }
377
378        if self.take_rename(true, cx).is_some() {
379            return;
380        }
381
382        if amount.move_context_menu_selection(self, cx) {
383            return;
384        }
385
386        let cur_position = self.scroll_position(cx);
387        let new_pos = cur_position + vec2f(0., amount.lines(self));
388        self.set_scroll_position(new_pos, cx);
389    }
390
391    /// Returns an ordering. The newest selection is:
392    ///     Ordering::Equal => on screen
393    ///     Ordering::Less => above the screen
394    ///     Ordering::Greater => below the screen
395    pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
396        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
397        let newest_head = self
398            .selections
399            .newest_anchor()
400            .head()
401            .to_display_point(&snapshot);
402        let screen_top = self
403            .scroll_manager
404            .anchor
405            .anchor
406            .to_display_point(&snapshot);
407
408        if screen_top > newest_head {
409            return Ordering::Less;
410        }
411
412        if let Some(visible_lines) = self.visible_line_count() {
413            if newest_head.row() < screen_top.row() + visible_lines as u32 {
414                return Ordering::Equal;
415            }
416        }
417
418        Ordering::Greater
419    }
420
421    pub fn read_scroll_position_from_db(
422        &mut self,
423        item_id: usize,
424        workspace_id: WorkspaceId,
425        cx: &mut ViewContext<Editor>,
426    ) {
427        let scroll_position = DB.get_scroll_position(item_id, workspace_id);
428        if let Ok(Some((top_row, x, y))) = scroll_position {
429            let top_anchor = self
430                .buffer()
431                .read(cx)
432                .snapshot(cx)
433                .anchor_at(Point::new(top_row as u32, 0), Bias::Left);
434            let scroll_anchor = ScrollAnchor {
435                offset: Vector2F::new(x, y),
436                anchor: top_anchor,
437            };
438            self.set_scroll_anchor(scroll_anchor, cx);
439        }
440    }
441}