1use gpui::Hsla;
2
3#[derive(Debug, Clone, Copy)]
4pub struct PlayerColor {
5 pub cursor: Hsla,
6 pub background: Hsla,
7 pub selection: Hsla,
8}
9
10/// A collection of colors that are used to color players in the editor.
11///
12/// The first color is always the local player's color, usually a blue.
13///
14/// The rest of the default colors crisscross back and forth on the
15/// color wheel so that the colors are as distinct as possible.
16#[derive(Clone)]
17pub struct PlayerColors(pub Vec<PlayerColor>);
18
19impl Default for PlayerColors {
20 /// Don't use this!
21 /// We have to have a default to be `[refineable::Refinable]`.
22 /// todo!("Find a way to not need this for Refinable")
23 fn default() -> Self {
24 Self::dark()
25 }
26}
27
28impl PlayerColors {
29 pub fn dark() -> Self {
30 Self(vec![
31 PlayerColor {
32 cursor: blue().dark().step_9(),
33 background: blue().dark().step_5(),
34 selection: blue().dark().step_3(),
35 },
36 PlayerColor {
37 cursor: orange().dark().step_9(),
38 background: orange().dark().step_5(),
39 selection: orange().dark().step_3(),
40 },
41 PlayerColor {
42 cursor: pink().dark().step_9(),
43 background: pink().dark().step_5(),
44 selection: pink().dark().step_3(),
45 },
46 PlayerColor {
47 cursor: lime().dark().step_9(),
48 background: lime().dark().step_5(),
49 selection: lime().dark().step_3(),
50 },
51 PlayerColor {
52 cursor: purple().dark().step_9(),
53 background: purple().dark().step_5(),
54 selection: purple().dark().step_3(),
55 },
56 PlayerColor {
57 cursor: amber().dark().step_9(),
58 background: amber().dark().step_5(),
59 selection: amber().dark().step_3(),
60 },
61 PlayerColor {
62 cursor: jade().dark().step_9(),
63 background: jade().dark().step_5(),
64 selection: jade().dark().step_3(),
65 },
66 PlayerColor {
67 cursor: red().dark().step_9(),
68 background: red().dark().step_5(),
69 selection: red().dark().step_3(),
70 },
71 ])
72 }
73
74 pub fn light() -> Self {
75 Self(vec![
76 PlayerColor {
77 cursor: blue().light().step_9(),
78 background: blue().light().step_4(),
79 selection: blue().light().step_3(),
80 },
81 PlayerColor {
82 cursor: orange().light().step_9(),
83 background: orange().light().step_4(),
84 selection: orange().light().step_3(),
85 },
86 PlayerColor {
87 cursor: pink().light().step_9(),
88 background: pink().light().step_4(),
89 selection: pink().light().step_3(),
90 },
91 PlayerColor {
92 cursor: lime().light().step_9(),
93 background: lime().light().step_4(),
94 selection: lime().light().step_3(),
95 },
96 PlayerColor {
97 cursor: purple().light().step_9(),
98 background: purple().light().step_4(),
99 selection: purple().light().step_3(),
100 },
101 PlayerColor {
102 cursor: amber().light().step_9(),
103 background: amber().light().step_4(),
104 selection: amber().light().step_3(),
105 },
106 PlayerColor {
107 cursor: jade().light().step_9(),
108 background: jade().light().step_4(),
109 selection: jade().light().step_3(),
110 },
111 PlayerColor {
112 cursor: red().light().step_9(),
113 background: red().light().step_4(),
114 selection: red().light().step_3(),
115 },
116 ])
117 }
118}
119
120impl PlayerColors {
121 pub fn local(&self) -> PlayerColor {
122 // todo!("use a valid color");
123 *self.0.first().unwrap()
124 }
125
126 pub fn absent(&self) -> PlayerColor {
127 // todo!("use a valid color");
128 *self.0.last().unwrap()
129 }
130
131 pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
132 let len = self.0.len() - 1;
133 self.0[(participant_index as usize % len) + 1]
134 }
135}
136
137#[cfg(feature = "stories")]
138pub use stories::*;
139
140use crate::{amber, blue, jade, lime, orange, pink, purple, red};
141
142#[cfg(feature = "stories")]
143mod stories {
144 use super::*;
145 use crate::{ActiveTheme, Story};
146 use gpui::{div, img, px, Div, ParentComponent, Render, Styled, ViewContext};
147
148 pub struct PlayerStory;
149
150 impl Render for PlayerStory {
151 type Element = Div<Self>;
152
153 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
154 Story::container(cx).child(
155 div()
156 .flex()
157 .flex_col()
158 .gap_4()
159 .child(Story::title_for::<_, PlayerColors>(cx))
160 .child(Story::label(cx, "Player Colors"))
161 .child(
162 div()
163 .flex()
164 .flex_col()
165 .gap_1()
166 .child(
167 div().flex().gap_1().children(
168 cx.theme().players().0.clone().iter_mut().map(|player| {
169 div().w_8().h_8().rounded_md().bg(player.cursor)
170 }),
171 ),
172 )
173 .child(div().flex().gap_1().children(
174 cx.theme().players().0.clone().iter_mut().map(|player| {
175 div().w_8().h_8().rounded_md().bg(player.background)
176 }),
177 ))
178 .child(div().flex().gap_1().children(
179 cx.theme().players().0.clone().iter_mut().map(|player| {
180 div().w_8().h_8().rounded_md().bg(player.selection)
181 }),
182 )),
183 )
184 .child(Story::label(cx, "Avatar Rings"))
185 .child(div().flex().gap_1().children(
186 cx.theme().players().0.clone().iter_mut().map(|player| {
187 div()
188 .my_1()
189 .rounded_full()
190 .border_2()
191 .border_color(player.cursor)
192 .child(
193 img()
194 .rounded_full()
195 .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
196 .size_6()
197 .bg(gpui::red()),
198 )
199 }),
200 ))
201 .child(Story::label(cx, "Player Backgrounds"))
202 .child(div().flex().gap_1().children(
203 cx.theme().players().0.clone().iter_mut().map(|player| {
204 div()
205 .my_1()
206 .rounded_xl()
207 .flex()
208 .items_center()
209 .h_8()
210 .py_0p5()
211 .px_1p5()
212 .bg(player.background)
213 .child(
214 div().relative().neg_mx_1().rounded_full().z_index(3)
215 .border_2()
216 .border_color(player.background)
217 .size(px(28.))
218 .child(
219 img()
220 .rounded_full()
221 .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
222 .size(px(24.))
223 .bg(gpui::red()),
224 ),
225 ).child(
226 div().relative().neg_mx_1().rounded_full().z_index(2)
227 .border_2()
228 .border_color(player.background)
229 .size(px(28.))
230 .child(
231 img()
232 .rounded_full()
233 .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
234 .size(px(24.))
235 .bg(gpui::red()),
236 ),
237 ).child(
238 div().relative().neg_mx_1().rounded_full().z_index(1)
239 .border_2()
240 .border_color(player.background)
241 .size(px(28.))
242 .child(
243 img()
244 .rounded_full()
245 .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
246 .size(px(24.))
247 .bg(gpui::red()),
248 ),
249 )
250 }),
251 ))
252 .child(Story::label(cx, "Player Selections"))
253 .child(div().flex().flex_col().gap_px().children(
254 cx.theme().players().0.clone().iter_mut().map(|player| {
255 div()
256 .flex()
257 .child(
258 div()
259 .flex()
260 .flex_none()
261 .rounded_sm()
262 .px_0p5()
263 .text_color(cx.theme().colors().text)
264 .bg(player.selection)
265 .child("The brown fox jumped over the lazy dog."),
266 )
267 .child(div().flex_1())
268 }),
269 )),
270 )
271 }
272 }
273}