title_bar.rs

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