-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathshell.rs
More file actions
388 lines (329 loc) · 11.4 KB
/
Copy pathshell.rs
File metadata and controls
388 lines (329 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
//! Communicate with the iced runtime from widgets.
use crate::clipboard;
use crate::event;
use crate::window;
use crate::{Clipboard, InputMethod, Window};
use std::rc::{self, Rc};
use std::sync::Arc;
use std::vec;
/// A connection to the state of a shell.
///
/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application,
/// like publishing messages or invalidating the current layout.
///
/// [`Widget`]: crate::Widget
#[derive(Debug)]
pub struct Shell<'a, Message> {
window: &'a dyn Window,
bus: &'a mut Bus<Message>,
waker: Waker,
event_status: event::Status,
redraw_request: window::RedrawRequest,
input_method: InputMethod,
is_layout_invalid: Option<Diff>,
are_widgets_invalid: bool,
clipboard: Clipboard,
}
impl<'a, Message> Shell<'a, Message> {
/// Creates a new [`Shell`] with the provided buffer of messages.
pub fn new(window: &'a dyn Window, waker: Waker, bus: &'a mut Bus<Message>) -> Self {
Self {
window,
bus,
waker,
event_status: event::Status::Ignored,
redraw_request: window::RedrawRequest::Wait,
is_layout_invalid: None,
are_widgets_invalid: false,
input_method: InputMethod::Disabled,
clipboard: Clipboard {
reads: Vec::new(),
write: None,
},
}
}
/// Creates a new [`Shell`] from the current one with the given list of local messages.
pub fn local<'b, A>(&self, bus: &'b mut Bus<A>) -> Shell<'b, A>
where
'a: 'b,
{
Shell::new(self.window, self.waker.clone(), bus)
}
/// Returns the [`Window`] of the [`Shell`].
pub fn window(&self) -> &'a dyn Window {
self.window
}
/// Returns the [`Waker`] of the [`Shell`].
pub fn waker(&self) -> &Waker {
&self.waker
}
/// Returns true if the [`Shell`] contains no published messages
#[must_use]
pub fn is_empty(&self) -> bool {
self.bus.messages.is_empty()
}
/// Publish the given `Message` for an application to process it.
pub fn publish(&mut self, message: Message) {
let _ = self.publish_and_track(message);
}
/// Publish the given `Message` for an application to process it.
///
/// The returned [`Tracking`] can be used to determine if the message
/// was processed.
pub fn publish_and_track(&mut self, message: Message) -> Tracking {
self.bus.push(message)
}
/// Forwards the given `Message` and fulfills the given [`Receipt`]
/// once processed.
pub fn forward(&mut self, message: Message, receipt: Receipt) {
self.bus.forward(message, receipt);
}
/// Marks the current event as captured. Prevents "event bubbling".
///
/// A widget should capture an event when no ancestor should
/// handle it.
pub fn capture_event(&mut self) {
self.event_status = event::Status::Captured;
}
/// Returns the current [`event::Status`] of the [`Shell`].
#[must_use]
pub fn event_status(&self) -> event::Status {
self.event_status
}
/// Returns whether the current event has been captured.
#[must_use]
pub fn is_event_captured(&self) -> bool {
self.event_status == event::Status::Captured
}
/// Requests a new frame to be drawn as soon as possible.
pub fn request_redraw(&mut self) {
self.redraw_request = window::RedrawRequest::NextFrame;
}
/// Requests a new frame to be drawn at the given [`window::RedrawRequest`].
pub fn request_redraw_at(&mut self, redraw_request: impl Into<window::RedrawRequest>) {
self.redraw_request = self.redraw_request.min(redraw_request.into());
}
/// Returns the request a redraw should happen, if any.
#[must_use]
pub fn redraw_request(&self) -> window::RedrawRequest {
self.redraw_request
}
/// Replaces the redraw request of the [`Shell`]; without conflict resolution.
///
/// This is useful if you want to overwrite the redraw request to a previous value.
/// Since it's a fairly advanced use case and should rarely be used, it is a static
/// method.
pub fn replace_redraw_request(shell: &mut Self, redraw_request: window::RedrawRequest) {
shell.redraw_request = redraw_request;
}
/// Requests the runtime to read the clipboard contents expecting the given [`clipboard::Kind`].
///
/// The runtime will produce a [`clipboard::Event::Read`] when the contents have been read.
pub fn read_clipboard(&mut self, kind: clipboard::Kind) {
self.clipboard.reads.push(kind);
}
/// Requests the runtime to write the given [`clipboard::Content`] to the clipboard.
///
/// The runtime will produce a [`clipboard::Event::Written`] when the contents have been written.
pub fn write_clipboard(&mut self, content: impl Into<clipboard::Content>) {
self.clipboard.write = Some(content.into());
}
/// Returns the [`Clipboard`] requests of the [`Shell`], mutably.
pub fn clipboard_mut(&mut self) -> &mut Clipboard {
&mut self.clipboard
}
/// Requests the current [`InputMethod`] strategy.
///
/// __Important__: This request will only be honored by the
/// [`Shell`] only during a [`window::Event::RedrawRequested`].
pub fn request_input_method<T: AsRef<str>>(&mut self, ime: &InputMethod<T>) {
self.input_method.merge(ime);
}
/// Returns the current [`InputMethod`] strategy.
#[must_use]
pub fn input_method(&self) -> &InputMethod {
&self.input_method
}
/// Returns the current [`InputMethod`] strategy.
#[must_use]
pub fn input_method_mut(&mut self) -> &mut InputMethod {
&mut self.input_method
}
/// Returns whether the current layout is invalid or not.
#[must_use]
pub fn is_layout_invalid(&self) -> Option<Diff> {
self.is_layout_invalid
}
/// Invalidates the current application layout.
///
/// The shell will relayout the application widgets.
pub fn invalidate_layout(&mut self) {
self.invalidate_layout_with(Diff::Skip);
}
/// Invalidates the current application layout with the following [`Diff`] strategy.
pub fn invalidate_layout_with(&mut self, diff: Diff) {
self.is_layout_invalid = Some(diff);
}
/// Triggers the given function if the layout is invalid, cleaning it in the
/// process.
pub fn revalidate_layout(&mut self, f: impl FnOnce(Diff)) {
if let Some(diff) = self.is_layout_invalid.take() {
f(diff);
}
}
/// Returns whether the widgets of the current application have been
/// invalidated.
#[must_use]
pub fn are_widgets_invalid(&self) -> bool {
self.are_widgets_invalid
}
/// Invalidates the current application widgets.
///
/// The shell will rebuild and relayout the widget tree.
pub fn invalidate_widgets(&mut self) {
self.are_widgets_invalid = true;
}
/// Merges the current [`Shell`] with another one by applying the given
/// function to the messages of the latter.
///
/// This method is useful for composition.
pub fn merge<B>(&mut self, mut other: Shell<'_, B>, f: impl Fn(B) -> Message) {
self.bus.messages.extend(
other
.bus
.messages
.drain(..)
.map(|(message, receipt)| (f(message), receipt)),
);
self.is_layout_invalid = match (self.is_layout_invalid, other.is_layout_invalid) {
(Some(a), Some(b)) => Some(a.max(b)),
_ => self.is_layout_invalid.or(other.is_layout_invalid),
};
self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid;
self.redraw_request = self.redraw_request.min(other.redraw_request);
self.event_status = self.event_status.merge(other.event_status);
self.input_method.merge(&other.input_method);
self.clipboard.merge(&mut other.clipboard);
}
}
/// A waker can be used to wake up the iced runtime and, consequently, trigger
/// wake events concurrently from widget logic.
#[derive(Clone)]
pub struct Waker {
wake: Arc<dyn Fn() + Send + Sync + 'static>,
}
impl Waker {
/// Creates a new [`Waker`] with the given `wake` function.
pub fn new(wake: impl Fn() + Send + Sync + 'static) -> Self {
Self {
wake: Arc::new(wake),
}
}
/// Creates a new [`Waker`] that does nothing.
pub fn noop() -> Self {
Self::new(|| {})
}
/// Wakes up the iced runtime as soon as possible.
///
/// You normally want to call this concurrently (e.g. from a different thread).
pub fn wake(&self) {
(self.wake)();
}
}
impl std::fmt::Debug for Waker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Waker").finish()
}
}
/// The diffing strategy to follow when invalidating some layout.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Diff {
/// Skips the diffing step.
Skip,
/// Performs diffing again before layouting.
Perform,
}
/// A channel of messages published by a [`Shell`].
#[derive(Debug)]
pub struct Bus<T> {
messages: Vec<(T, Receipt)>,
}
impl<T> Bus<T> {
/// Creates an empty [`Bus`].
pub fn new() -> Self {
Self {
messages: Vec::new(),
}
}
/// Returns `true` if the [`Bus`] has no messages pending.
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
/// Returns the amount of messages pending in the [`Bus`].
pub fn len(&self) -> usize {
self.messages.len()
}
/// Pushes a new message to the [`Bus`].
///
/// The returned [`Tracking`] can be used to determine if the message
/// was processed.
pub fn push(&mut self, message: T) -> Tracking {
let receipt = Receipt::new();
let tracking = receipt.tracking();
self.messages.push((message, receipt));
tracking
}
/// Forward a new message to the [`Bus`] with the given [`Receipt`].
pub fn forward(&mut self, message: T, receipt: Receipt) {
self.messages.push((message, receipt));
}
/// Drains the [`Bus`].
pub fn drain(&mut self) -> impl Iterator<Item = (T, Receipt)> {
self.messages.drain(..)
}
}
impl<T> Default for Bus<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> IntoIterator for Bus<T> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
iter: self.messages.into_iter(),
}
}
}
/// An iterator returned by the implementation of [`IntoIterator`] for [`Bus`].
pub struct IntoIter<T> {
iter: vec::IntoIter<(T, Receipt)>,
}
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
Some(self.iter.next()?.0)
}
}
/// Proof that a message has been received.
#[derive(Debug)]
pub struct Receipt(Rc<()>);
impl Receipt {
fn new() -> Self {
Self(Rc::new(()))
}
fn tracking(&self) -> Tracking {
Tracking(Rc::downgrade(&self.0))
}
}
/// A message tracking returned by [`Shell::publish`].
#[derive(Debug, Clone)]
pub struct Tracking(rc::Weak<()>);
impl Tracking {
/// Returns `true` if the message of this [`Tracking`] has been processed
/// by `update` logic.
pub fn is_processed(&self) -> bool {
self.0.strong_count() == 0
}
}