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