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            self.focus_active_item(cx);
400            self.activate(cx);
401            cx.notify();
402        }
403    }
404
405    pub fn activate_prev_item(&mut self, cx: &mut ViewContext<Self>) {
406        let mut index = self.active_item_index;
407        if index > 0 {
408            index -= 1;
409        } else if self.items.len() > 0 {
410            index = self.items.len() - 1;
411        }
412        self.activate_item(index, true, cx);
413    }
414
415    pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
416        let mut index = self.active_item_index;
417        if index + 1 < self.items.len() {
418            index += 1;
419        } else {
420            index = 0;
421        }
422        self.activate_item(index, true, cx);
423    }
424
425    pub fn close_active_item(&mut self, cx: &mut ViewContext<Self>) {
426        if !self.items.is_empty() {
427            self.close_item(self.items[self.active_item_index].id(), cx)
428        }
429    }
430
431    pub fn close_inactive_items(&mut self, cx: &mut ViewContext<Self>) {
432        if !self.items.is_empty() {
433            let active_item_id = self.items[self.active_item_index].id();
434            self.close_items(cx, |id| id != active_item_id);
435        }
436    }
437
438    pub fn close_item(&mut self, view_id_to_close: usize, cx: &mut ViewContext<Self>) {
439        self.close_items(cx, |view_id| view_id == view_id_to_close);
440    }
441
442    pub fn close_items(
443        &mut self,
444        cx: &mut ViewContext<Self>,
445        should_close: impl Fn(usize) -> bool,
446    ) {
447        let mut item_ix = 0;
448        let mut new_active_item_index = self.active_item_index;
449        self.items.retain(|item| {
450            if should_close(item.id()) {
451                if item_ix == self.active_item_index {
452                    item.deactivated(cx);
453                }
454
455                if item_ix < self.active_item_index {
456                    new_active_item_index -= 1;
457                }
458
459                let mut nav_history = self.nav_history.borrow_mut();
460                if let Some(path) = item.project_path(cx) {
461                    nav_history.paths_by_item.insert(item.id(), path);
462                } else {
463                    nav_history.paths_by_item.remove(&item.id());
464                }
465
466                item_ix += 1;
467                false
468            } else {
469                item_ix += 1;
470                true
471            }
472        });
473
474        if self.items.is_empty() {
475            cx.emit(Event::Remove);
476        } else {
477            self.active_item_index = cmp::min(new_active_item_index, self.items.len() - 1);
478            self.focus_active_item(cx);
479            self.activate(cx);
480        }
481        self.update_active_toolbar(cx);
482
483        cx.notify();
484    }
485
486    fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
487        if let Some(active_item) = self.active_item() {
488            cx.focus(active_item);
489        }
490    }
491
492    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
493        cx.emit(Event::Split(direction));
494    }
495
496    pub fn show_toolbar<F, V>(&mut self, cx: &mut ViewContext<Self>, build_toolbar: F)
497    where
498        F: FnOnce(&mut ViewContext<V>) -> V,
499        V: Toolbar,
500    {
501        let type_id = TypeId::of::<V>();
502        if self.active_toolbar_type != Some(type_id) {
503            self.dismiss_toolbar(cx);
504
505            let active_item = self.active_item();
506            self.toolbars
507                .entry(type_id)
508                .or_insert_with(|| Box::new(cx.add_view(build_toolbar)));
509
510            self.active_toolbar_type = Some(type_id);
511            self.active_toolbar_visible =
512                self.toolbars[&type_id].active_item_changed(active_item, cx);
513            cx.notify();
514        }
515    }
516
517    pub fn dismiss_toolbar(&mut self, cx: &mut ViewContext<Self>) {
518        if let Some(active_toolbar_type) = self.active_toolbar_type.take() {
519            self.toolbars
520                .get_mut(&active_toolbar_type)
521                .unwrap()
522                .on_dismiss(cx);
523            self.active_toolbar_visible = false;
524            self.focus_active_item(cx);
525            cx.notify();
526        }
527    }
528
529    pub fn toolbar<T: Toolbar>(&self) -> Option<ViewHandle<T>> {
530        self.toolbars
531            .get(&TypeId::of::<T>())
532            .and_then(|toolbar| toolbar.to_any().downcast())
533    }
534
535    pub fn active_toolbar(&self) -> Option<AnyViewHandle> {
536        let type_id = self.active_toolbar_type?;
537        let toolbar = self.toolbars.get(&type_id)?;
538        if self.active_toolbar_visible {
539            Some(toolbar.to_any())
540        } else {
541            None
542        }
543    }
544
545    fn update_active_toolbar(&mut self, cx: &mut ViewContext<Self>) {
546        let active_item = self.items.get(self.active_item_index);
547        for (toolbar_type_id, toolbar) in &self.toolbars {
548            let visible = toolbar.active_item_changed(active_item.cloned(), cx);
549            if Some(*toolbar_type_id) == self.active_toolbar_type {
550                self.active_toolbar_visible = visible;
551            }
552        }
553    }
554
555    fn render_tabs(&self, cx: &mut RenderContext<Self>) -> ElementBox {
556        let theme = cx.global::<Settings>().theme.clone();
557
558        enum Tabs {}
559        let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
560            let mut row = Flex::row();
561            for (ix, item) in self.items.iter().enumerate() {
562                let is_active = ix == self.active_item_index;
563
564                row.add_child({
565                    let tab_style = if is_active {
566                        theme.workspace.active_tab.clone()
567                    } else {
568                        theme.workspace.tab.clone()
569                    };
570                    let title = item.tab_content(&tab_style, cx);
571
572                    let mut style = if is_active {
573                        theme.workspace.active_tab.clone()
574                    } else {
575                        theme.workspace.tab.clone()
576                    };
577                    if ix == 0 {
578                        style.container.border.left = false;
579                    }
580
581                    EventHandler::new(
582                        Container::new(
583                            Flex::row()
584                                .with_child(
585                                    Align::new({
586                                        let diameter = 7.0;
587                                        let icon_color = if item.has_conflict(cx) {
588                                            Some(style.icon_conflict)
589                                        } else if item.is_dirty(cx) {
590                                            Some(style.icon_dirty)
591                                        } else {
592                                            None
593                                        };
594
595                                        ConstrainedBox::new(
596                                            Canvas::new(move |bounds, _, cx| {
597                                                if let Some(color) = icon_color {
598                                                    let square = RectF::new(
599                                                        bounds.origin(),
600                                                        vec2f(diameter, diameter),
601                                                    );
602                                                    cx.scene.push_quad(Quad {
603                                                        bounds: square,
604                                                        background: Some(color),
605                                                        border: Default::default(),
606                                                        corner_radius: diameter / 2.,
607                                                    });
608                                                }
609                                            })
610                                            .boxed(),
611                                        )
612                                        .with_width(diameter)
613                                        .with_height(diameter)
614                                        .boxed()
615                                    })
616                                    .boxed(),
617                                )
618                                .with_child(
619                                    Container::new(Align::new(title).boxed())
620                                        .with_style(ContainerStyle {
621                                            margin: Margin {
622                                                left: style.spacing,
623                                                right: style.spacing,
624                                                ..Default::default()
625                                            },
626                                            ..Default::default()
627                                        })
628                                        .boxed(),
629                                )
630                                .with_child(
631                                    Align::new(
632                                        ConstrainedBox::new(if mouse_state.hovered {
633                                            let item_id = item.id();
634                                            enum TabCloseButton {}
635                                            let icon = Svg::new("icons/x.svg");
636                                            MouseEventHandler::new::<TabCloseButton, _, _>(
637                                                item_id,
638                                                cx,
639                                                |mouse_state, _| {
640                                                    if mouse_state.hovered {
641                                                        icon.with_color(style.icon_close_active)
642                                                            .boxed()
643                                                    } else {
644                                                        icon.with_color(style.icon_close).boxed()
645                                                    }
646                                                },
647                                            )
648                                            .with_padding(Padding::uniform(4.))
649                                            .with_cursor_style(CursorStyle::PointingHand)
650                                            .on_click(move |cx| {
651                                                cx.dispatch_action(CloseItem(item_id))
652                                            })
653                                            .named("close-tab-icon")
654                                        } else {
655                                            Empty::new().boxed()
656                                        })
657                                        .with_width(style.icon_width)
658                                        .boxed(),
659                                    )
660                                    .boxed(),
661                                )
662                                .boxed(),
663                        )
664                        .with_style(style.container)
665                        .boxed(),
666                    )
667                    .on_mouse_down(move |cx| {
668                        cx.dispatch_action(ActivateItem(ix));
669                        true
670                    })
671                    .boxed()
672                })
673            }
674
675            row.add_child(
676                Empty::new()
677                    .contained()
678                    .with_border(theme.workspace.tab.container.border)
679                    .flexible(0., true)
680                    .named("filler"),
681            );
682
683            row.boxed()
684        });
685
686        ConstrainedBox::new(tabs.boxed())
687            .with_height(theme.workspace.tab.height)
688            .named("tabs")
689    }
690}
691
692impl Entity for Pane {
693    type Event = Event;
694}
695
696impl View for Pane {
697    fn ui_name() -> &'static str {
698        "Pane"
699    }
700
701    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
702        let this = cx.handle();
703
704        EventHandler::new(if let Some(active_item) = self.active_item() {
705            Flex::column()
706                .with_child(self.render_tabs(cx))
707                .with_children(
708                    self.active_toolbar()
709                        .as_ref()
710                        .map(|view| ChildView::new(view).boxed()),
711                )
712                .with_child(ChildView::new(active_item).flexible(1., true).boxed())
713                .boxed()
714        } else {
715            Empty::new().boxed()
716        })
717        .on_navigate_mouse_down(move |direction, cx| {
718            let this = this.clone();
719            match direction {
720                NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))),
721                NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))),
722            }
723
724            true
725        })
726        .named("pane")
727    }
728
729    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
730        self.focus_active_item(cx);
731    }
732}
733
734impl<T: Toolbar> ToolbarHandle for ViewHandle<T> {
735    fn active_item_changed(
736        &self,
737        item: Option<Box<dyn ItemHandle>>,
738        cx: &mut MutableAppContext,
739    ) -> bool {
740        self.update(cx, |this, cx| this.active_item_changed(item, cx))
741    }
742
743    fn on_dismiss(&self, cx: &mut MutableAppContext) {
744        self.update(cx, |this, cx| this.on_dismiss(cx));
745    }
746
747    fn to_any(&self) -> AnyViewHandle {
748        self.into()
749    }
750}
751
752impl ItemNavHistory {
753    pub fn new<T: Item>(history: Rc<RefCell<NavHistory>>, item: &ViewHandle<T>) -> Self {
754        Self {
755            history,
756            item: Rc::new(item.downgrade()),
757        }
758    }
759
760    pub fn history(&self) -> Rc<RefCell<NavHistory>> {
761        self.history.clone()
762    }
763
764    pub fn push<D: 'static + Any>(&self, data: Option<D>) {
765        self.history.borrow_mut().push(data, self.item.clone());
766    }
767}
768
769impl NavHistory {
770    pub fn disable(&mut self) {
771        self.mode = NavigationMode::Disabled;
772    }
773
774    pub fn enable(&mut self) {
775        self.mode = NavigationMode::Normal;
776    }
777
778    pub fn pop_backward(&mut self) -> Option<NavigationEntry> {
779        self.backward_stack.pop_back()
780    }
781
782    pub fn pop_forward(&mut self) -> Option<NavigationEntry> {
783        self.forward_stack.pop_back()
784    }
785
786    fn pop(&mut self, mode: NavigationMode) -> Option<NavigationEntry> {
787        match mode {
788            NavigationMode::Normal | NavigationMode::Disabled => None,
789            NavigationMode::GoingBack => self.pop_backward(),
790            NavigationMode::GoingForward => self.pop_forward(),
791        }
792    }
793
794    fn set_mode(&mut self, mode: NavigationMode) {
795        self.mode = mode;
796    }
797
798    pub fn push<D: 'static + Any>(&mut self, data: Option<D>, item: Rc<dyn WeakItemHandle>) {
799        match self.mode {
800            NavigationMode::Disabled => {}
801            NavigationMode::Normal => {
802                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
803                    self.backward_stack.pop_front();
804                }
805                self.backward_stack.push_back(NavigationEntry {
806                    item,
807                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
808                });
809                self.forward_stack.clear();
810            }
811            NavigationMode::GoingBack => {
812                if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
813                    self.forward_stack.pop_front();
814                }
815                self.forward_stack.push_back(NavigationEntry {
816                    item,
817                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
818                });
819            }
820            NavigationMode::GoingForward => {
821                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
822                    self.backward_stack.pop_front();
823                }
824                self.backward_stack.push_back(NavigationEntry {
825                    item,
826                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
827                });
828            }
829        }
830    }
831}