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