gpui_kit/display/
status.rs1use gpui::{App, IntoElement, ParentElement, RenderOnce, SharedString, Styled, Window, div, px};
2use gpui_kit_semantics::{NodeSpec, Role, Semantic};
3use gpui_kit_theme::{ActiveTheme, Radius, Space, TypeScale};
4
5use crate::display::badge::Tone;
6use crate::foundation::{Ident, StyledExt};
7use crate::motion;
8
9#[derive(Debug, IntoElement)]
11pub struct StatusDot {
12 tone: Tone,
13 busy: Option<Ident>,
16}
17
18impl StatusDot {
19 pub fn new(tone: Tone) -> Self {
20 Self { tone, busy: None }
21 }
22
23 pub fn busy(mut self, ident: impl Into<Ident>) -> Self {
29 self.busy = Some(ident.into());
30 self
31 }
32}
33
34impl RenderOnce for StatusDot {
35 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
36 let theme = cx.theme().clone();
37 let dot = div()
38 .flex_none()
39 .size(px(7.0))
40 .rounded_full()
41 .bg(self.tone.color(&theme));
42 match self.busy {
43 Some(ident) => motion::breathe(dot, ident.element_id(), &theme, cx),
44 None => dot.into_any_element(),
45 }
46 }
47}
48
49#[derive(Debug, IntoElement)]
51pub struct StatusLine {
52 ident: Option<Ident>,
53 label: SharedString,
54 tone: Tone,
55}
56
57impl StatusLine {
58 pub fn new(label: impl Into<SharedString>, tone: Tone) -> Self {
59 Self {
60 ident: None,
61 label: label.into(),
62 tone,
63 }
64 }
65
66 pub fn id(mut self, ident: impl Into<Ident>) -> Self {
67 self.ident = Some(ident.into());
68 self
69 }
70}
71
72impl RenderOnce for StatusLine {
73 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
74 let theme = cx.theme().clone();
75 let element = div()
76 .row()
77 .gap_token(&theme, Space::Sm)
78 .type_scale(&theme, TypeScale::Label)
79 .text_color(theme.colors.text_muted)
80 .child(StatusDot::new(self.tone))
81 .child(self.label.clone());
82 match self.ident {
83 Some(ident) => element
84 .semantic_in(
85 cx,
86 NodeSpec::new(ident.semantic_id(), Role::Status).text(self.label.clone()),
87 )
88 .into_any_element(),
89 None => element.into_any_element(),
90 }
91 }
92}
93
94#[derive(Debug, IntoElement)]
99pub struct Callout {
100 ident: Option<Ident>,
101 message: SharedString,
102 tone: Tone,
103}
104
105impl Callout {
106 pub fn new(message: impl Into<SharedString>, tone: Tone) -> Self {
107 Self {
108 ident: None,
109 message: message.into(),
110 tone,
111 }
112 }
113
114 pub fn id(mut self, ident: impl Into<Ident>) -> Self {
115 self.ident = Some(ident.into());
116 self
117 }
118}
119
120impl RenderOnce for Callout {
121 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
122 let theme = cx.theme().clone();
123 let color = self.tone.color(&theme);
124 let content = div()
125 .w_full()
126 .flex()
127 .flex_row()
128 .items_start()
129 .gap_token(&theme, Space::Sm)
130 .child(div().mt(px(5.0)).child(StatusDot::new(self.tone)))
131 .child(div().min_w_0().child(self.message.clone()));
132
133 let frame = div()
134 .w_full()
135 .px_token(&theme, Space::Lg)
136 .py_token(&theme, Space::Md)
137 .radius(&theme, Radius::Card)
138 .bg(color.opacity(0.14))
139 .type_scale(&theme, TypeScale::Label)
140 .line_height(px(theme.typography.body.line_height))
141 .text_color(color.opacity(0.92));
142
143 match self.ident {
148 Some(ident) => frame
149 .child(motion::content_in(
150 ident.child("in").element_id(),
151 &theme,
152 content,
153 ))
154 .semantic_in(
155 cx,
156 NodeSpec::new(ident.semantic_id(), Role::Status).text(self.message.clone()),
157 )
158 .into_any_element(),
159 None => frame.child(content).into_any_element(),
160 }
161 }
162}