pane.rs

  1use super::{ItemHandle, SplitDirection};
  2use crate::{Item, Settings, WeakItemHandle, Workspace};
  3use anyhow::{anyhow, Result};
  4use client::PeerId;
  5use collections::{HashMap, VecDeque};
  6use gpui::{
  7    action,
  8    elements::*,
  9    geometry::{rect::RectF, vector::vec2f},
 10    keymap::Binding,
 11    platform::{CursorStyle, NavigationDirection},
 12    AnyViewHandle, AppContext, Entity, MutableAppContext, Quad, RenderContext, Task, View,
 13    ViewContext, ViewHandle, WeakViewHandle,
 14};
 15use project::{ProjectEntryId, ProjectPath};
 16use std::{
 17    any::{Any, TypeId},
 18    cell::RefCell,
 19    cmp, mem,
 20    rc::Rc,
 21};
 22use util::ResultExt;
 23
 24action!(Split, SplitDirection);
 25action!(ActivateItem, usize);
 26action!(ActivatePrevItem);
 27action!(ActivateNextItem);
 28action!(CloseActiveItem);
 29action!(CloseInactiveItems);
 30action!(CloseItem, usize);
 31action!(GoBack, Option<WeakViewHandle<Pane>>);
 32action!(GoForward, Option<WeakViewHandle<Pane>>);
 33
 34const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 35
 36pub fn init(cx: &mut MutableAppContext) {
 37    cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
 38        pane.activate_item(action.0, cx);
 39    });
 40    cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
 41        pane.activate_prev_item(cx);
 42    });
 43    cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
 44        pane.activate_next_item(cx);
 45    });
 46    cx.add_action(|pane: &mut Pane, _: &CloseActiveItem, cx| {
 47        pane.close_active_item(cx);
 48    });
 49    cx.add_action(|pane: &mut Pane, _: &CloseInactiveItems, cx| {
 50        pane.close_inactive_items(cx);
 51    });
 52    cx.add_action(|pane: &mut Pane, action: &CloseItem, cx| {
 53        pane.close_item(action.0, cx);
 54    });
 55    cx.add_action(|pane: &mut Pane, action: &Split, cx| {
 56        pane.split(action.0, cx);
 57    });
 58    cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| {
 59        Pane::go_back(
 60            workspace,
 61            action
 62                .0
 63                .as_ref()
 64                .and_then(|weak_handle| weak_handle.upgrade(cx)),
 65            cx,
 66        )
 67        .detach();
 68    });
 69    cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| {
 70        Pane::go_forward(
 71            workspace,
 72            action
 73                .0
 74                .as_ref()
 75                .and_then(|weak_handle| weak_handle.upgrade(cx)),
 76            cx,
 77        )
 78        .detach();
 79    });
 80
 81    cx.add_bindings(vec![
 82        Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")),
 83        Binding::new("shift-cmd-}", ActivateNextItem, Some("Pane")),
 84        Binding::new("cmd-w", CloseActiveItem, Some("Pane")),
 85        Binding::new("alt-cmd-w", CloseInactiveItems, Some("Pane")),
 86        Binding::new("cmd-k up", Split(SplitDirection::Up), Some("Pane")),
 87        Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
 88        Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
 89        Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
 90        Binding::new("ctrl--", GoBack(None), Some("Pane")),
 91        Binding::new("shift-ctrl-_", GoForward(None), Some("Pane")),
 92    ]);
 93}
 94
 95pub enum Event {
 96    Activate,
 97    Remove,
 98    Split(SplitDirection),
 99}
