1use std::sync::atomic::AtomicBool;
2use std::sync::Arc;
3
4use gpui2::{Div, Render, View, VisualContext};
5
6use crate::prelude::*;
7use crate::settings::user_settings;
8use crate::{
9 Avatar, Button, Icon, IconButton, IconColor, MicStatus, PlayerStack, PlayerWithCallStatus,
10 ScreenShareStatus, 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(IconColor::Error))
156 .on_click(|title_bar, cx| title_bar.toggle_mic_status(cx)),
157 )
158 .child(
159 IconButton::<TitleBar>::new("toggle_deafened", Icon::AudioOn)
160 .when(self.is_deafened, |this| this.color(IconColor::Error))
161 .on_click(|title_bar, cx| title_bar.toggle_deafened(cx)),
162 )
163 .child(
164 IconButton::<TitleBar>::new("toggle_screen_share", Icon::Screen)
165 .when(
166 self.screen_share_status == ScreenShareStatus::Shared,
167 |this| this.color(IconColor::Accent),
168 )
169 .on_click(|title_bar, cx| {
170 title_bar.toggle_screen_share_status(cx)
171 }),
172 ),
173 )
174 .child(
175 div().px_2().flex().items_center().child(
176 Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
177 .shape(Shape::RoundedRectangle),
178 ),
179 ),
180 )
181 }
182}
183
184#[cfg(feature = "stories")]
185pub use stories::*;
186
187#[cfg(feature = "stories")]
188mod stories {
189 use super::*;
190 use crate::Story;
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 cx.build_view(|cx| Self {
199 title_bar: TitleBar::view(cx, None),
200 })
201 }
202 }
203
204 impl Render for TitleBarStory {
205 type Element = Div<Self>;
206
207 fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
208 Story::container(cx)
209 .child(Story::title_for::<_, TitleBar>(cx))
210 .child(Story::label(cx, "Default"))
211 .child(self.title_bar.clone())
212 }
213 }
214}