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 loop {
216 // Retrieve the weak item handle from the history.
217 let entry = pane.nav_history.borrow_mut().pop(mode)?;
218
219 // If the item is still present in this pane, then activate it.
220 if let Some(index) = entry
221 .item
222 .upgrade(cx)
223 .and_then(|v| pane.index_for_item(v.as_ref()))
224 {
225 if let Some(item) = pane.active_item() {
226 pane.nav_history.borrow_mut().set_mode(mode);
227 item.deactivated(cx);
228 pane.nav_history
229 .borrow_mut()
230 .set_mode(NavigationMode::Normal);
231 }
232
233 let prev_active_index = mem::replace(&mut pane.active_item_index, index);
234 pane.focus_active_item(cx);
235 let mut navigated = prev_active_index != pane.active_item_index;
236 if let Some(data) = entry.data {
237 navigated |= pane.active_item()?.navigate(data, cx);
238 }
239
240 if navigated {
241 cx.notify();
242 break None;
243 }
244 }
245 // If the item is no longer present in this pane, then retrieve its
246 // project path in order to reopen it.
247 else {
248 break pane
249 .nav_history
250 .borrow_mut()
251 .paths_by_item
252 .get(&entry.item.id())
253 .cloned()
254 .map(|project_path| (project_path, entry));
255 }
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, _| {
268 pane.nav_history.borrow_mut().set_mode(mode);
269 });
270 let item = workspace.update(&mut cx, |workspace, cx| {
271 Self::open_item(
272 workspace,
273 pane.clone(),
274 project_entry_id,
275 cx,
276 build_item,
277 )
278 });
279 pane.update(&mut cx, |pane, cx| {
280 pane.nav_history
281 .borrow_mut()
282 .set_mode(NavigationMode::Normal);
283 if let Some(data) = entry.data {
284 item.navigate(data, cx);
285 }
286 });
287 } else {
288 workspace
289 .update(&mut cx, |workspace, cx| {
290 Self::navigate_history(workspace, pane, mode, cx)
291 })
292 .await;
293 }
294 }
295 })
296 } else {
297 Task::ready(())
298 }
299 }
300
301 pub(crate) fn open_item(
302 workspace: &mut Workspace,
303 pane: ViewHandle<Pane>,
304 project_entry_id: ProjectEntryId,
305 cx: &mut ViewContext<Workspace>,
306 build_item: impl FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
307 ) -> Box<dyn ItemHandle> {
308 let existing_item = pane.update(cx, |pane, cx| {
309 for (ix, item) in pane.items.iter().enumerate() {
310 if item.project_entry_id(cx) == Some(project_entry_id) {
311 let item = item.boxed_clone();
312 pane.activate_item(ix, true, cx);
313 return Some(item);
314 }
315 }
316 None
317 });
318 if let Some(existing_item) = existing_item {
319 existing_item
320 } else {
321 let item = build_item(cx);
322 Self::add_item(workspace, pane, item.boxed_clone(), true, cx);
323 item
324 }
325 }
326
327 pub(crate) fn add_item(
328 workspace: &mut Workspace,
329 pane: ViewHandle<Pane>,
330 item: Box<dyn ItemHandle>,
331 local: bool,
332 cx: &mut ViewContext<Workspace>,
333 ) {
334 // Prevent adding the same item to the pane more than once.
335 if let Some(item_ix) = pane.read(cx).items.iter().position(|i| i.id() == item.id()) {
336 pane.update(cx, |pane, cx| pane.activate_item(item_ix, local, cx));
337 return;
338 }
339
340 item.set_nav_history(pane.read(cx).nav_history.clone(), cx);
341 item.added_to_pane(workspace, pane.clone(), cx);
342 pane.update(cx, |pane, cx| {
343 let item_idx = cmp::min(pane.active_item_index + 1, pane.items.len());
344 pane.items.insert(item_idx, item);
345 pane.activate_item(item_idx, local, cx);
346 cx.notify();
347 });
348 }
349
350 pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {
351 self.items.iter()
352 }
353
354 pub fn items_of_type<'a, T: View>(&'a self) -> impl 'a + Iterator<Item = ViewHandle<T>> {
355 self.items
356 .iter()
357 .filter_map(|item| item.to_any().downcast())
358 }
359
360 pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
361 self.items.get(self.active_item_index).cloned()
362 }
363
364 pub fn project_entry_id_for_item(
365 &self,
366 item: &dyn ItemHandle,
367 cx: &AppContext,
368 ) -> Option<ProjectEntryId> {
369 self.items.iter().find_map(|existing| {
370 if existing.id() == item.id() {
371 existing.project_entry_id(cx)
372 } else {
373 None
374 }
375 })
376 }
377
378 pub fn item_for_entry(
379 &self,
380 entry_id: ProjectEntryId,
381 cx: &AppContext,
382 ) -> Option<Box<dyn ItemHandle>> {
383 self.items.iter().find_map(|item| {
384 if item.project_entry_id(cx) == Some(entry_id) {
385 Some(item.boxed_clone())
386 } else {
387 None
388 }
389 })
390 }
391
392 pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
393 self.items.iter().position(|i| i.id() == item.id())
394 }
395
396 pub fn activate_item(&mut self, index: usize, local: bool, cx: &mut ViewContext<Self>) {
397 if index < self.items.len() {
398 let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
399 if prev_active_item_ix != self.active_item_index
400 && prev_active_item_ix < self.items.len()
401 {
402 self.items[prev_active_item_ix].deactivated(cx);
403 cx.emit(Event::ActivateItem { local });
404 }
405 self.update_active_toolbar(cx);
406 if local {
407 self.focus_active_item(cx);
408 self.activate(cx);
409 }
410 cx.notify();
411 }
412 }
413
414 pub fn activate_prev_item(&mut self, cx: &mut ViewContext<Self>) {
415 let mut index = self.active_item_index;
416 if index > 0 {
417 index -= 1;
418 } else if self.items.len() > 0 {
419 index = self.items.len() - 1;
420 }
421 self.activate_item(index, true, cx);
422 }
423
424 pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
425 let mut index = self.active_item_index;
426 if index + 1 < self.items.len() {
427 index += 1;
428 } else {
429 index = 0;
430 }
431 self.activate_item(index, true, cx);
432 }
433
434 pub fn close_active_item(&mut self, cx: &mut ViewContext<Self>) {
435 if !self.items.is_empty() {
436 self.close_item(self.items[self.active_item_index].id(), cx)
437 }
438 }
439
440 pub fn close_inactive_items(&mut self, cx: &mut ViewContext<Self>) {
441 if !self.items.is_empty() {
442 let active_item_id = self.items[self.active_item_index].id();
443 self.close_items(cx, |id| id != active_item_id);
444 }
445 }
446
447 pub fn close_item(&mut self, view_id_to_close: usize, cx: &mut ViewContext<Self>) {
448 self.close_items(cx, |view_id| view_id == view_id_to_close);
449 }
450
451 pub fn close_items(
452 &mut self,
453 cx: &mut ViewContext<Self>,
454 should_close: impl Fn(usize) -> bool,
455 ) {
456 let mut item_ix = 0;
457 let mut new_active_item_index = self.active_item_index;
458 self.items.retain(|item| {
459 if should_close(item.id()) {
460 if item_ix == self.active_item_index {
461 item.deactivated(cx);
462 }
463
464 if item_ix < self.active_item_index {
465 new_active_item_index -= 1;
466 }
467
468 let mut nav_history = self.nav_history.borrow_mut();
469 if let Some(path) = item.project_path(cx) {
470 nav_history.paths_by_item.insert(item.id(), path);
471 } else {
472 nav_history.paths_by_item.remove(&item.id());
473 }
474
475 item_ix += 1;
476 false
477 } else {
478 item_ix += 1;
479 true
480 }
481 });
482
483 if self.items.is_empty() {
484 cx.emit(Event::Remove);
485 } else {
486 self.active_item_index = cmp::min(new_active_item_index, self.items.len() - 1);
487 self.focus_active_item(cx);
488 self.activate(cx);
489 }
490 self.update_active_toolbar(cx);
491
492 cx.notify();
493 }
494
495 pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
496 if let Some(active_item) = self.active_item() {
497 cx.focus(active_item);
498 }
499 }
500
501 pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
502 cx.emit(Event::Split(direction));
503 }
504
505 pub fn show_toolbar<F, V>(&mut self, cx: &mut ViewContext<Self>, build_toolbar: F)
506 where
507 F: FnOnce(&mut ViewContext<V>) -> V,
508 V: Toolbar,
509 {
510 let type_id = TypeId::of::<V>();
511 if self.active_toolbar_type != Some(type_id) {
512 self.dismiss_toolbar(cx);
513
514 let active_item = self.active_item();
515 self.toolbars
516 .entry(type_id)
517 .or_insert_with(|| Box::new(cx.add_view(build_toolbar)));
518
519 self.active_toolbar_type = Some(type_id);
520 self.active_toolbar_visible =
521 self.toolbars[&type_id].active_item_changed(active_item, cx);
522 cx.notify();
523 }
524 }
525
526 pub fn dismiss_toolbar(&mut self, cx: &mut ViewContext<Self>) {
527 if let Some(active_toolbar_type) = self.active_toolbar_type.take() {
528 self.toolbars
529 .get_mut(&active_toolbar_type)
530 .unwrap()
531 .on_dismiss(cx);
532 self.active_toolbar_visible = false;
533 self.focus_active_item(cx);
534 cx.notify();
535 }
536 }
537
538 pub fn toolbar<T: Toolbar>(&self) -> Option<ViewHandle<T>> {
539 self.toolbars
540 .get(&TypeId::of::<T>())
541 .and_then(|toolbar| toolbar.to_any().downcast())
542 }
543
544 pub fn active_toolbar(&self) -> Option<AnyViewHandle> {
545 let type_id = self.active_toolbar_type?;
546 let toolbar = self.toolbars.get(&type_id)?;
547 if self.active_toolbar_visible {
548 Some(toolbar.to_any())
549 } else {
550 None
551 }
552 }
553
554 fn update_active_toolbar(&mut self, cx: &mut ViewContext<Self>) {
555 let active_item = self.items.get(self.active_item_index);
556 for (toolbar_type_id, toolbar) in &self.toolbars {
557 let visible = toolbar.active_item_changed(active_item.cloned(), cx);
558 if Some(*toolbar_type_id) == self.active_toolbar_type {
559 self.active_toolbar_visible = visible;
560 }
561 }
562 }
563
564 fn render_tabs(&self, cx: &mut RenderContext<Self>) -> ElementBox {
565 let theme = cx.global::<Settings>().theme.clone();
566
567 enum Tabs {}
568 let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
569 let mut row = Flex::row();
570 for (ix, item) in self.items.iter().enumerate() {
571 let is_active = ix == self.active_item_index;
572
573 row.add_child({
574 let tab_style = if is_active {
575 theme.workspace.active_tab.clone()
576 } else {
577 theme.workspace.tab.clone()
578 };
579 let title = item.tab_content(&tab_style, cx);
580
581 let mut style = if is_active {
582 theme.workspace.active_tab.clone()
583 } else {
584 theme.workspace.tab.clone()
585 };
586 if ix == 0 {
587 style.container.border.left = false;
588 }
589
590 EventHandler::new(
591 Container::new(
592 Flex::row()
593 .with_child(
594 Align::new({
595 let diameter = 7.0;
596 let icon_color = if item.has_conflict(cx) {
597 Some(style.icon_conflict)
598 } else if item.is_dirty(cx) {
599 Some(style.icon_dirty)
600 } else {
601 None
602 };
603
604 ConstrainedBox::new(
605 Canvas::new(move |bounds, _, cx| {
606 if let Some(color) = icon_color {
607 let square = RectF::new(
608 bounds.origin(),
609 vec2f(diameter, diameter),
610 );
611 cx.scene.push_quad(Quad {
612 bounds: square,
613 background: Some(color),
614 border: Default::default(),
615 corner_radius: diameter / 2.,
616 });
617 }
618 })
619 .boxed(),
620 )
621 .with_width(diameter)
622 .with_height(diameter)
623 .boxed()
624 })
625 .boxed(),
626 )
627 .with_child(
628 Container::new(Align::new(title).boxed())
629 .with_style(ContainerStyle {
630 margin: Margin {
631 left: style.spacing,
632 right: style.spacing,
633 ..Default::default()
634 },
635 ..Default::default()
636 })
637 .boxed(),
638 )
639 .with_child(
640 Align::new(
641 ConstrainedBox::new(if mouse_state.hovered {
642 let item_id = item.id();
643 enum TabCloseButton {}
644 let icon = Svg::new("icons/x.svg");
645 MouseEventHandler::new::<TabCloseButton, _, _>(
646 item_id,
647 cx,
648 |mouse_state, _| {
649 if mouse_state.hovered {
650 icon.with_color(style.icon_close_active)
651 .boxed()
652 } else {
653 icon.with_color(style.icon_close).boxed()
654 }
655 },
656 )
657 .with_padding(Padding::uniform(4.))
658 .with_cursor_style(CursorStyle::PointingHand)
659 .on_click(move |cx| {
660 cx.dispatch_action(CloseItem(item_id))
661 })
662 .named("close-tab-icon")
663 } else {
664 Empty::new().boxed()
665 })
666 .with_width(style.icon_width)
667 .boxed(),
668 )
669 .boxed(),
670 )
671 .boxed(),
672 )
673 .with_style(style.container)
674 .boxed(),
675 )
676 .on_mouse_down(move |cx| {
677 cx.dispatch_action(ActivateItem(ix));
678 true
679 })
680 .boxed()
681 })
682 }
683
684 row.add_child(
685 Empty::new()
686 .contained()
687 .with_border(theme.workspace.tab.container.border)
688 .flexible(0., true)
689 .named("filler"),
690 );
691
692 row.boxed()
693 });
694
695 ConstrainedBox::new(tabs.boxed())
696 .with_height(theme.workspace.tab.height)
697 .named("tabs")
698 }
699}
700
701impl Entity for Pane {
702 type Event = Event;
703}
704
705impl View for Pane {
706 fn ui_name() -> &'static str {
707 "Pane"
708 }
709
710 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
711 let this = cx.handle();
712
713 EventHandler::new(if let Some(active_item) = self.active_item() {
714 Flex::column()
715 .with_child(self.render_tabs(cx))
716 .with_children(
717 self.active_toolbar()
718 .as_ref()
719 .map(|view| ChildView::new(view).boxed()),
720 )
721 .with_child(ChildView::new(active_item).flexible(1., true).boxed())
722 .boxed()
723 } else {
724 Empty::new().boxed()
725 })
726 .on_navigate_mouse_down(move |direction, cx| {
727 let this = this.clone();
728 match direction {
729 NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))),
730 NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))),
731 }
732
733 true
734 })
735 .named("pane")
736 }
737
738 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
739 self.focus_active_item(cx);
740 }
741}
742
743impl<T: Toolbar> ToolbarHandle for ViewHandle<T> {
744 fn active_item_changed(
745 &self,
746 item: Option<Box<dyn ItemHandle>>,
747 cx: &mut MutableAppContext,
748 ) -> bool {
749 self.update(cx, |this, cx| this.active_item_changed(item, cx))
750 }
751
752 fn on_dismiss(&self, cx: &mut MutableAppContext) {
753 self.update(cx, |this, cx| this.on_dismiss(cx));
754 }
755
756 fn to_any(&self) -> AnyViewHandle {
757 self.into()
758 }
759}
760
761impl ItemNavHistory {
762 pub fn new<T: Item>(history: Rc<RefCell<NavHistory>>, item: &ViewHandle<T>) -> Self {
763 Self {
764 history,
765 item: Rc::new(item.downgrade()),
766 }
767 }
768
769 pub fn history(&self) -> Rc<RefCell<NavHistory>> {
770 self.history.clone()
771 }
772
773 pub fn push<D: 'static + Any>(&self, data: Option<D>) {
774 self.history.borrow_mut().push(data, self.item.clone());
775 }
776}
777
778impl NavHistory {
779 pub fn disable(&mut self) {
780 self.mode = NavigationMode::Disabled;
781 }
782
783 pub fn enable(&mut self) {
784 self.mode = NavigationMode::Normal;
785 }
786
787 pub fn pop_backward(&mut self) -> Option<NavigationEntry> {
788 self.backward_stack.pop_back()
789 }
790
791 pub fn pop_forward(&mut self) -> Option<NavigationEntry> {
792 self.forward_stack.pop_back()
793 }
794
795 fn pop(&mut self, mode: NavigationMode) -> Option<NavigationEntry> {
796 match mode {
797 NavigationMode::Normal | NavigationMode::Disabled => None,
798 NavigationMode::GoingBack => self.pop_backward(),
799 NavigationMode::GoingForward => self.pop_forward(),
800 }
801 }
802
803 fn set_mode(&mut self, mode: NavigationMode) {
804 self.mode = mode;
805 }
806
807 pub fn push<D: 'static + Any>(&mut self, data: Option<D>, item: Rc<dyn WeakItemHandle>) {
808 match self.mode {
809 NavigationMode::Disabled => {}
810 NavigationMode::Normal => {
811 if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
812 self.backward_stack.pop_front();
813 }
814 self.backward_stack.push_back(NavigationEntry {
815 item,
816 data: data.map(|data| Box::new(data) as Box<dyn Any>),
817 });
818 self.forward_stack.clear();
819 }
820 NavigationMode::GoingBack => {
821 if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
822 self.forward_stack.pop_front();
823 }
824 self.forward_stack.push_back(NavigationEntry {
825 item,
826 data: data.map(|data| Box::new(data) as Box<dyn Any>),
827 });
828 }
829 NavigationMode::GoingForward => {
830 if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
831 self.backward_stack.pop_front();
832 }
833 self.backward_stack.push_back(NavigationEntry {
834 item,
835 data: data.map(|data| Box::new(data) as Box<dyn Any>),
836 });
837 }
838 }
839 }
840}