1use std::time::Duration;
19
20use gpui_kit::{
21 AnyView, App, ElementId, Entity, InteractiveElement as _, IntoElement, ParentElement as _,
22 RenderOnce, StyleRefinement, Styled, Window,
23 base::{self, Disableable as _, PresencePhase, StyledExt as _},
24 div,
25 prelude::FluentBuilder as _,
26};
27
28pub use gpui_kit::base::{NavMotion, NavOperation, NavPage, NavStackEvent, NavStackState};
29
30use crate::{Button, Icon, Theme};
31use gpui_kit::assets::IconName;
32
33pub trait NavStackExt {
39 fn discard_forward(&self, cx: &mut App);
43
44 fn pop_and_discard(&self, motion: NavMotion, cx: &mut App) -> Option<AnyView>;
49}
50
51impl NavStackExt for Entity<NavStackState> {
52 fn discard_forward(&self, cx: &mut App) {
53 self.update(cx, |stack, cx| {
54 if stack.forward_views().len() == 0 {
55 return;
56 }
57 let views: Vec<AnyView> = stack.views().cloned().collect();
58 stack.clear(cx);
59 for view in views {
60 stack.push(view, NavMotion::Immediate, cx);
61 }
62 });
63 }
64
65 fn pop_and_discard(&self, motion: NavMotion, cx: &mut App) -> Option<AnyView> {
66 let popped = self.update(cx, |stack, cx| stack.pop(motion, cx))?;
67 let animated = motion == NavMotion::Animated && !cx.reduce_motion();
71 if !animated {
72 self.discard_forward(cx);
73 return Some(popped);
74 }
75 let delay = Theme::global(cx).motion.fast + Duration::from_millis(16);
76 let stack = self.clone();
77 let waiting = popped.clone();
78 cx.spawn(async move |cx| {
79 cx.background_executor().timer(delay).await;
80 cx.update(|cx| {
81 let still_waiting = stack.read(cx).forward_views().next() == Some(&waiting);
85 if still_waiting {
86 stack.discard_forward(cx);
87 }
88 });
89 })
90 .detach();
91 Some(popped)
92 }
93}
94
95#[derive(IntoElement)]
101pub struct NavStack {
102 state: Entity<NavStackState>,
103 style: StyleRefinement,
104}
105
106impl NavStack {
107 pub fn new(state: &Entity<NavStackState>) -> Self {
109 Self {
110 state: state.clone(),
111 style: StyleRefinement::default(),
112 }
113 }
114}
115
116impl Styled for NavStack {
117 fn style(&mut self) -> &mut StyleRefinement {
118 &mut self.style
119 }
120}
121
122impl RenderOnce for NavStack {
123 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
124 let transition = Theme::global(cx).motion.fast_transition();
125 base::NavStack::new(&self.state)
126 .size_full()
127 .refine_style(&self.style)
128 .transition(transition)
129 .item(|page, _, _| {
130 let opacity = match page.phase() {
131 PresencePhase::Entering => page.progress(),
132 PresencePhase::Exiting => 1. - page.progress(),
133 PresencePhase::Present => 1.,
134 PresencePhase::Absent => 0.,
135 };
136 page.opacity(opacity).into_any_element()
137 })
138 }
139}
140
141#[derive(IntoElement)]
147pub struct NavButtons {
148 id: ElementId,
149 state: Entity<NavStackState>,
150 tooltips: bool,
151 back_icon: Icon,
152 forward_icon: Icon,
153}
154
155impl NavButtons {
156 pub fn new(id: impl Into<ElementId>, state: &Entity<NavStackState>) -> Self {
158 Self {
159 id: id.into(),
160 state: state.clone(),
161 tooltips: true,
162 back_icon: IconName::ArrowLeft.into(),
163 forward_icon: IconName::ArrowRight.into(),
164 }
165 }
166
167 pub fn back_icon(mut self, icon: impl Into<Icon>) -> Self {
170 self.back_icon = icon.into();
171 self
172 }
173
174 pub fn forward_icon(mut self, icon: impl Into<Icon>) -> Self {
176 self.forward_icon = icon.into();
177 self
178 }
179
180 pub fn tooltips(mut self, tooltips: bool) -> Self {
182 self.tooltips = tooltips;
183 self
184 }
185}
186
187impl RenderOnce for NavButtons {
188 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
189 let (can_go_back, can_go_forward) = {
190 let state = self.state.read(cx);
191 (state.depth() > 1, state.forward_views().len() > 0)
192 };
193 let back_state = self.state.clone();
194 let forward_state = self.state;
195 div()
196 .id(self.id.clone())
197 .flex()
198 .items_center()
199 .gap_1()
200 .child(
201 Button::new(ElementId::NamedChild(self.id.clone().into(), "back".into()))
202 .ghost()
203 .icon(self.back_icon)
204 .accessibility_label("Back")
205 .when(self.tooltips, |this| this.tooltip("Back"))
206 .disabled(!can_go_back)
207 .on_click(move |_, _, cx| {
208 back_state.update(cx, |state, cx| {
209 state.pop(NavMotion::Animated, cx);
210 });
211 }),
212 )
213 .child(
214 Button::new(ElementId::NamedChild(self.id.into(), "forward".into()))
215 .ghost()
216 .icon(self.forward_icon)
217 .accessibility_label("Forward")
218 .when(self.tooltips, |this| this.tooltip("Forward"))
219 .disabled(!can_go_forward)
220 .on_click(move |_, _, cx| {
221 forward_state.update(cx, |state, cx| {
222 state.forward(NavMotion::Animated, cx);
223 });
224 }),
225 )
226 }
227}