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}