zi/component/mod.rs
1//! Defines the `Component` trait and related types.
2pub mod bindings;
3pub mod layout;
4pub(crate) mod template;
5
6pub use self::layout::{ComponentExt, Layout};
7
8use std::{
9 any::{self, TypeId},
10 fmt,
11 marker::PhantomData,
12 rc::Rc,
13};
14
15use self::{
16 bindings::{Bindings, NamedBindingQuery},
17 template::{ComponentId, DynamicMessage},
18};
19use crate::{
20 app::{ComponentMessage, MessageSender},
21 terminal::{Key, Rect},
22};
23
24/// Components are the building blocks of the UI in Zi.
25///
26/// The trait describes stateful components and their lifecycle. This is the
27/// main trait that users of the library will implement to describe their UI.
28/// All components are owned directly by an [`App`](../struct.App.html) which
29/// manages their lifecycle. An `App` instance will create new components,
30/// update them in reaction to user input or to messages from other components
31/// and eventually drop them when a component gone off screen.
32///
33/// Anyone familiar with Yew, Elm or React + Redux should be familiar with all
34/// the high-level concepts. Moreover, the names of some types and functions are
35/// the same as in `Yew`.
36///
37/// A component has to describe how:
38/// - how to create a fresh instance from `Component::Properties` received from their parent (`create` fn)
39/// - how to render it (`view` fn)
40/// - how to update its inter
41///
42pub trait Component: Sized + 'static {
43 /// Messages are used to make components dynamic and interactive. For simple
44 /// components, this will be `()`. Complex ones will typically use
45 /// an enum to declare multiple Message types.
46 type Message: Send + 'static;
47
48 /// Properties are the inputs to a Component.
49 type Properties;
50
51 /// Components are created with three pieces of data:
52 /// - their Properties
53 /// - the current position and size on the screen
54 /// - a `ComponentLink` which can be used to send messages and create callbacks for triggering updates
55 ///
56 /// Conceptually, there's an "update" method for each one of these:
57 /// - `change` when the Properties change
58 /// - `resize` when their current position and size on the screen changes
59 /// - `update` when the a message was sent to the component
60 fn create(properties: Self::Properties, frame: Rect, link: ComponentLink<Self>) -> Self;
61
62 /// Returns the current visual layout of the component.
63 fn view(&self) -> Layout;
64
65 /// When the parent of a Component is re-rendered, it will either be re-created or
66 /// receive new properties in the `change` lifecycle method. Component's can choose
67 /// to re-render if the new properties are different than the previously
68 /// received properties.
69 ///
70 /// Root components don't have a parent and subsequently, their `change`
71 /// method will never be called. Components which don't have properties
72 /// should always return false.
73 fn change(&mut self, _properties: Self::Properties) -> ShouldRender {
74 ShouldRender::No
75 }
76
77 /// This method is called when a component's position and size on the screen changes.
78 fn resize(&mut self, _frame: Rect) -> ShouldRender {
79 ShouldRender::No
80 }
81
82 /// Components handle messages in their `update` method and commonly use this method
83 /// to update their state and (optionally) re-render themselves.
84 fn update(&mut self, _message: Self::Message) -> ShouldRender {
85 ShouldRender::No
86 }
87
88 /// Updates the key bindings of the component.
89 ///
90 /// This method will be called after the component lifecycle methods. It is
91 /// used to specify how to react in response to keyboard events, typically
92 /// by sending a message.
93 fn bindings(&self, _bindings: &mut Bindings<Self>) {}
94
95 fn notify_binding_queries(&self, _queries: &[Option<NamedBindingQuery>], _keys: &[Key]) {}
96
97 fn tick(&self) -> Option<Self::Message> {
98 None
99 }
100}
101
102/// Callback wrapper. Useful for passing callbacks in child components
103/// `Properties`. An `Rc` wrapper is used to make it cloneable.
104pub struct Callback<InputT, OutputT = ()>(pub Rc<dyn Fn(InputT) -> OutputT>);
105
106impl<InputT, OutputT> Clone for Callback<InputT, OutputT> {
107 fn clone(&self) -> Self {
108 Self(self.0.clone())
109 }
110}
111
112impl<InputT, OutputT> fmt::Debug for Callback<InputT, OutputT> {
113 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(
115 formatter,
116 "Callback({} -> {} @ {:?})",
117 any::type_name::<InputT>(),
118 any::type_name::<OutputT>(),
119 Rc::as_ptr(&self.0)
120 )
121 }
122}
123
124impl<InputT, OutputT> PartialEq for Callback<InputT, OutputT> {
125 fn eq(&self, other: &Self) -> bool {
126 // `Callback` is a fat pointer: vtable address + data address. We're
127 // only comparing the pointers to the data portion for equality.
128 //
129 // This could fail if some of your objects can have the same address but
130 // different concrete types, for example if one is stored in a field of
131 // another, or if they are different zero-sized types.
132 //
133 // Comparing vtable addresses doesn't work either as "vtable addresses
134 // are not guaranteed to be unique and could vary between different code
135 // generation units. Furthermore vtables for different types could have
136 // the same address after being merged together".
137 //
138 // References
139 // - https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons
140 // - https://users.rust-lang.org/t/rc-dyn-trait-ptr-equality
141 std::ptr::eq(
142 self.0.as_ref() as *const _ as *const (),
143 other.0.as_ref() as *const _ as *const (),
144 )
145 }
146}
147
148impl<InputT, OutputT> Callback<InputT, OutputT> {
149 pub fn emit(&self, value: InputT) -> OutputT {
150 (self.0)(value)
151 }
152}
153
154impl<InputT, OutputT, FnT> From<FnT> for Callback<InputT, OutputT>
155where
156 FnT: Fn(InputT) -> OutputT + 'static,
157{
158 fn from(function: FnT) -> Self {
159 Self(Rc::new(function))
160 }
161}
162
163/// A context for sending messages to a component or the runtime.
164///
165/// It can be used in a multi-threaded environment (implements `Sync` and
166/// `Send`). Additionally, it can send messages to the runtime, in particular
167/// it's used to gracefully stop a running [`App`](struct.App.html).
168#[derive(Debug)]
169pub struct ComponentLink<ComponentT> {
170 sender: Box<dyn MessageSender>,
171 component_id: ComponentId,
172 _component: PhantomData<fn() -> ComponentT>,
173}
174
175impl<ComponentT: Component> ComponentLink<ComponentT> {
176 /// Sends a message to the component.
177 pub fn send(&self, message: ComponentT::Message) {
178 self.sender.send(ComponentMessage(LinkMessage::Component(
179 self.component_id,
180 DynamicMessage(Box::new(message)),
181 )));
182 }
183
184 /// Creates a `Callback` which will send a message to the linked component's
185 /// update method when invoked.
186 pub fn callback<InputT>(
187 &self,
188 callback: impl Fn(InputT) -> ComponentT::Message + 'static,
189 ) -> Callback<InputT> {
190 let link = self.clone();
191 Callback(Rc::new(move |input| link.send(callback(input))))
192 }
193
194 /// Sends a message to the `App` runtime requesting it to stop executing.
195 ///
196 /// This method only sends a message and returns immediately, the app will
197 /// stop asynchronously and may deliver other pending messages before
198 /// exiting.
199 pub fn exit(&self) {
200 self.sender.send(ComponentMessage(LinkMessage::Exit));
201 }
202
203 pub(crate) fn new(sender: Box<dyn MessageSender>, component_id: ComponentId) -> Self {
204 assert_eq!(TypeId::of::<ComponentT>(), component_id.type_id());
205 Self {
206 sender,
207 component_id,
208 _component: PhantomData,
209 }
210 }
211}
212
213impl<ComponentT> Clone for ComponentLink<ComponentT> {
214 fn clone(&self) -> Self {
215 Self {
216 sender: self.sender.clone_box(),
217 component_id: self.component_id,
218 _component: PhantomData,
219 }
220 }
221}
222
223impl<ComponentT> PartialEq for ComponentLink<ComponentT> {
224 fn eq(&self, other: &Self) -> bool {
225 self.component_id == other.component_id
226 }
227}
228
229/// Type to indicate whether a component should be rendered again.
230#[derive(Clone, Copy, Debug, PartialEq, Eq)]
231pub enum ShouldRender {
232 Yes,
233 No,
234}
235
236impl From<ShouldRender> for bool {
237 fn from(should_render: ShouldRender) -> Self {
238 matches!(should_render, ShouldRender::Yes)
239 }
240}
241
242impl From<bool> for ShouldRender {
243 fn from(should_render: bool) -> Self {
244 if should_render {
245 ShouldRender::Yes
246 } else {
247 ShouldRender::No
248 }
249 }
250}
251
252impl std::ops::BitOr for ShouldRender {
253 type Output = Self;
254
255 fn bitor(self, other: Self) -> Self {
256 ((self == ShouldRender::Yes) || (other == ShouldRender::Yes)).into()
257 }
258}
259
260impl std::ops::BitAnd for ShouldRender {
261 type Output = Self;
262
263 fn bitand(self, other: Self) -> Self {
264 ((self == ShouldRender::Yes) && (other == ShouldRender::Yes)).into()
265 }
266}
267
268pub(crate) enum LinkMessage {
269 Component(ComponentId, DynamicMessage),
270 Exit,
271}
272
273impl std::fmt::Debug for LinkMessage {
274 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 write!(formatter, "LinkMessage::")?;
276 match self {
277 Self::Component(id, message) => write!(
278 formatter,
279 "Component({:?}, DynamicMessage(...) @ {:?})",
280 id, &*message.0 as *const _
281 ),
282 Self::Exit => write!(formatter, "Exit"),
283 }
284 }
285}