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