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 | Trait | Job |
11//! |-----------------|---------------|--------------------------------------------------------|
12//! | `editor` | EntityView | Owns text, cursor, blink; renders via ExampleEditorText |
13//! | `input` | View | Single-line input with own state + cached editor child |
14//! | `editor_info` | View | Read-only stats display; zero-wiring, same editor entity |
15//! | `text_area` | ComponentView | Stateless multi-line wrapper; inner editor caches |
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_editor_info;
32mod example_input;
33mod example_render_log;
34mod example_text_area;
35
36#[cfg(test)]
37mod example_tests;
38
39use gpui::{
40 App, Bounds, Context, Hsla, KeyBinding, Window, WindowBounds, WindowOptions, actions, div,
41 hsla, prelude::*, px, rgb, size,
42};
43use gpui_platform::application;
44
45use example_editor::ExampleEditor;
46use example_editor_info::EditorInfo;
47use example_input::{ExampleInput, ExampleInputState};
48use example_render_log::{RenderLog, RenderLogPanel};
49use example_text_area::ExampleTextArea;
50
51actions!(
52 view_example,
53 [Backspace, Delete, Left, Right, Home, End, Enter, Quit,]
54);
55
56// ---------------------------------------------------------------------------
57// ViewExample — the root view using `Render` and `window.use_state()`
58// ---------------------------------------------------------------------------
59
60struct ViewExample {
61 input_color: Hsla,
62 textarea_color: Hsla,
63}
64
65impl ViewExample {
66 fn new() -> Self {
67 Self {
68 input_color: hsla(0., 0., 0.1, 1.),
69 textarea_color: hsla(250. / 360., 0.7, 0.4, 1.),
70 }
71 }
72}
73
74impl Render for ViewExample {
75 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
76 let render_log = window.use_state(cx, |_window, cx| RenderLog::new(cx));
77 render_log.update(cx, |log, _cx| log.begin_frame());
78 let input_state = window.use_state(cx, {
79 let render_log = render_log.clone();
80 move |window, cx| ExampleInputState::new(render_log, window, cx)
81 });
82 let input_editor = input_state.read(cx).editor.clone();
83 let textarea_editor = window.use_state(cx, |window, cx| ExampleEditor::new(window, cx));
84 textarea_editor.update(cx, |e, _cx| {
85 if e.render_log.is_none() {
86 e.render_log = Some(render_log.clone());
87 }
88 });
89 let input_color = self.input_color;
90 let textarea_color = self.textarea_color;
91
92 div()
93 .flex()
94 .flex_col()
95 .size_full()
96 .bg(rgb(0xf0f0f0))
97 .p(px(24.))
98 .gap(px(20.))
99 .child(
100 div()
101 .flex()
102 .flex_col()
103 .gap(px(4.))
104 .child(
105 div()
106 .text_sm()
107 .text_color(hsla(0., 0., 0.3, 1.))
108 .child("Single-line input (View with own state + cached editor)"),
109 )
110 .child(
111 ExampleInput::new(input_state, render_log.clone())
112 .width(px(320.))
113 .color(input_color),
114 )
115 .child(EditorInfo::new(input_editor, render_log.clone())),
116 )
117 .child(
118 div()
119 .flex()
120 .flex_col()
121 .gap(px(4.))
122 .child(div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child(
123 "Multi-line text area (TextArea — same entity type, different View)",
124 ))
125 .child(
126 ExampleTextArea::new(textarea_editor, render_log.clone(), 5)
127 .color(textarea_color),
128 ),
129 )
130 .child(RenderLogPanel::new(render_log))
131 }
132}
133
134// ---------------------------------------------------------------------------
135// Entry point
136// ---------------------------------------------------------------------------
137
138fn run_example() {
139 application().run(|cx: &mut App| {
140 let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx);
141 cx.bind_keys([
142 KeyBinding::new("backspace", Backspace, None),
143 KeyBinding::new("delete", Delete, None),
144 KeyBinding::new("left", Left, None),
145 KeyBinding::new("right", Right, None),
146 KeyBinding::new("home", Home, None),
147 KeyBinding::new("end", End, None),
148 KeyBinding::new("enter", Enter, None),
149 KeyBinding::new("cmd-q", Quit, None),
150 ]);
151
152 cx.open_window(
153 WindowOptions {
154 window_bounds: Some(WindowBounds::Windowed(bounds)),
155 ..Default::default()
156 },
157 |_, cx| cx.new(|_| ViewExample::new()),
158 )
159 .unwrap();
160
161 cx.on_action(|_: &Quit, cx| cx.quit());
162 cx.activate(true);
163 });
164}
165
166#[cfg(not(target_family = "wasm"))]
167fn main() {
168 run_example();
169}
170
171#[cfg(target_family = "wasm")]
172#[wasm_bindgen::prelude::wasm_bindgen(start)]
173pub fn start() {
174 gpui_platform::web_init();
175 run_example();
176}