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;
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_editor = window.use_state(cx, |_window, cx| ExampleEditor::new(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()
91 .text_sm()
92 .text_color(hsla(0., 0., 0.3, 1.))
93 .child("Single-line input (Input — View with cached ExampleEditorText)"),
94 )
95 .child(
96 ExampleInput::new(input_editor)
97 .width(px(320.))
98 .color(input_color),
99 ),
100 )
101 .child(
102 div()
103 .flex()
104 .flex_col()
105 .gap(px(4.))
106 .child(div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child(
107 "Multi-line text area (TextArea — same entity type, different View)",
108 ))
109 .child(ExampleTextArea::new(textarea_editor, 5).color(textarea_color)),
110 )
111 .child(
112 div()
113 .flex()
114 .flex_col()
115 .gap(px(2.))
116 .mt(px(12.))
117 .text_xs()
118 .text_color(hsla(0., 0., 0.5, 1.))
119 .child("• ExampleEditor entity owns state, blink task, EntityInputHandler")
120 .child("• ExampleEditorText element shapes text, paints cursor, wires handle_input")
121 .child("• Input / TextArea views compose ExampleEditorText with container styling")
122 .child("• ViewElement::cached() enables render caching via #[derive(Hash)]")
123 .child("• Entities created via window.use_state()"),
124 )
125 }
126}
127
128// ---------------------------------------------------------------------------
129// Entry point
130// ---------------------------------------------------------------------------
131
132fn run_example() {
133 application().run(|cx: &mut App| {
134 let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx);
135 cx.bind_keys([
136 KeyBinding::new("backspace", Backspace, None),
137 KeyBinding::new("delete", Delete, None),
138 KeyBinding::new("left", Left, None),
139 KeyBinding::new("right", Right, None),
140 KeyBinding::new("home", Home, None),
141 KeyBinding::new("end", End, None),
142 KeyBinding::new("enter", Enter, None),
143 KeyBinding::new("cmd-q", Quit, None),
144 ]);
145
146 cx.open_window(
147 WindowOptions {
148 window_bounds: Some(WindowBounds::Windowed(bounds)),
149 ..Default::default()
150 },
151 |_, cx| cx.new(|_| ViewExample::new()),
152 )
153 .unwrap();
154
155 cx.on_action(|_: &Quit, cx| cx.quit());
156 cx.activate(true);
157 });
158}
159
160#[cfg(not(target_family = "wasm"))]
161fn main() {
162 run_example();
163}
164
165#[cfg(target_family = "wasm")]
166#[wasm_bindgen::prelude::wasm_bindgen(start)]
167pub fn start() {
168 gpui_platform::web_init();
169 run_example();
170}