100
101pub struct Pane {
102    items: Vec<Box<dyn ItemHandle>>,
103    active_item_index: usize,
104    nav_history: Rc<RefCell<NavHistory>>,
105    toolbars: HashMap<TypeId, Box<dyn ToolbarHandle>>,
106    active_toolbar_type: Option<TypeId>,
107    active_toolbar_visible: bool,
108}
109
110pub(crate) struct FollowerState {
111    pub(crate) leader_id: PeerId,
112    pub(crate) current_view_id: usize,
113    pub(crate) items_by_leader_view_id: HashMap<usize, Box<dyn ItemHandle>>,
114}
115
116pub trait Toolbar: View {
117    fn active_item_changed(
118        &mut self,
119        item: Option<Box<dyn ItemHandle>>,
120        cx: &mut ViewContext<Self>,
121    ) -> bool;
122    fn on_dismiss(&mut self, cx: &mut ViewContext<Self>);
123}
124
125trait ToolbarHandle {
126    fn active_item_changed(
127        &self,
128        item: Option<Box<dyn ItemHandle>>,
129        cx: &mut MutableAppContext,
130    ) -> bool;
131    fn on_dismiss(&self, cx: &mut MutableAppContext);
132    fn to_any(&self) -> AnyViewHandle;
133}
134
135pub struct ItemNavHistory {
136    history: Rc<RefCell<NavHistory>>,
137    item: Rc<dyn WeakItemHandle>,
138}
139
140#[derive(Default)]
141pub struct NavHistory {
142    mode: NavigationMode,
143    backward_stack: VecDeque<NavigationEntry>,
144    forward_stack: VecDeque<NavigationEntry>,
145    paths_by_item: HashMap<usize, ProjectPath>,
146}
147
148#[derive(Copy, Clone)]
149enum NavigationMode {
150    Normal,
151    GoingBack,
152    GoingForward,
153    Disabled,
154}
155
156impl Default for NavigationMode {
157    fn default() -> Self {
158        Self::Normal
159    }
160}
161
162pub struct NavigationEntry {
163    pub item: Rc<dyn WeakItemHandle>,
164    pub data: Option<Box<dyn Any>>,
165}
166
167impl Pane {
168    pub fn new() -> Self {
169        Self {
170            items: Vec::new(),
171            active_item_index: 0,
172            nav_history: Default::default(),
173            toolbars: Default::default(),
174            active_toolbar_type: Default::default(),
175            active_toolbar_visible: false,
176        }
177    }
178
179    pub fn nav_history(&self) -> &Rc<RefCell<NavHistory>> {
180        &self.nav_history
181    }
182
183    pub fn activate(&self, cx: &mut ViewContext<Self>) {
184        cx.emit(Event::Activate);
185    }
186
187    pub fn go_back(
188        workspace: &mut Workspace,
189        pane: Option<ViewHandle<Pane>>,
190        cx: &mut ViewContext<Workspace>,
191    ) -> Task<()> {
192        Self::navigate_history(
193            workspace,
194            pane.unwrap_or_else(|| workspace.active_pane().clone()),
195            NavigationMode::GoingBack,
196            cx,
197        )
198    }
199
200    pub fn go_forward(
201        workspace: &mut Workspace,
202        pane: Option<ViewHandle<Pane>>,
203        cx: &mut ViewContext<Workspace>,
204    ) -> Task<()> {
205        Self::navigate_history(
206            workspace,
207            pane.unwrap_or_else(|| workspace.active_pane().clone()),
208            NavigationMode::GoingForward,
209            cx,
210        )
211    }
212
213    fn navigate_history(
214        workspace: &mut Workspace,
215        pane: ViewHandle<Pane>,
216        mode: NavigationMode,
217        cx: &mut ViewContext<Workspace>,
218    ) -> Task<()> {
219        workspace.activate_pane(pane.clone(), cx);
220
221        let to_load = pane.update(cx, |pane, cx| {
222            // Retrieve the weak item handle from the history.
223            let entry = pane.nav_history.borrow_mut().pop(mode)?;
224
225            // If the item is still present in this pane, then activate it.
226            if let Some(index) = entry
227                .item
228                .upgrade(cx)
229                .and_then(|v| pane.index_for_item(v.as_ref()))
230            {
231                if let Some(item) = pane.active_item() {
232                    pane.nav_history.borrow_mut().set_mode(mode);
233                    item.deactivated(cx);
234                    pane.nav_history
235                        .borrow_mut()
236                        .set_mode(NavigationMode::Normal);
237                }
238
239                pane.active_item_index = index;
240                pane.focus_active_item(cx);
241                if let Some(data) = entry.data {
242                    pane.active_item()?.navigate(data, cx);
243                }
244                cx.notify();
245                None
246            }
247            // If the item is no longer present in this pane, then retrieve its
248            // project path in order to reopen it.
249            else {
250                pane.nav_history
251                    .borrow_mut()
252                    .paths_by_item
253                    .get(&entry.item.id())
254                    .cloned()
255                    .map(|project_path| (project_path, entry))
256            }
257        });
258
259        if let Some((project_path, entry)) = to_load {
260            // If the item was no longer present, then load it again from its previous path.
261            let pane = pane.downgrade();
262            let task = workspace.load_path(project_path, cx);
263            cx.spawn(|workspace, mut cx| async move {
264                let task = task.await;
265                if let Some(pane) = pane.upgrade(&cx) {
266                    if let Some((project_entry_id, build_item)) = task.log_err() {
267                        pane.update(&mut cx, |pane, cx| {
268                            pane.nav_history.borrow_mut().set_mode(mode);
269                            let item = pane.open_item(project_entry_id, cx, build_item);
270                            pane.nav_history
271                                .borrow_mut()
272                                .set_mode(NavigationMode::Normal);
273                            if let Some(data) = entry.data {
274                                item.navigate(data, cx);
275                            }
276                        });
277                    } else {
278                        workspace
279                            .update(&mut cx, |workspace, cx| {
280                                Self::navigate_history(workspace, pane, mode, cx)
281                            })
282                            .await;
283                    }
284                }
285            })
286        } else {
287            Task::ready(())
288        }
289    }
290
291    pub fn open_item(
292        &mut self,
293        project_entry_id: ProjectEntryId,
294        cx: &mut ViewContext<Self>,
295        build_item: impl FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
296    ) -> Box<dyn ItemHandle> {
297        for (ix, item) in self.items.iter().enumerate() {
298            if item.project_entry_id(cx) == Some(project_entry_id) {
299                let item = item.boxed_clone();
300                self.activate_item(ix, cx);
301                return item;
302            }
303        }
304
305        let item = build_item(cx);
306        self.add_item(item.boxed_clone(), cx);
307        item
308    }
309
310    pub(crate) fn add_item(&mut self, mut item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
311        item.set_nav_history(self.nav_history.clone(), cx);
312        item.added_to_pane(cx);
313        let item_idx = cmp::min(self.active_item_index + 1, self.items.len());
314        self.items.insert(item_idx, item);
315        self.activate_item(item_idx, cx);
316        cx.notify();
317    }
318
319    pub(crate) fn set_follow_state(
320        &mut self,
321        follower_state: FollowerState,
322        cx: &mut ViewContext<Self>,
323    ) -> Result<()> {
324        let current_view_id = follower_state.current_view_id as usize;
325        let item = follower_state
326            .items_by_leader_view_id
327            .get(&current_view_id)
328            .ok_or_else(|| anyhow!("invalid current view id"))?
329            .clone();
330        self.add_item(item, cx);
331        Ok(())
332    }
333
334    pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {
335        self.items.iter()
336    }
337
338    pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
339        self.items.get(self.active_item_index).cloned()
340    }
341
342    pub fn project_entry_id_for_item(
343        &self,
344        item: &dyn ItemHandle,
345        cx: &AppContext,
346    ) -> Option<ProjectEntryId> {
347        self.items.iter().find_map(|existing| {
348            if existing.id() == item.id() {
349                existing.project_entry_id(cx)
350            } else {
351                None
352            }
353        })
354    }
355
356    pub fn item_for_entry(
357        &self,
358        entry_id: ProjectEntryId,
359        cx: &AppContext,
360    ) -> Option<Box<dyn ItemHandle>> {
361        self.items.iter().find_map(|item| {
362            if item.project_entry_id(cx) == Some(entry_id) {
363                Some(item.boxed_clone())
364            } else {
365                None
366            }
367        })
368    }
369
370    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
371        self.items.iter().position(|i| i.id() == item.id())
372    }
373
374    pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext<Self>) {
375        if index < self.items.len() {
376            let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
377            if prev_active_item_ix != self.active_item_index
378                && prev_active_item_ix < self.items.len()
379            {
380                self.items[prev_active_item_ix].deactivated(cx);
381            }
382            self.update_active_toolbar(cx);
383            self.focus_active_item(cx);
384            self.activate(cx);
385            cx.notify();
386        }
387    }
388
389    pub fn activate_prev_item(&mut self, cx: &mut ViewContext<Self>) {
390        let mut index = self.active_item_index;
391        if index > 0 {
392            index -= 1;
393        } else if self.items.len() > 0 {
394            index = self.items.len() - 1;
395        }
396        self.activate_item(index, cx);
397    }
398
399    pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
400        let mut index = self.active_item_index;
401        if index + 1 < self.items.len() {
402            index += 1;
403        } else {
404            index = 0;
405        }
406        self.activate_item(index, cx);
407    }
408
409    pub fn close_active_item(&mut self, cx: &mut ViewContext<Self>) {
410        if !self.items.is_empty() {
411            self.close_item(self.items[self.active_item_index].id(), cx)
412        }
413    }
414
415    pub fn close_inactive_items(&mut self, cx: &mut ViewContext<Self>) {
416        if !self.items.is_empty() {
417            let active_item_id = self.items[self.active_item_index].id();
418            self.close_items(cx, |id| id != active_item_id);
419        }
420    }
421
422    pub fn close_item(&mut self, view_id_to_close: usize, cx: &mut ViewContext<Self>) {
423        self.close_items(cx, |view_id| view_id == view_id_to_close);
424    }
425
426    pub fn close_items(
427        &mut self,
428        cx: &mut ViewContext<Self>,
429        should_close: impl Fn(usize) -> bool,
430    ) {
431        let mut item_ix = 0;
432        let mut new_active_item_index = self.active_item_index;
433        self.items.retain(|item| {
434            if should_close(item.id()) {
435                if item_ix == self.active_item_index {
436                    item.deactivated(cx);
437                }
438
439                if item_ix < self.active_item_index {
440                    new_active_item_index -= 1;
441                }
442
443                let mut nav_history = self.nav_history.borrow_mut();
444                if let Some(path) = item.project_path(cx) {
445                    nav_history.paths_by_item.insert(item.id(), path);
446                } else {
447                    nav_history.paths_by_item.remove(&item.id());
448                }
449
450                item_ix += 1;
451                false
452            } else {
453                item_ix += 1;
454                true
455            }
456        });
457
458        if self.items.is_empty() {
459            cx.emit(Event::Remove);
460        } else {
461            self.active_item_index = cmp::min(new_active_item_index, self.items.len() - 1);
462            self.focus_active_item(cx);
463            self.activate(cx);
464        }
465        self.update_active_toolbar(cx);
466
467        cx.notify();
468    }
469
470    fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
471        if let Some(active_item) = self.active_item() {
472            cx.focus(active_item);
473        }
474    }
475
476    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
477        cx.emit(Event::Split(direction));
478    }
479
480    pub fn show_toolbar<F, V>(&mut self, cx: &mut ViewContext<Self>, build_toolbar: F)
481    where
482        F: FnOnce(&mut ViewContext<V>) -> V,
483        V: Toolbar,
484    {
485        let type_id = TypeId::of::<V>();
486        if self.active_toolbar_type != Some(type_id) {
487            self.dismiss_toolbar(cx);
488
489            let active_item = self.active_item();
490            self.toolbars
491                .entry(type_id)
492                .or_insert_with(|| Box::new(cx.add_view(build_toolbar)));
493
494            self.active_toolbar_type = Some(type_id);
495            self.active_toolbar_visible =
496                self.toolbars[&type_id].active_item_changed(active_item, cx);
497            cx.notify();
498        }
499    }
500
501    pub fn dismiss_toolbar(&mut self, cx: &mut ViewContext<Self>) {
502        if let Some(active_toolbar_type) = self.active_toolbar_type.take() {
503            self.toolbars
504                .get_mut(&active_toolbar_type)
505                .unwrap()
506                .on_dismiss(cx);
507            self.active_toolbar_visible = false;
508            self.focus_active_item(cx);
509            cx.notify();
510        }
511    }
512
513    pub fn toolbar<T: Toolbar>(&self) -> Option<ViewHandle<T>> {
514        self.toolbars
515            .get(&TypeId::of::<T>())
516            .and_then(|toolbar| toolbar.to_any().downcast())
517    }
518
519    pub fn active_toolbar(&self) -> Option<AnyViewHandle> {
520        let type_id = self.active_toolbar_type?;
521        let toolbar = self.toolbars.get(&type_id)?;
522        if self.active_toolbar_visible {
523            Some(toolbar.to_any())
524        } else {
525            None
526        }
527    }
528
529    fn update_active_toolbar(&mut self, cx: &mut ViewContext<Self>) {
530        let active_item = self.items.get(self.active_item_index);
531        for (toolbar_type_id, toolbar) in &self.toolbars {
532            let visible = toolbar.active_item_changed(active_item.cloned(), cx);
533            if Some(*toolbar_type_id) == self.active_toolbar_type {
534                self.active_toolbar_visible = visible;
535            }
536        }
537    }
538
539    fn render_tabs(&self, cx: &mut RenderContext<Self>) -> ElementBox {
540        let theme = cx.global::<Settings>().theme.clone();
541
542        enum Tabs {}
543        let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
544            let mut row = Flex::row();
545            for (ix, item) in self.items.iter().enumerate() {
546                let is_active = ix == self.active_item_index;
547
548                row.add_child({
549                    let tab_style = if is_active {
550                        theme.workspace.active_tab.clone()
551                    } else {
552                        theme.workspace.tab.clone()
553                    };
554                    let title = item.tab_content(&tab_style, cx);
555
556                    let mut style = if is_active {
557                        theme.workspace.active_tab.clone()
558                    } else {
559                        theme.workspace.tab.clone()
560                    };
561                    if ix == 0 {
562                        style.container.border.left = false;
563                    }
564
565                    EventHandler::new(
566                        Container::new(
567                            Flex::row()
568                                .with_child(
569                                    Align::new({
570                                        let diameter = 7.0;
571                                        let icon_color = if item.has_conflict(cx) {
572                                            Some(style.icon_conflict)
573                                        } else if item.is_dirty(cx) {
574                                            Some(style.icon_dirty)
575                                        } else {
576                                            None
577                                        };
578
579                                        ConstrainedBox::new(
580                                            Canvas::new(move |bounds, _, cx| {
581                                                if let Some(color) = icon_color {
582                                                    let square = RectF::new(
583                                                        bounds.origin(),
584                                                        vec2f(diameter, diameter),
585                                                    );
586                                                    cx.scene.push_quad(Quad {
587                                                        bounds: square,
588                                                        background: Some(color),
589                                                        border: Default::default(),
590                                                        corner_radius: diameter / 2.,
591                                                    });
592                                                }
593                                            })
594                                            .boxed(),
595                                        )
596                                        .with_width(diameter)
597                                        .with_height(diameter)
598                                        .boxed()
599                                    })
600                                    .boxed(),
601                                )
602                                .with_child(
603                                    Container::new(Align::new(title).boxed())
604                                        .with_style(ContainerStyle {
605                                            margin: Margin {
606                                                left: style.spacing,
607                                                right: style.spacing,
608                                                ..Default::default()
609                                            },
610                                            ..Default::default()
611                                        })
612                                        .boxed(),
613                                )
614                                .with_child(
615                                    Align::new(
616                                        ConstrainedBox::new(if mouse_state.hovered {
617                                            let item_id = item.id();
618                                            enum TabCloseButton {}
619                                            let icon = Svg::new("icons/x.svg");
620                                            MouseEventHandler::new::<TabCloseButton, _, _>(
621                                                item_id,
622                                                cx,
623                                                |mouse_state, _| {
624                                                    if mouse_state.hovered {
625                                                        icon.with_color(style.icon_close_active)
626                                                            .boxed()
627                                                    } else {
628                                                        icon.with_color(style.icon_close).boxed()
629                                                    }
630                                                },
631                                            )
632                                            .with_padding(Padding::uniform(4.))
633                                            .with_cursor_style(CursorStyle::PointingHand)
634                                            .on_click(move |cx| {
635                                                cx.dispatch_action(CloseItem(item_id))
636                                            })
637                                            .named("close-tab-icon")
638                                        } else {
639                                            Empty::new().boxed()
640                                        })
641                                        .with_width(style.icon_width)
642                                        .boxed(),
643                                    )
644                                    .boxed(),
645                                )
646                                .boxed(),
647                        )
648                        .with_style(style.container)
649                        .boxed(),
650                    )
651                    .on_mouse_down(move |cx| {
652                        cx.dispatch_action(ActivateItem(ix));
653                        true
654                    })
655                    .boxed()
656                })
657            }
658
659            row.add_child(
660                Empty::new()
661                    .contained()
662                    .with_border(theme.workspace.tab.container.border)
663                    .flexible(0., true)
664                    .named("filler"),
665            );
666
667            row.boxed()
668        });
669
670        ConstrainedBox::new(tabs.boxed())
671            .with_height(theme.workspace.tab.height)
672            .named("tabs")
673    }
674}
675
676impl Entity for Pane {
677    type Event = Event;
678}
679
680impl View for Pane {
681    fn ui_name() -> &'static str {
682        "Pane"
683    }
684
685    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
686        let this = cx.handle();
687
688        EventHandler::new(if let Some(active_item) = self.active_item() {
689            Flex::column()
690                .with_child(self.render_tabs(cx))
691                .with_children(
692                    self.active_toolbar()
693                        .as_ref()
694                        .map(|view| ChildView::new(view).boxed()),
695                )
696                .with_child(ChildView::new(active_item).flexible(1., true).boxed())
697                .boxed()
698        } else {
699            Empty::new().boxed()
700        })
701        .on_navigate_mouse_down(move |direction, cx| {
702            let this = this.clone();
703            match direction {
704                NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))),
705                NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))),
706            }
707
708            true
709        })
710        .named("pane")
711    }
712
713    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
714        self.focus_active_item(cx);
715    }
716}
717
718impl<T: Toolbar> ToolbarHandle for ViewHandle<T> {
719    fn active_item_changed(
720        &self,
721        item: Option<Box<dyn ItemHandle>>,
722        cx: &mut MutableAppContext,
723    ) -> bool {
724        self.update(cx, |this, cx| this.active_item_changed(item, cx))
725    }
726
727    fn on_dismiss(&self, cx: &mut MutableAppContext) {
728        self.update(cx, |this, cx| this.on_dismiss(cx));
729    }
730
731    fn to_any(&self) -> AnyViewHandle {
732        self.into()
733    }
734}
735
736impl ItemNavHistory {
737    pub fn new<T: Item>(history: Rc<RefCell<NavHistory>>, item: &ViewHandle<T>) -> Self {
738        Self {
739            history,
740            item: Rc::new(item.downgrade()),
741        }
742    }
743
744    pub fn history(&self) -> Rc<RefCell<NavHistory>> {
745        self.history.clone()
746    }
747
748    pub fn push<D: 'static + Any>(&self, data: Option<D>) {
749        self.history.borrow_mut().push(data, self.item.clone());
750    }
751}
752
753impl NavHistory {
754    pub fn disable(&mut self) {
755        self.mode = NavigationMode::Disabled;
756    }
757
758    pub fn enable(&mut self) {
759        self.mode = NavigationMode::Normal;
760    }
761
762    pub fn pop_backward(&mut self) -> Option<NavigationEntry> {
763        self.backward_stack.pop_back()
764    }
765
766    pub fn pop_forward(&mut self) -> Option<NavigationEntry> {
767        self.forward_stack.pop_back()
768    }
769
770    fn pop(&mut self, mode: NavigationMode) -> Option<NavigationEntry> {
771        match mode {
772            NavigationMode::Normal | NavigationMode::Disabled => None,
773            NavigationMode::GoingBack => self.pop_backward(),
774            NavigationMode::GoingForward => self.pop_forward(),
775        }
776    }
777
778    fn set_mode(&mut self, mode: NavigationMode) {
779        self.mode = mode;
780    }
781
782    pub fn push<D: 'static + Any>(&mut self, data: Option<D>, item: Rc<dyn WeakItemHandle>) {
783        match self.mode {
784            NavigationMode::Disabled => {}
785            NavigationMode::Normal => {
786                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
787                    self.backward_stack.pop_front();
788                }
789                self.backward_stack.push_back(NavigationEntry {
790                    item,
791                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
792                });
793                self.forward_stack.clear();
794            }
795            NavigationMode::GoingBack => {
796                if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
797                    self.forward_stack.pop_front();
798                }
799                self.forward_stack.push_back(NavigationEntry {
800                    item,
801                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
802                });
803            }
804            NavigationMode::GoingForward => {
805                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
806                    self.backward_stack.pop_front();
807                }
808                self.backward_stack.push_back(NavigationEntry {
809                    item,
810                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
811                });
812            }
813        }
814    }
815}