1#![cfg_attr(target_family = "wasm", no_main)]
2
3//! **view_example** — an end-to-end GPUI example demonstrating how Entity,
4//! Element, View, and Render compose together to build rich text components.
5//!
6//! ## Architecture
7//!
8//! Each module has a focused job:
9//!
10//! | Module | Layer | Job |
11//! |-----------------|---------|----------------------------------------------------------|
12//! | `editor` | Entity | Owns text, cursor, blink task, `EntityInputHandler` |
13//! | `editor_text` | Element | Shapes text, paints cursor, wires `handle_input` |
14//! | `input` | View | Single-line input — composes `ExampleEditorText` with styling |
15//! | `text_area` | View | Multi-line text area — same entity, different layout |
16//! | `main` (here) | Render | Root view — creates entities with `use_state`, assembles |
17//!
18//! ## Running
19//!
20//! ```sh
21//! cargo run --example view_example -p gpui
22//! ```
23//!
24//! ## Testing
25//!
26//! ```sh
27//! cargo test --example view_example -p gpui
28//! ```
29
30mod example_editor;
31mod example_input;
32mod example_text_area;
33
34#[cfg(test)]
35mod example_tests;
36
37use gpui::{
38 App, Bounds, Context, Hsla, KeyBinding, Window, WindowBounds, WindowOptions, actions, div,
39 hsla, prelude::*, px, rgb, size,
40};
41use gpui_platform::application;
42
43use example_editor::ExampleEditor;
44use example_input::{ExampleInput, ExampleInputState};
45use example_text_area::ExampleTextArea;
46
47actions!(
48 view_example,
49 [Backspace, Delete, Left, Right, Home, End, Enter, Quit,]
50);
51
52// ---------------------------------------------------------------------------
53// ViewExample — the root view using `Render` and `window.use_state()`
54// ---------------------------------------------------------------------------
55
56struct ViewExample {
57 input_color: Hsla,
58 textarea_color: Hsla,
59}
60
61impl ViewExample {
62 fn new() -> Self {
63 Self {
64 input_color: hsla(0., 0., 0.1, 1.),
65 textarea_color: hsla(250. / 360., 0.7, 0.4, 1.),
66 }
67 }
68}
69
70impl Render for ViewExample {
71 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
72 let input_state = window.use_state(cx, |window, cx| ExampleInputState::new(window, cx));
73 let textarea_editor = window.use_state(cx, |_window, cx| ExampleEditor::new(cx));
74 let input_color = self.input_color;
75 let textarea_color = self.textarea_color;
76
77 div()
78 .flex()
79 .flex_col()
80 .size_full()
81 .bg(rgb(0xf0f0f0))
82 .p(px(24.))
83 .gap(px(20.))
84 .child(
85 div()
86 .flex()
87 .flex_col()
88 .gap(px(4.))
89 .child(
90 div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child(
91 "Single-line input (Input — View with own state + cached editor)",
92 ),
93 )
94 .child(
95 ExampleInput::new(input_state)
96 .width(px(320.))
97 .color(input_color),
98 ),
99 )
100 .child(
101 div()
102 .flex()
103 .flex_col()
104 .gap(px(4.))
105 .child(div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child(
106 "Multi-line text area (TextArea — same entity type, different View)",
107 ))
108 .child(ExampleTextArea::new(textarea_editor, 5).color(textarea_color)),
109 )
110 .child(
111 div()
112 .flex()
113 .flex_col()
114 .gap(px(2.))
115 .mt(px(12.))
116 .text_xs()
117 .text_color(hsla(0., 0., 0.5, 1.))
118 .child("• ExampleEditor entity owns text, cursor, blink (EntityView)")
119 .child("• ExampleInput is a View with its own state — caches independently")
120 .child(
121 "• ExampleTextArea is a ComponentView — stateless wrapper, editor caches",
122 )
123 .child("• Press Enter in input to flash border (only chrome re-renders)")
124 .child("• Entities created via window.use_state()"),
125 )
126 }
127}
128
129// ---------------------------------------------------------------------------
130// Entry point
131// ---------------------------------------------------------------------------
132
133fn run_example() {
134 application().run(|cx: &mut App| {
135 let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx);
136 cx.bind_keys([
137 KeyBinding::new("backspace", Backspace, None),
138 KeyBinding::new("delete", Delete, None),
139 KeyBinding::new("left", Left, None),
140 KeyBinding::new("right", Right, None),
141 KeyBinding::new("home", Home, None),
142 KeyBinding::new("end", End, None),
143 KeyBinding::new("enter", Enter, None),
144 KeyBinding::new("cmd-q", Quit, None),
145 ]);
146
147 cx.open_window(
148 WindowOptions {
149 window_bounds: Some(WindowBounds::Windowed(bounds)),
150 ..Default::default()
151 },
152 |_, cx| cx.new(|_| ViewExample::new()),
153 )
154 .unwrap();
155
156 cx.on_action(|_: &Quit, cx| cx.quit());
157 cx.activate(true);
158 });
159}
160
161#[cfg(not(target_family = "wasm"))]
162fn main() {
163 run_example();
164}
165
166#[cfg(target_family = "wasm")]
167#[wasm_bindgen::prelude::wasm_bindgen(start)]
168pub fn start() {
169 gpui_platform::web_init();
170 run_example();
171}