1//! The `ExampleTextArea` view — a multi-line text area component.
2//!
3//! Same `ExampleEditor` entity, different presentation: taller box with configurable
4//! row count. Demonstrates that the same entity type can back different `View`
5//! components with different props and layouts.
6
7use gpui::{
8 App, BoxShadow, CursorStyle, Entity, Hsla, IntoViewElement, StyleRefinement, ViewElement,
9 Window, div, hsla, point, prelude::*, px, white,
10};
11
12use crate::example_editor::ExampleEditor;
13use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
14
15#[derive(Hash, IntoViewElement)]
16pub struct ExampleTextArea {
17 editor: Entity<ExampleEditor>,
18 rows: usize,
19 color: Option<Hsla>,
20}
21
22impl ExampleTextArea {
23 pub fn new(editor: Entity<ExampleEditor>, rows: usize) -> Self {
24 Self {
25 editor,
26 rows,
27 color: None,
28 }
29 }
30
31 pub fn color(mut self, color: Hsla) -> Self {
32 self.color = Some(color);
33 self
34 }
35}
36
37impl gpui::View for ExampleTextArea {
38 type Entity = ExampleEditor;
39
40 fn entity(&self) -> Option<Entity<ExampleEditor>> {
41 Some(self.editor.clone())
42 }
43
44 fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
45 let row_height = px(20.);
46 let box_height = row_height * self.rows as f32 + px(16.);
47 let mut style = StyleRefinement::default();
48 style.size.width = Some(px(400.).into());
49 style.size.height = Some(box_height.into());
50 Some(style)
51 }
52
53 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
54 let focus_handle = self.editor.read(cx).focus_handle.clone();
55 let is_focused = focus_handle.is_focused(window);
56 let text_color = self.color.unwrap_or(hsla(0., 0., 0.1, 1.));
57 let row_height = px(20.);
58 let box_height = row_height * self.rows as f32 + px(16.);
59 let editor = self.editor;
60
61 div()
62 .id("text-area")
63 .key_context("TextInput")
64 .track_focus(&focus_handle)
65 .cursor(CursorStyle::IBeam)
66 .on_action({
67 let editor = editor.clone();
68 move |action: &Backspace, _window, cx| {
69 editor.update(cx, |state, cx| state.backspace(action, _window, cx));
70 }
71 })
72 .on_action({
73 let editor = editor.clone();
74 move |action: &Delete, _window, cx| {
75 editor.update(cx, |state, cx| state.delete(action, _window, cx));
76 }
77 })
78 .on_action({
79 let editor = editor.clone();
80 move |action: &Left, _window, cx| {
81 editor.update(cx, |state, cx| state.left(action, _window, cx));
82 }
83 })
84 .on_action({
85 let editor = editor.clone();
86 move |action: &Right, _window, cx| {
87 editor.update(cx, |state, cx| state.right(action, _window, cx));
88 }
89 })
90 .on_action({
91 let editor = editor.clone();
92 move |action: &Home, _window, cx| {
93 editor.update(cx, |state, cx| state.home(action, _window, cx));
94 }
95 })
96 .on_action({
97 let editor = editor.clone();
98 move |action: &End, _window, cx| {
99 editor.update(cx, |state, cx| state.end(action, _window, cx));
100 }
101 })
102 .on_action({
103 let editor = editor.clone();
104 move |_: &Enter, _window, cx| {
105 editor.update(cx, |state, cx| state.insert_newline(cx));
106 }
107 })
108 .w(px(400.))
109 .h(box_height)
110 .p(px(8.))
111 .bg(white())
112 .border_1()
113 .border_color(if is_focused {
114 hsla(220. / 360., 0.8, 0.5, 1.)
115 } else {
116 hsla(0., 0., 0.75, 1.)
117 })
118 .when(is_focused, |this| {
119 this.shadow(vec![BoxShadow {
120 color: hsla(220. / 360., 0.8, 0.5, 0.3),
121 offset: point(px(0.), px(0.)),
122 blur_radius: px(4.),
123 spread_radius: px(1.),
124 }])
125 })
126 .rounded(px(4.))
127 .overflow_hidden()
128 .line_height(row_height)
129 .text_size(px(14.))
130 .text_color(text_color)
131 .child(ViewElement::new(editor))
132 }
133}