title_bar.rs

  1use std::sync::atomic::AtomicBool;
  2use std::sync::Arc;
  3
  4use gpui3::{view, Context, View};
  5
  6use crate::prelude::*;
  7use crate::{
  8    random_players_with_call_status, theme, Avatar, Button, Icon, IconButton, IconColor, MicStatus,
  9    PlayerStack, PlayerWithCallStatus, ScreenShareStatus, ToolDivider, TrafficLights,
 10};
 11
 12#[derive(Clone)]
 13pub struct Livestream {
 14    pub players: Vec<PlayerWithCallStatus>,
 15    pub channel: Option<String>, // projects
 16                                 // windows
 17}
 18
 19#[derive(Clone)]
 20pub struct TitleBar {
 21    /// If the window is active from the OS's perspective.
 22    is_active: Arc<AtomicBool>,
 23    livestream: Option<Livestream>,
 24    mic_status: MicStatus,
 25    is_deafened: bool,
 26    screen_share_status: ScreenShareStatus,
 27}
 28
 29impl TitleBar {
 30    pub fn new(cx: &mut ViewContext<Self>) -> Self {
 31        let is_active = Arc::new(AtomicBool::new(true));
 32        let active = is_active.clone();
 33
 34        // cx.observe_window_activation(move |_, is_active, cx| {
 35        //     active.store(is_active, std::sync::atomic::Ordering::SeqCst);
 36        //     cx.notify();
 37        // })
 38        // .detach();
 39
 40        Self {
 41            is_active,
 42            livestream: None,
 43            mic_status: MicStatus::Unmuted,
 44            is_deafened: false,
 45            screen_share_status: ScreenShareStatus::NotShared,
 46        }
 47    }
 48
 49    pub fn set_livestream(mut self, livestream: Option<Livestream>) -> Self {
 50        self.livestream = livestream;
 51        self
 52    }
 53
 54    pub fn is_mic_muted(&self) -> bool {
 55        self.mic_status == MicStatus::Muted
 56    }
 57
 58    pub fn toggle_mic_status(&mut self, cx: &mut ViewContext<Self>) {
 59        self.mic_status = self.mic_status.inverse();
 60
 61        // Undeafen yourself when unmuting the mic while deafened.
 62        if self.is_deafened && self.mic_status == MicStatus::Unmuted {
 63            self.is_deafened = false;
 64        }
 65
 66        cx.notify();
 67    }
 68
 69    pub fn toggle_deafened(&mut self, cx: &mut ViewContext<Self>) {
 70        self.is_deafened = !self.is_deafened;
 71        self.mic_status = MicStatus::Muted;
 72
 73        cx.notify()
 74    }
 75
 76    pub fn toggle_screen_share_status(&mut self, cx: &mut ViewContext<Self>) {
 77        self.screen_share_status = self.screen_share_status.inverse();
 78
 79        cx.notify();
 80    }
 81
 82    pub fn view(cx: &mut WindowContext) -> View<Self> {
 83        view(
 84            cx.entity(|cx| {
 85                Self::new(cx).set_livestream(Some(Livestream {
 86                    players: random_players_with_call_status(7),
 87                    channel: Some("gpui2-ui".to_string()),
 88                }))
 89            }),
 90            Self::render,
 91        )
 92    }
 93
 94    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
 95        let theme = theme(cx);
 96        // let has_focus = cx.window_is_active();
 97        let has_focus = true;
 98
 99        let player_list = if let Some(livestream) = &self.livestream {
100            livestream.players.clone().into_iter()
101        } else {
102            vec![].into_iter()
103        };
104
105        div()
106            .flex()
107            .items_center()
108            .justify_between()
109            .w_full()
110            .h_8()
111            .fill(theme.lowest.base.default.background)
112            .child(
113                div()
114                    .flex()
115                    .items_center()
116                    .h_full()
117                    .gap_4()
118                    .px_2()
119                    .child(TrafficLights::new().window_has_focus(has_focus))
120                    // === Project Info === //
121                    .child(
122                        div()
123                            .flex()
124                            .items_center()
125                            .gap_1()
126                            .child(Button::new("zed"))
127                            .child(Button::new("nate/gpui2-ui-components")),
128                    )
129                    .children(player_list.map(|p| PlayerStack::new(p)))
130                    .child(IconButton::new(Icon::Plus)),
131            )
132            .child(
133                div()
134                    .flex()
135                    .items_center()
136                    .child(
137                        div()
138                            .px_2()
139                            .flex()
140                            .items_center()
141                            .gap_1()
142                            .child(IconButton::new(Icon::FolderX))
143                            .child(IconButton::new(Icon::Close)),
144                    )
145                    .child(ToolDivider::new())
146                    .child(
147                        div()
148                            .px_2()
149                            .flex()
150                            .items_center()
151                            .gap_1()
152                            .child(
153                                IconButton::<TitleBar>::new(Icon::Mic)
154                                    .when(self.is_mic_muted(), |this| this.color(IconColor::Error))
155                                    .on_click(|title_bar, cx| title_bar.toggle_mic_status(cx)),
156                            )
157                            .child(
158                                IconButton::<TitleBar>::new(Icon::AudioOn)
159                                    .when(self.is_deafened, |this| this.color(IconColor::Error))
160                                    .on_click(|title_bar, cx| title_bar.toggle_deafened(cx)),
161                            )
162                            .child(
163                                IconButton::<TitleBar>::new(Icon::Screen)
164                                    .when(
165                                        self.screen_share_status == ScreenShareStatus::Shared,
166                                        |this| this.color(IconColor::Accent),
167                                    )
168                                    .on_click(|title_bar, cx| {
169                                        title_bar.toggle_screen_share_status(cx)
170                                    }),
171                            ),
172                    )
173                    .child(
174                        div().px_2().flex().items_center().child(
175                            Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
176                                .shape(Shape::RoundedRectangle),
177                        ),
178                    ),
179            )
180    }
181}
182
183#[cfg(feature = "stories")]
184pub use stories::*;
185
186#[cfg(feature = "stories")]
187mod stories {
188    use crate::Story;
189
190    use super::*;
191
192    pub struct TitleBarStory {
193        title_bar: View<TitleBar>,
194    }
195
196    impl TitleBarStory {
197        pub fn view(cx: &mut WindowContext) -> View<Self> {
198            view(
199                cx.entity(|cx| Self {
200                    title_bar: TitleBar::view(cx),
201                }),
202                Self::render,
203            )
204        }
205
206        fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
207            Story::container(cx)
208                .child(Story::title_for::<_, TitleBar>(cx))
209                .child(Story::label(cx, "Default"))
210                .child(self.title_bar.clone())
211        }
212    }
213}