gpui_base/input/base/
mod.rs1use crate::{StateStyle, StyledExt as _, TestSupportExt as _};
2use gpui::{
3 AnyElement, App, Div, ElementId, InteractiveElement, Interactivity, IntoElement, ParentElement,
4 Refineable as _, RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement,
5 Styled, Window, div, prelude::FluentBuilder as _,
6};
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub struct InputContextMenuCapabilities {
26 disabled: bool,
27 readonly: bool,
28 code_editor: bool,
29 selection: bool,
30 masked: bool,
31 go_to_definition: bool,
32 code_actions: bool,
33}
34
35impl InputContextMenuCapabilities {
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn disabled(mut self, disabled: bool) -> Self {
41 self.disabled = disabled;
42 self
43 }
44
45 pub fn readonly(mut self, readonly: bool) -> Self {
46 self.readonly = readonly;
47 self
48 }
49
50 pub fn code_editor(mut self, code_editor: bool) -> Self {
51 self.code_editor = code_editor;
52 self
53 }
54
55 pub fn selection(mut self, selection: bool) -> Self {
57 self.selection = selection;
58 self
59 }
60
61 pub fn masked(mut self, masked: bool) -> Self {
63 self.masked = masked;
64 self
65 }
66
67 pub fn go_to_definition(mut self, go_to_definition: bool) -> Self {
68 self.go_to_definition = go_to_definition;
69 self
70 }
71
72 pub fn code_actions(mut self, code_actions: bool) -> Self {
73 self.code_actions = code_actions;
74 self
75 }
76
77 pub fn is_disabled(&self) -> bool {
78 self.disabled
79 }
80
81 pub fn is_readonly(&self) -> bool {
82 self.readonly
83 }
84
85 pub fn is_editable(&self) -> bool {
90 !self.disabled && !self.readonly
91 }
92
93 pub fn is_code_editor(&self) -> bool {
94 self.code_editor
95 }
96
97 pub fn has_selection(&self) -> bool {
98 self.selection
99 }
100
101 pub fn is_masked(&self) -> bool {
102 self.masked
103 }
104
105 pub fn is_copyable(&self) -> bool {
110 self.selection && !self.masked
111 }
112
113 pub fn has_definition(&self) -> bool {
114 self.go_to_definition
115 }
116
117 pub fn has_code_actions(&self) -> bool {
118 self.code_actions
119 }
120}
121
122#[derive(IntoElement)]
127pub struct InputBase {
128 base: crate::ObservedElement<gpui::Stateful<Div>>,
129 style: StyleRefinement,
130 semantic_styles: InputStyles,
131 children: Vec<AnyElement>,
132 focused: bool,
133 disabled: bool,
134 role: crate::RoleOverride,
135}
136
137impl InputBase {
138 pub fn new(id: impl Into<ElementId>) -> Self {
139 Self {
140 base: div().id(id).test_support(),
141 style: StyleRefinement::default(),
142 semantic_styles: InputStyles::default(),
143 children: Vec::new(),
144 focused: false,
145 disabled: false,
146 role: crate::RoleOverride::Implicit,
147 }
148 }
149 pub fn role(mut self, role: impl Into<crate::RoleOverride>) -> Self {
150 self.role = role.into();
151 self
152 }
153
154 pub fn focused(mut self, focused: bool) -> Self {
155 self.focused = focused;
156 self
157 }
158
159 pub fn disabled(mut self, disabled: bool) -> Self {
160 self.disabled = disabled;
161 self
162 }
163
164 pub fn semantic_owner(self, editor: gpui::EntityId) -> Self {
178 #[cfg(feature = "test-support")]
179 {
180 Self {
181 base: self.base.semantic_owner([
182 ElementId::View(editor),
183 super::state::EDITOR_ELEMENT_ID.into(),
184 ]),
185 ..self
186 }
187 }
188 #[cfg(not(feature = "test-support"))]
189 {
190 let _ = editor;
191 self
192 }
193 }
194
195 pub fn styles(mut self, build: impl FnOnce(InputStyles) -> InputStyles) -> Self {
196 self.semantic_styles = build(self.semantic_styles);
197 self
198 }
199
200 pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
201 self.base = self.base.aria_label(label.into());
202 self
203 }
204
205 fn resolved_style(&self) -> StyleRefinement {
206 crate::state_style::resolve_style(
207 &self.style,
208 [
209 self.focused.then_some(&self.semantic_styles.focused),
210 self.disabled.then_some(&self.semantic_styles.disabled),
211 ]
212 .into_iter()
213 .flatten(),
214 )
215 }
216}
217
218#[derive(Default)]
219pub struct InputStyles {
220 focused: StyleRefinement,
221 disabled: StyleRefinement,
222}
223
224impl InputStyles {
225 pub fn focused(mut self, build: impl FnOnce(StateStyle) -> StateStyle) -> Self {
226 self.focused
227 .refine(&build(StateStyle::default()).into_refinement());
228 self
229 }
230
231 pub fn disabled(mut self, build: impl FnOnce(StateStyle) -> StateStyle) -> Self {
232 self.disabled
233 .refine(&build(StateStyle::default()).into_refinement());
234 self
235 }
236}
237
238impl Styled for InputBase {
239 fn style(&mut self) -> &mut StyleRefinement {
240 &mut self.style
241 }
242}
243
244impl ParentElement for InputBase {
245 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
246 self.children.extend(elements);
247 }
248}
249
250impl InteractiveElement for InputBase {
251 fn track_focus(mut self, focus: &gpui::FocusHandle) -> Self {
252 self.base = self.base.track_focus(focus);
253 self
254 }
255
256 fn interactivity(&mut self) -> &mut Interactivity {
257 self.base.interactivity()
258 }
259}
260
261impl StatefulInteractiveElement for InputBase {}
262
263impl RenderOnce for InputBase {
264 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
265 let style = self.resolved_style();
266 self.base
267 .when_some(self.role.resolve(|| Role::TextInput), |this, role| {
268 this.role(role)
269 })
270 .children(self.children)
271 .refine_style(&style)
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn frame_accepts_application_owned_content_and_style() {
281 let _ = InputBase::new("input")
282 .focused(true)
283 .disabled(false)
284 .styles(|styles| {
285 styles
286 .focused(|style| style.border_1())
287 .disabled(|style| style.opacity(0.5))
288 })
289 .child("value")
290 .opacity(0.8);
291 }
292
293 #[test]
294 fn semantic_state_styles_override_the_normal_style() {
295 let focused = InputBase::new("focused")
296 .focused(true)
297 .border_color(gpui::red())
298 .styles(|styles| styles.focused(|style| style.border_color(gpui::blue())));
299 assert_eq!(focused.resolved_style().border_color, Some(gpui::blue()));
300
301 let disabled = InputBase::new("disabled")
302 .focused(true)
303 .disabled(true)
304 .opacity(1.)
305 .styles(|styles| {
306 styles
307 .focused(|style| style.opacity(0.8))
308 .disabled(|style| style.opacity(0.5))
309 });
310 assert_eq!(disabled.resolved_style().opacity, Some(0.5));
311 }
312}