1use std::rc::Rc;
2
3use gpui::{
4 App, DefiniteLength, Entity, IntoElement, RenderOnce, SharedString, StyleRefinement, Styled,
5 Window, prelude::FluentBuilder as _,
6};
7
8use super::{Input, TextareaState};
9use crate::native_menu::NativeMenu;
10use crate::{RoleOverride, StyledExt as _};
11
12#[derive(IntoElement)]
14pub struct Textarea {
15 state: Entity<TextareaState>,
16 style: StyleRefinement,
17 height: Option<DefiniteLength>,
18 appearance: bool,
19 bordered: bool,
20 disabled: bool,
21 readonly: bool,
22 tab_index: isize,
23 role: RoleOverride,
24 accessibility_id: Option<SharedString>,
25 aria_label: Option<SharedString>,
26
27 context_menu_builder: Option<Rc<dyn Fn(NativeMenu, &mut Window, &mut App) -> NativeMenu>>,
31
32 paste_handler: Option<Rc<dyn Fn(&gpui::ClipboardItem, &mut Window, &mut App) -> bool>>,
33}
34
35impl Textarea {
36 pub fn new(state: &Entity<TextareaState>) -> Self {
37 Self {
38 state: state.clone(),
39 style: StyleRefinement::default(),
40 height: None,
41 appearance: true,
42 bordered: true,
43 disabled: false,
44 readonly: false,
45 tab_index: 0,
46 role: RoleOverride::default(),
47 accessibility_id: None,
48 aria_label: None,
49 context_menu_builder: None,
50 paste_handler: None,
51 }
52 }
53
54 pub fn h(mut self, height: impl Into<DefiniteLength>) -> Self {
55 self.height = Some(height.into());
56 self
57 }
58
59 pub fn appearance(mut self, appearance: bool) -> Self {
60 self.appearance = appearance;
61 self
62 }
63
64 pub fn bordered(mut self, bordered: bool) -> Self {
65 self.bordered = bordered;
66 self
67 }
68
69 pub fn disabled(mut self, disabled: bool) -> Self {
70 self.disabled = disabled;
71 self
72 }
73
74 pub fn readonly(mut self, readonly: bool) -> Self {
80 self.readonly = readonly;
81 self
82 }
83
84 pub fn tab_index(mut self, index: isize) -> Self {
85 self.tab_index = index;
86 self
87 }
88
89 pub fn role(mut self, role: impl Into<RoleOverride>) -> Self {
90 self.role = role.into();
91 self
92 }
93
94 pub fn accessibility_id(mut self, id: impl Into<SharedString>) -> Self {
96 self.accessibility_id = Some(id.into());
97 self
98 }
99
100 pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
101 self.aria_label = Some(label.into());
102 self
103 }
104
105 pub fn context_menu(
110 mut self,
111 f: impl Fn(NativeMenu, &mut Window, &mut App) -> NativeMenu + 'static,
112 ) -> Self {
113 self.context_menu_builder = Some(Rc::new(f));
114 self
115 }
116
117 pub fn on_paste(
124 mut self,
125 handler: impl Fn(&gpui::ClipboardItem, &mut Window, &mut App) -> bool + 'static,
126 ) -> Self {
127 self.paste_handler = Some(Rc::new(handler));
128 self
129 }
130}
131
132impl Styled for Textarea {
133 fn style(&mut self) -> &mut StyleRefinement {
134 &mut self.style
135 }
136}
137
138impl Textarea {
139 pub(crate) fn into_input(self) -> Input {
142 Input::from_state(self.state.clone())
143 .appearance(self.appearance)
144 .bordered(self.bordered)
145 .disabled(self.disabled)
146 .readonly(self.readonly)
147 .tab_index(self.tab_index)
148 .role(self.role)
149 .when_some(self.height, |this, height| this.h(height))
150 .when_some(self.accessibility_id, |this, id| this.accessibility_id(id))
151 .when_some(self.aria_label, |this, label| this.aria_label(label))
152 .when_some(self.context_menu_builder, |this, build| {
153 this.context_menu(move |menu, window, cx| build(menu, window, cx))
154 })
155 .when_some(self.paste_handler, |this, handler| {
156 this.on_paste(move |item, window, cx| handler(item, window, cx))
157 })
158 .refine_style(&self.style)
159 }
160}
161
162impl RenderOnce for Textarea {
163 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
164 self.into_input()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[gpui::test]
173 fn test_on_paste_builder(cx: &mut gpui::TestAppContext) {
174 use gpui::{AppContext as _, Render};
175
176 struct Probe;
177 impl Render for Probe {
178 fn render(
179 &mut self,
180 _: &mut Window,
181 _: &mut gpui::Context<Self>,
182 ) -> impl gpui::IntoElement {
183 gpui::div()
184 }
185 }
186
187 cx.update(crate::init);
188 let _ = cx.add_window_view(|window, cx| {
189 let state = cx.new(|cx| TextareaState::new(window, cx));
190 assert!(Textarea::new(&state).paste_handler.is_none());
191 let textarea = Textarea::new(&state).on_paste(|_, _, _| true);
192 assert!(textarea.paste_handler.is_some());
193 Probe
194 });
195 }
196}