Skip to main content

gpui_component/input/
textarea.rs

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/// A styled ordinary multi-line text field.
13#[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    aria_label: Option<SharedString>,
25
26    /// An optional context menu builder to allow a custom context menu.
27    ///
28    /// If set, this overrides the built-in context menu.
29    context_menu_builder: Option<Rc<dyn Fn(NativeMenu, &mut Window, &mut App) -> NativeMenu>>,
30}
31
32impl Textarea {
33    pub fn new(state: &Entity<TextareaState>) -> Self {
34        Self {
35            state: state.clone(),
36            style: StyleRefinement::default(),
37            height: None,
38            appearance: true,
39            bordered: true,
40            disabled: false,
41            readonly: false,
42            tab_index: 0,
43            role: RoleOverride::default(),
44            aria_label: None,
45            context_menu_builder: None,
46        }
47    }
48
49    pub fn h(mut self, height: impl Into<DefiniteLength>) -> Self {
50        self.height = Some(height.into());
51        self
52    }
53
54    pub fn appearance(mut self, appearance: bool) -> Self {
55        self.appearance = appearance;
56        self
57    }
58
59    pub fn bordered(mut self, bordered: bool) -> Self {
60        self.bordered = bordered;
61        self
62    }
63
64    pub fn disabled(mut self, disabled: bool) -> Self {
65        self.disabled = disabled;
66        self
67    }
68
69    /// Set the textarea to read-only, default is `false`.
70    ///
71    /// Unlike [`Self::disabled`], a read-only textarea keeps the normal appearance
72    /// and still can be focused, selected and copied, it only rejects the changes
73    /// made by the user.
74    pub fn readonly(mut self, readonly: bool) -> Self {
75        self.readonly = readonly;
76        self
77    }
78
79    pub fn tab_index(mut self, index: isize) -> Self {
80        self.tab_index = index;
81        self
82    }
83
84    pub fn role(mut self, role: impl Into<RoleOverride>) -> Self {
85        self.role = role.into();
86        self
87    }
88
89    pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
90        self.aria_label = Some(label.into());
91        self
92    }
93
94    /// Replace the built-in context menu shown on right-click.
95    ///
96    /// The closure receives an empty menu and returns the one to show, so it
97    /// decides entirely what appears — the default items are not added.
98    pub fn context_menu(
99        mut self,
100        f: impl Fn(NativeMenu, &mut Window, &mut App) -> NativeMenu + 'static,
101    ) -> Self {
102        self.context_menu_builder = Some(Rc::new(f));
103        self
104    }
105}
106
107impl Styled for Textarea {
108    fn style(&mut self) -> &mut StyleRefinement {
109        &mut self.style
110    }
111}
112
113impl RenderOnce for Textarea {
114    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
115        Input::from_state(self.state.clone())
116            .appearance(self.appearance)
117            .bordered(self.bordered)
118            .disabled(self.disabled)
119            .readonly(self.readonly)
120            .tab_index(self.tab_index)
121            .role(self.role)
122            .when_some(self.height, |this, height| this.h(height))
123            .when_some(self.aria_label, |this, label| this.aria_label(label))
124            .when_some(self.context_menu_builder, |this, build| {
125                this.context_menu(move |menu, window, cx| build(menu, window, cx))
126            })
127            .refine_style(&self.style)
128    }
129}