pane.rs

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