1use std::sync::atomic::AtomicBool;
2use std::sync::Arc;
3
4use gpui3::{view, Context, View};
5
6use crate::prelude::*;
7use crate::settings::user_settings;
8use crate::{
9 theme, Avatar, Button, Icon, IconButton, IconColor, MicStatus, PlayerStack,
10 PlayerWithCallStatus, 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 view(
85 cx.entity(|cx| Self::new(cx).set_livestream(livestream)),
86 Self::render,
87 )
88 }
89
90 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
91 let theme = theme(cx);
92 let color = ThemeColor::new(cx);
93 let settings = user_settings(cx);
94
95 // let has_focus = cx.window_is_active();
96 let has_focus = true;
97
98 let player_list = if let Some(livestream) = &self.livestream {
99 livestream.players.clone().into_iter()
100 } else {
101 vec![].into_iter()
102 };
103
104 div()
105 .flex()
106 .items_center()
107 .justify_between()
108 .w_full()
109 .bg(color.background)
110 .py_1()
111 .child(
112 div()
113 .flex()
114 .items_center()
115 .h_full()
116 .gap_4()
117 .px_2()
118 .child(TrafficLights::new().window_has_focus(has_focus))
119 // === Project Info === //
120 .child(
121 div()
122 .flex()
123 .items_center()
124 .gap_1()
125 .when(*settings.titlebar.show_project_owner, |this| {
126 this.child(Button::new("iamnbutler"))
127 })
128 .child(Button::new("zed"))
129 .child(Button::new("nate/gpui2-ui-components")),
130 )
131 .children(player_list.map(|p| PlayerStack::new(p)))
132 .child(IconButton::new(Icon::Plus)),
133 )
134 .child(
135 div()
136 .flex()
137 .items_center()
138 .child(
139 div()
140 .px_2()
141 .flex()
142 .items_center()
143 .gap_1()
144 .child(IconButton::new(Icon::FolderX))
145 .child(IconButton::new(Icon::Exit)),
146 )
147 .child(ToolDivider::new())
148 .child(
149 div()
150 .px_2()
151 .flex()
152 .items_center()
153 .gap_1()
154 .child(
155 IconButton::<TitleBar>::new(Icon::Mic)
156 .when(self.is_mic_muted(), |this| this.color(IconColor::Error))
157 .on_click(|title_bar, cx| title_bar.toggle_mic_status(cx)),
158 )
159 .child(
160 IconButton::<TitleBar>::new(Icon::AudioOn)
161 .when(self.is_deafened, |this| this.color(IconColor::Error))
162 .on_click(|title_bar, cx| title_bar.toggle_deafened(cx)),
163 )
164 .child(
165 IconButton::<TitleBar>::new(Icon::Screen)
166 .when(
167 self.screen_share_status == ScreenShareStatus::Shared,
168 |this| this.color(IconColor::Accent),
169 )
170 .on_click(|title_bar, cx| {
171 title_bar.toggle_screen_share_status(cx)
172 }),
173 ),
174 )
175 .child(
176 div().px_2().flex().items_center().child(
177 Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
178 .shape(Shape::RoundedRectangle),
179 ),
180 ),
181 )
182 }
183}
184
185#[cfg(feature = "stories")]
186pub use stories::*;
187
188#[cfg(feature = "stories")]
189mod stories {
190 use crate::Story;
191
192 use super::*;
193
194 pub struct TitleBarStory {
195 title_bar: View<TitleBar>,
196 }
197
198 impl TitleBarStory {
199 pub fn view(cx: &mut WindowContext) -> View<Self> {
200 view(
201 cx.entity(|cx| Self {
202 title_bar: TitleBar::view(cx, None),
203 }),
204 Self::render,
205 )
206 }
207
208 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
209 Story::container(cx)
210 .child(Story::title_for::<_, TitleBar>(cx))
211 .child(Story::label(cx, "Default"))
212 .child(self.title_bar.clone())
213 }
214 }
215}