relm/state/
mod.rs

1/*
2 * Copyright (c) 2017 Boucher, Antoni <bouanto@zoho.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 * this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to
7 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 * the Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22//! This crate provide the non-GUI part of relm:
23//! Basic component and message connection methods.
24
25#![warn(
26    missing_docs,
27    trivial_casts,
28    trivial_numeric_casts,
29    unused_extern_crates,
30    unused_import_braces,
31    unused_qualifications,
32    unused_results,
33)]
34
35mod into;
36mod macros;
37
38use std::time::SystemTime;
39
40pub use crate::core::{EventStream, StreamHandle};
41
42pub use self::into::{IntoOption, IntoPair};
43
44/// Handle event stream to send messages to the [`update()`](trait.Update.html#tymethod.update) method.
45pub struct Relm<UPDATE: Update> {
46    stream: StreamHandle<UPDATE::Msg>,
47}
48
49impl<UPDATE: Update> Clone for Relm<UPDATE> {
50    fn clone(&self) -> Self {
51        Relm {
52            stream: self.stream.clone(),
53        }
54    }
55}
56
57impl<UPDATE: Update> Relm<UPDATE> {
58    /// Create a new relm stream handler.
59    pub fn new(stream: &EventStream<UPDATE::Msg>) -> Self {
60        Relm {
61            stream: stream.downgrade(),
62        }
63    }
64
65    /// Get the event stream of this stream.
66    /// This is used internally by the library.
67    pub fn stream(&self) -> &StreamHandle<UPDATE::Msg> {
68        &self.stream
69    }
70}
71
72/// Trait for a basic (non-widget) component.
73/// A component has a model (data) associated with it and can mutate it when it receives a message
74/// (in the `update()` method).
75pub trait Update
76    where Self: Sized,
77          Self::Msg: DisplayVariant,
78{
79    /// The type of the model.
80    type Model;
81    /// The type of the parameter of the model() function used to initialize the model.
82    type ModelParam: Sized;
83    /// The type of the messages sent to the [`update()`](trait.Update.html#tymethod.update) method.
84    type Msg;
85
86    /// Create the initial model.
87    fn model(relm: &Relm<Self>, param: Self::ModelParam) -> Self::Model;
88
89    /// Connect the subscriptions.
90    /// Subscriptions are `Future`/`Stream` that are spawn when the object is created.
91    fn subscriptions(&mut self, _relm: &Relm<Self>) {
92    }
93
94    /// Method called when a message is received from an event.
95    fn update(&mut self, event: Self::Msg);
96}
97
98/// Trait for an `Update` object that can be created directly.
99/// This is useful for non-widget component.
100pub trait UpdateNew: Update {
101    /// Create a new component.
102    fn new(_relm: &Relm<Self>, _model: Self::Model) -> Self;
103}
104
105/// Format trait for enum variants.
106///
107/// `DisplayVariant` is similar to `Debug`, but only works on enum and does not list the
108/// variants' parameters.
109///
110/// This is used internally by the library.
111pub trait DisplayVariant {
112    /// Formats the current variant of the enum.
113    fn display_variant(&self) -> &'static str;
114}
115
116impl DisplayVariant for () {
117    fn display_variant(&self) -> &'static str {
118        ""
119    }
120}
121
122/// Create a bare component, i.e. a component only implementing the Update trait, not the Widget
123/// trait.
124pub fn execute<UPDATE>(model_param: UPDATE::ModelParam) -> EventStream<UPDATE::Msg>
125where UPDATE: Update + UpdateNew + 'static
126{
127    let stream = EventStream::new();
128
129    let relm = Relm::new(&stream);
130    let model = UPDATE::model(&relm, model_param);
131    let component = UPDATE::new(&relm, model);
132
133    init_component::<UPDATE>(&stream, component, &relm);
134    stream
135}
136
137/// Initialize a component by creating its subscriptions and dispatching the messages from the
138/// stream.
139pub fn init_component<UPDATE>(stream: &EventStream<UPDATE::Msg>, mut component: UPDATE, relm: &Relm<UPDATE>)
140    where UPDATE: Update + 'static,
141          UPDATE::Msg: DisplayVariant + 'static,
142{
143    component.subscriptions(relm);
144    stream.set_callback(move |event| {
145        update_component(&mut component, event);
146    });
147}
148
149fn update_component<COMPONENT>(component: &mut COMPONENT, event: COMPONENT::Msg)
150    where COMPONENT: Update,
151{
152    if cfg!(debug_assertions) {
153        let time = SystemTime::now();
154        let debug = event.display_variant();
155        let debug =
156            if debug.len() > 100 {
157                format!("{}…", &debug[..100])
158            }
159            else {
160                debug.to_string()
161            };
162        component.update(event);
163        if let Ok(duration) = time.elapsed() {
164            let ms = duration.subsec_nanos() as u64 / 1_000_000 + duration.as_secs() * 1000;
165            if ms >= 16 {
166                log::warn!("The update function was slow to execute for message {}: {}ms", debug, ms);
167            }
168        }
169    }
170    else {
171        component.update(event)
172    }
173}