gpui_base/input/base/
mod.rs1use crate::{StateStyle, StyledExt 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: 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),
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 styles(mut self, build: impl FnOnce(InputStyles) -> InputStyles) -> Self {
165 self.semantic_styles = build(self.semantic_styles);
166 self
167 }
168
169 pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
170 self.base = self.base.aria_label(label.into());
171 self
172 }
173
174 fn resolved_style(&self) -> StyleRefinement {
175 crate::state_style::resolve_style(
176 &self.style,
177 [
178 self.focused.then_some(&self.semantic_styles.focused),
179 self.disabled.then_some(&self.semantic_styles.disabled),
180 ]
181 .into_iter()
182 .flatten(),
183 )
184 }
185}
186
187#[derive(Default)]
188pub struct InputStyles {
189 focused: StyleRefinement,
190 disabled: StyleRefinement,
191}
192
193impl InputStyles {
194 pub fn focused(mut self, build: impl FnOnce(StateStyle) -> StateStyle) -> Self {
195 self.focused
196 .refine(&build(StateStyle::default()).into_refinement());
197 self
198 }
199
200 pub fn disabled(mut self, build: impl FnOnce(StateStyle) -> StateStyle) -> Self {
201 self.disabled
202 .refine(&build(StateStyle::default()).into_refinement());
203 self
204 }
205}
206
207impl Styled for InputBase {
208 fn style(&mut self) -> &mut StyleRefinement {
209 &mut self.style
210 }
211}
212
213impl ParentElement for InputBase {
214 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
215 self.children.extend(elements);
216 }
217}
218
219impl InteractiveElement for InputBase {
220 fn interactivity(&mut self) -> &mut Interactivity {
221 self.base.interactivity()
222 }
223}
224
225impl StatefulInteractiveElement for InputBase {}
226
227impl RenderOnce for InputBase {
228 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
229 let style = self.resolved_style();
230 self.base
231 .when_some(self.role.resolve(|| Role::TextInput), |this, role| {
232 this.role(role)
233 })
234 .children(self.children)
235 .refine_style(&style)
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn frame_accepts_application_owned_content_and_style() {
245 let _ = InputBase::new("input")
246 .focused(true)
247 .disabled(false)
248 .styles(|styles| {
249 styles
250 .focused(|style| style.border_1())
251 .disabled(|style| style.opacity(0.5))
252 })
253 .child("value")
254 .opacity(0.8);
255 }
256
257 #[test]
258 fn semantic_state_styles_override_the_normal_style() {
259 let focused = InputBase::new("focused")
260 .focused(true)
261 .border_color(gpui::red())
262 .styles(|styles| styles.focused(|style| style.border_color(gpui::blue())));
263 assert_eq!(focused.resolved_style().border_color, Some(gpui::blue()));
264
265 let disabled = InputBase::new("disabled")
266 .focused(true)
267 .disabled(true)
268 .opacity(1.)
269 .styles(|styles| {
270 styles
271 .focused(|style| style.opacity(0.8))
272 .disabled(|style| style.opacity(0.5))
273 });
274 assert_eq!(disabled.resolved_style().opacity, Some(0.5));
275 }
276}