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
use crate::clipboard;
use crate::dnd;
use crate::event;
use crate::window;
use crate::{Clipboard, InputMethod};
/// 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> {
messages: &'a mut Vec<Message>,
event_status: event::Status,
redraw_request: window::RedrawRequest,
input_method: InputMethod,
is_layout_invalid: bool,
are_widgets_invalid: bool,
is_auto_focus_pending: bool,
clipboard: Clipboard,
}
impl<'a, Message> Shell<'a, Message> {
/// Creates a new [`Shell`] with the provided buffer of messages.
pub fn new(messages: &'a mut Vec<Message>) -> Self {
Self {
messages,
event_status: event::Status::Ignored,
redraw_request: window::RedrawRequest::Wait,
is_layout_invalid: false,
are_widgets_invalid: false,
is_auto_focus_pending: false,
input_method: InputMethod::Disabled,
clipboard: Clipboard {
reads: Vec::new(),
write: None,
dnd_requests: Vec::new(),
primary_reads: false,
primary_write: None,
},
}
}
/// Returns true if the [`Shell`] contains no published messages
#[must_use]
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
/// Publish the given `Message` for an application to process it.
pub fn publish(&mut self, message: Message) {
self.messages.push(message);
}
/// 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: clipboard::Content) {
self.clipboard.write = Some(content);
}
/// Returns the [`Clipboard`] requests of the [`Shell`], mutably.
pub fn clipboard_mut(&mut self) -> &mut Clipboard {
&mut self.clipboard
}
/// Start a Wayland drag-and-drop operation.
///
/// The runtime will create a `wl_data_source`, offer the given MIME
/// types, and call `wl_data_device.start_drag(…)` with the current
/// pointer serial.
pub fn start_dnd(
&mut self,
mime_types: Vec<String>,
actions: dnd::DndAction,
data: Vec<Vec<u8>>,
internal: bool,
) {
self.clipboard.dnd_requests.push(dnd::Request::StartDrag {
internal,
mime_types,
actions,
data,
icon: None,
hotspot: None,
});
}
/// Start a Wayland drag-and-drop operation with a custom icon.
///
/// Same as [`start_dnd`](Self::start_dnd) but with pre-rendered
/// pixel data that the compositor will display under the cursor.
///
/// `scale` is the buffer scale for HiDPI support (1 for standard, 2 for 2x).
pub fn start_dnd_with_icon(
&mut self,
mime_types: Vec<String>,
actions: dnd::DndAction,
data: Vec<Vec<u8>>,
internal: bool,
width: u32,
height: u32,
pixels: Vec<u8>,
scale: i32,
) {
self.clipboard.dnd_requests.push(dnd::Request::StartDrag {
internal,
mime_types,
actions,
data,
icon: Some(dnd::DndIcon::Pixels {
width,
height,
pixels,
scale,
}),
hotspot: None,
});
}
/// Start a Wayland drag-and-drop operation with an iced widget
/// [`Element`] as the icon.
///
/// The runtime will render the element offscreen using the
/// compositor, convert to pre-multiplied ARGB, and use it as the
/// Wayland drag icon surface.
///
/// [`Element`]: crate::Element
pub fn start_dnd_with_icon_element(
&mut self,
mime_types: Vec<String>,
actions: dnd::DndAction,
data: Vec<Vec<u8>>,
internal: bool,
icon_surface: dnd::DndIconSurface,
hotspot_x: i32,
hotspot_y: i32,
) {
self.clipboard.dnd_requests.push(dnd::Request::StartDrag {
internal,
mime_types,
actions,
data,
icon: Some(dnd::DndIcon::Element(icon_surface)),
hotspot: Some((hotspot_x, hotspot_y)),
});
}
/// Accept the current DnD offer for a specific MIME type.
pub fn accept_dnd_mime_type(&mut self, mime_type: Option<String>) {
self.clipboard
.dnd_requests
.push(dnd::Request::AcceptMimeType(mime_type));
}
/// Set the accepted DnD actions for the current offer.
pub fn set_dnd_actions(&mut self, actions: dnd::DndAction, preferred: dnd::DndAction) {
self.clipboard
.dnd_requests
.push(dnd::Request::SetActions { actions, preferred });
}
/// Request data for a given MIME type from the current DnD offer.
pub fn request_dnd_data(&mut self, mime_type: String) {
self.clipboard
.dnd_requests
.push(dnd::Request::RequestData { mime_type });
}
/// Signal that the destination has finished processing the DnD drop.
pub fn finish_dnd(&mut self) {
self.clipboard.dnd_requests.push(dnd::Request::FinishDnd);
}
/// End / cancel the current DnD operation from the source side.
pub fn end_dnd(&mut self) {
self.clipboard.dnd_requests.push(dnd::Request::EndDnd);
}
/// 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) -> bool {
self.is_layout_invalid
}
/// Invalidates the current application layout.
///
/// The shell will relayout the application widgets.
pub fn invalidate_layout(&mut self) {
self.is_layout_invalid = true;
}
/// Triggers the given function if the layout is invalid, cleaning it in the
/// process.
pub fn revalidate_layout(&mut self, f: impl FnOnce()) {
if self.is_layout_invalid {
self.is_layout_invalid = false;
f();
}
}
/// 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;
}
/// Returns whether an auto-focus operation is pending.
#[must_use]
pub fn is_auto_focus_pending(&self) -> bool {
self.is_auto_focus_pending
}
/// Requests the framework to run the auto-focus operation after
/// the current event batch is processed.
pub fn request_auto_focus(&mut self) {
self.is_auto_focus_pending = 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.messages.extend(other.messages.drain(..).map(f));
self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid;
self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid;
self.is_auto_focus_pending = self.is_auto_focus_pending || other.is_auto_focus_pending;
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);
}
}