gpui_component/speech/
button.rs1use gpui::{App, ElementId, Entity, IntoElement, RenderOnce, StyleRefinement, Styled, Window, div};
2use rust_i18n::t;
3
4use crate::{
5 Disableable, IconName, Selectable as _, Sizable, Size, StyledExt as _,
6 button::{Button, ButtonVariants as _},
7};
8
9use super::{SpeechState, SpeechStatus};
10
11#[derive(IntoElement)]
21pub struct SpeechButton {
22 id: ElementId,
23 state: Entity<SpeechState>,
24 size: Size,
25 disabled: bool,
26 show_when_unsupported: bool,
27 style: StyleRefinement,
28}
29
30impl SpeechButton {
31 pub fn new(state: &Entity<SpeechState>) -> Self {
33 Self {
34 id: ("speech-button", state.entity_id()).into(),
35 state: state.clone(),
36 size: Size::default(),
37 disabled: false,
38 show_when_unsupported: false,
39 style: StyleRefinement::default(),
40 }
41 }
42
43 pub fn show_when_unsupported(mut self, show: bool) -> Self {
46 self.show_when_unsupported = show;
47 self
48 }
49}
50
51impl Sizable for SpeechButton {
52 fn with_size(mut self, size: impl Into<Size>) -> Self {
53 self.size = size.into();
54 self
55 }
56}
57
58impl Styled for SpeechButton {
59 fn style(&mut self) -> &mut StyleRefinement {
62 &mut self.style
63 }
64}
65
66impl Disableable for SpeechButton {
67 fn disabled(mut self, disabled: bool) -> Self {
68 self.disabled = disabled;
69 self
70 }
71}
72
73impl RenderOnce for SpeechButton {
74 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
75 let state = self.state.read(cx);
76 let status = state.status();
77 let supported = state.has_recognizer();
78 if !supported && !self.show_when_unsupported {
79 return div().into_any_element();
80 }
81 let available = status.is_active() || state.is_available(cx);
84 let capturing = status.is_capturing();
85 let label = if !available {
86 t!("Speech.Unavailable")
87 } else if capturing {
88 t!("Speech.Stop")
89 } else {
90 t!("Speech.Start")
91 };
92
93 Button::new(self.id)
94 .ghost()
95 .with_size(self.size)
96 .icon(if capturing {
97 IconName::Square
98 } else {
99 IconName::Mic
100 })
101 .selected(capturing)
102 .loading(status == SpeechStatus::Stopping)
103 .disabled(self.disabled || !available)
104 .tooltip(label.clone())
105 .accessibility_label(label)
106 .refine_style(&self.style)
107 .on_click({
108 let state = self.state.clone();
109 move |_, _, cx| state.update(cx, |state, cx| state.toggle(cx))
110 })
111 .into_any_element()
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use gpui::{AppContext as _, TestAppContext};
118
119 use super::*;
120
121 #[gpui::test]
122 fn test_speech_button_builder(cx: &mut TestAppContext) {
123 let state = cx.update(|cx| cx.new(|cx| SpeechState::new(cx).system_fallback(false)));
124 let button = SpeechButton::new(&state)
125 .small()
126 .disabled(true)
127 .show_when_unsupported(true);
128
129 assert_eq!(button.size, Size::Small);
130 assert!(button.disabled);
131 assert!(button.show_when_unsupported);
132 assert_eq!(
133 button.id,
134 ElementId::from(("speech-button", state.entity_id()))
135 );
136 }
137}