1use std::sync::atomic::AtomicBool;
2use std::sync::Arc;
3
4use gpui2::{AppContext, Context, ModelContext, View};
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 ModelContext<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 AppContext, livestream: Option<Livestream>) -> View<Self> {
84 {
85 let state = cx.entity(|cx| Self::new(cx).set_livestream(livestream));
86 let render = Self::render;
87 View::for_handle(state, render)
88 }
89 }
90
91 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
92 let theme = theme(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(theme.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("plus", 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("folder_x", Icon::FolderX))
145 .child(IconButton::new("exit", 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("toggle_mic_status", 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("toggle_deafened", 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("toggle_screen_share", 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 AppContext) -> View<Self> {
200 {
201 let state = cx.entity(|cx| Self {
202 title_bar: TitleBar::view(cx, None),
203 });
204 let render = Self::render;
205 View::for_handle(state, render)
206 }
207 }
208
209 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
210 Story::container(cx)
211 .child(Story::title_for::<_, TitleBar>(cx))
212 .child(Story::label(cx, "Default"))
213 .child(self.title_bar.clone())
214 }
215 }
216}