1pub use component_shape::{
13 ComponentCapabilities, ComponentPrototyping, ComponentShapeFor, ComponentShapeMetadata,
14 ComponentSuffix, DeclaredComponentShape, McpInput, McpInputShape, McpPrimitiveKind,
15 McpRangeBoundKind, RenderCapability, ValueBindingCapability, ValueChange,
16 component_suffix_from_suffix, is_valid_component_suffix, validate_component_suffix,
17};
18pub use component_shape_gpui_macros::{GpuiComponentShape, component_shape};
19
20pub trait GpuiComponentRender<State: 'static>: 'static {
22 const RENDERS: bool;
24
25 fn new(entity: &gpui_kit::Entity<State>) -> impl gpui_kit::IntoElement;
27}
28
29#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
31pub struct NoGpuiRenderComponent;
32
33impl<State: 'static> GpuiComponentRender<State> for NoGpuiRenderComponent {
34 const RENDERS: bool = false;
35
36 fn new(_entity: &gpui_kit::Entity<State>) -> impl gpui_kit::IntoElement {
37 gpui_kit::div()
38 }
39}
40
41pub trait GpuiComponentShape: ComponentShapeMetadata {
43 type State: 'static;
45
46 type RenderComponent: GpuiComponentRender<Self::State>;
48
49 fn new(
51 window: &mut gpui_kit::Window,
52 cx: &mut gpui_kit::Context<'_, Self::State>,
53 ) -> Self::State;
54}
55
56pub trait GpuiComponentShapeBuilder<Shape: GpuiComponentShape> {
63 fn build(
65 self,
66 window: &mut gpui_kit::Window,
67 cx: &mut gpui_kit::Context<'_, Shape::State>,
68 ) -> Shape::State;
69}
70
71#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
73pub struct DefaultGpuiComponentShapeBuilder<Shape>(core::marker::PhantomData<fn() -> Shape>);
74
75impl<Shape> DefaultGpuiComponentShapeBuilder<Shape> {
76 pub const fn new() -> Self {
78 Self(core::marker::PhantomData)
79 }
80}
81
82impl<Shape> GpuiComponentShapeBuilder<Shape> for DefaultGpuiComponentShapeBuilder<Shape>
83where
84 Shape: GpuiComponentShape,
85{
86 fn build(
87 self,
88 window: &mut gpui_kit::Window,
89 cx: &mut gpui_kit::Context<'_, Shape::State>,
90 ) -> Shape::State {
91 Shape::new(window, cx)
92 }
93}
94
95#[diagnostic::on_unimplemented(
97 message = "GPUI component shape `{Self}` must be declared with `component_shape_gpui::component_shape!` or `#[derive(component_shape_gpui::GpuiComponentShape)]`",
98 note = "hand-written `GpuiComponentShape` implementations are not accepted by consumers that require declared shapes"
99)]
100pub trait DeclaredGpuiComponentShape: GpuiComponentShape + DeclaredComponentShape {}
101
102#[diagnostic::on_unimplemented(
108 message = "GPUI component shape `{Self}` is not compatible with value `{Value}`",
109 note = "declare `value = {Value}`, include `{Value}` in `values(...)`, or publish value compatibility through a value-binding impl"
110)]
111pub trait GpuiComponentShapeFor<Value>: GpuiComponentShape + ComponentShapeFor<Value> {}
112
113#[diagnostic::on_unimplemented(
115 message = "GPUI component shape `{Self}` does not implement value binding for `{Value}`",
116 note = "add a `GpuiComponentValueBinding<T>` impl inside `component_shape!`, or derive with `value_binding` and a matching state binding"
117)]
118pub trait GpuiComponentValueBinding<Value>: GpuiComponentShape
119where
120 Self::State: gpui_kit::EventEmitter<Self::Event>,
121{
122 type Event: 'static;
124
125 fn seed_value_binding_state(
127 _state: &mut Self::State,
128 _value: Option<&Value>,
129 _window: &mut gpui_kit::Window,
130 _cx: &mut gpui_kit::Context<'_, Self::State>,
131 ) {
132 }
133
134 fn value_change(state: &Self::State, event: &Self::Event) -> ValueChange<Value>;
136}
137
138#[diagnostic::on_unimplemented(
140 message = "GPUI component state `{Self}` does not implement value binding for `{Value}`",
141 note = "implement `GpuiComponentStateValueBinding<T>` for the backing state"
142)]
143pub trait GpuiComponentStateValueBinding<Value>: gpui_kit::EventEmitter<Self::Event> {
144 type Event: 'static;
146
147 fn seed_value_binding_state(
149 _state: &mut Self,
150 _value: Option<&Value>,
151 _window: &mut gpui_kit::Window,
152 _cx: &mut gpui_kit::Context<'_, Self>,
153 ) where
154 Self: Sized,
155 {
156 }
157
158 fn value_change(state: &Self, event: &Self::Event) -> ValueChange<Value>;
160}
161
162pub type GpuiComponentStateOf<Shape> = <Shape as GpuiComponentShape>::State;
164
165pub type GpuiComponentEventOf<Shape, Value> = <Shape as GpuiComponentValueBinding<Value>>::Event;
167
168pub fn build_component_shape<Shape, Builder>(
170 builder: Builder,
171 window: &mut gpui_kit::Window,
172 cx: &mut gpui_kit::Context<'_, GpuiComponentStateOf<Shape>>,
173) -> GpuiComponentStateOf<Shape>
174where
175 Shape: GpuiComponentShape,
176 Builder: GpuiComponentShapeBuilder<Shape>,
177{
178 builder.build(window, cx)
179}
180
181pub fn seed_value_binding_state<Shape, Value>(
184 state: &mut GpuiComponentStateOf<Shape>,
185 value: Option<&Value>,
186 window: &mut gpui_kit::Window,
187 cx: &mut gpui_kit::Context<'_, GpuiComponentStateOf<Shape>>,
188) where
189 Shape: GpuiComponentValueBinding<Value>,
190 GpuiComponentStateOf<Shape>: gpui_kit::EventEmitter<GpuiComponentEventOf<Shape, Value>>,
191{
192 Shape::seed_value_binding_state(state, value, window, cx);
193}
194
195pub fn value_change<Shape, Value>(
198 state: &GpuiComponentStateOf<Shape>,
199 event: &GpuiComponentEventOf<Shape, Value>,
200) -> ValueChange<Value>
201where
202 Shape: GpuiComponentValueBinding<Value>,
203 GpuiComponentStateOf<Shape>: gpui_kit::EventEmitter<GpuiComponentEventOf<Shape, Value>>,
204{
205 Shape::value_change(state, event)
206}
207
208#[cfg(test)]
209mod tests {
210 use super::{
211 ComponentShapeMetadata, DefaultGpuiComponentShapeBuilder, GpuiComponentRender,
212 GpuiComponentShape, GpuiComponentShapeBuilder, GpuiComponentStateValueBinding,
213 GpuiComponentValueBinding, NoGpuiRenderComponent, ValueChange, build_component_shape,
214 seed_value_binding_state, value_change,
215 };
216
217 #[derive(Debug, Default, Eq, PartialEq)]
218 struct TestState {
219 value: Option<u32>,
220 }
221
222 impl gpui_kit::Render for TestState {
223 fn render(
224 &mut self,
225 _window: &mut gpui_kit::Window,
226 _cx: &mut gpui_kit::Context<'_, Self>,
227 ) -> impl gpui_kit::IntoElement {
228 gpui_kit::div()
229 }
230 }
231
232 struct TestEvent(Option<u32>);
233
234 impl gpui_kit::EventEmitter<TestEvent> for TestState {}
235
236 struct TestShape;
237
238 impl ComponentShapeMetadata for TestShape {}
239
240 impl GpuiComponentShape for TestShape {
241 type State = TestState;
242 type RenderComponent = NoGpuiRenderComponent;
243
244 fn new(
245 _window: &mut gpui_kit::Window,
246 _cx: &mut gpui_kit::Context<'_, Self::State>,
247 ) -> Self::State {
248 TestState { value: Some(1) }
249 }
250 }
251
252 impl GpuiComponentValueBinding<u32> for TestShape {
253 type Event = TestEvent;
254
255 fn value_change(_state: &Self::State, event: &Self::Event) -> ValueChange<u32> {
256 match event.0 {
257 Some(value) => ValueChange::Set(value),
258 None => ValueChange::Clear,
259 }
260 }
261 }
262
263 impl GpuiComponentStateValueBinding<u32> for TestState {
264 type Event = TestEvent;
265
266 fn value_change(_state: &Self, event: &Self::Event) -> ValueChange<u32> {
267 match event.0 {
268 Some(value) => ValueChange::Set(value),
269 None => ValueChange::Clear,
270 }
271 }
272 }
273
274 struct ConfiguredBuilder(u32);
275
276 impl GpuiComponentShapeBuilder<TestShape> for ConfiguredBuilder {
277 fn build(
278 self,
279 _window: &mut gpui_kit::Window,
280 _cx: &mut gpui_kit::Context<'_, TestState>,
281 ) -> TestState {
282 TestState {
283 value: Some(self.0),
284 }
285 }
286 }
287
288 #[test]
289 fn marker_and_default_builder_metadata_are_stable() {
290 assert_eq!(
291 DefaultGpuiComponentShapeBuilder::<()>::new(),
292 DefaultGpuiComponentShapeBuilder::default()
293 );
294 const {
295 assert!(!<NoGpuiRenderComponent as GpuiComponentRender<()>>::RENDERS);
296 }
297 }
298
299 #[test]
300 fn runtime_helpers_dispatch_through_shape_contracts() {
301 let mut app = gpui_kit::TestApp::new();
302 let mut window = app.open_window(|window, cx| {
303 build_component_shape::<TestShape, _>(
304 DefaultGpuiComponentShapeBuilder::new(),
305 window,
306 cx,
307 )
308 });
309
310 assert_eq!(window.read(|state, _| state.value), Some(1));
311 let root = window.root();
312 let _render = NoGpuiRenderComponent::new(&root);
313
314 window.update(|state, window, cx| {
315 seed_value_binding_state::<TestShape, u32>(state, Some(&7), window, cx);
316 assert_eq!(state.value, Some(1), "the default seed hook is a no-op");
317 assert_eq!(
318 value_change::<TestShape, u32>(state, &TestEvent(Some(9))),
319 ValueChange::Set(9)
320 );
321 assert_eq!(
322 <TestState as GpuiComponentStateValueBinding<u32>>::value_change(
323 state,
324 &TestEvent(None),
325 ),
326 ValueChange::Clear
327 );
328 <TestState as GpuiComponentStateValueBinding<u32>>::seed_value_binding_state(
329 state,
330 Some(&11),
331 window,
332 cx,
333 );
334 assert_eq!(
335 state.value,
336 Some(1),
337 "the state default seed hook is a no-op"
338 );
339
340 let configured =
341 build_component_shape::<TestShape, _>(ConfiguredBuilder(42), window, cx);
342 assert_eq!(configured.value, Some(42));
343 });
344 }
345}