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}