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}