iced_winit/program/
window_manager.rs

1use crate::conversion;
2use crate::core::alignment;
3use crate::core::input_method;
4use crate::core::mouse;
5use crate::core::renderer;
6use crate::core::text;
7use crate::core::theme;
8use crate::core::time::Instant;
9use crate::core::window::{Id, RedrawRequest};
10use crate::core::{
11    Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector,
12};
13use crate::graphics::Compositor;
14use crate::program::{Program, State};
15
16use winit::dpi::{LogicalPosition, LogicalSize};
17use winit::monitor::MonitorHandle;
18
19use std::collections::BTreeMap;
20use std::sync::Arc;
21
22#[allow(missing_debug_implementations)]
23pub struct WindowManager<P, C>
24where
25    P: Program,
26    C: Compositor<Renderer = P::Renderer>,
27    P::Theme: theme::Base,
28{
29    aliases: BTreeMap<winit::window::WindowId, Id>,
30    entries: BTreeMap<Id, Window<P, C>>,
31}
32
33impl<P, C> WindowManager<P, C>
34where
35    P: Program,
36    C: Compositor<Renderer = P::Renderer>,
37    P::Theme: theme::Base,
38{
39    pub fn new() -> Self {
40        Self {
41            aliases: BTreeMap::new(),
42            entries: BTreeMap::new(),
43        }
44    }
45
46    pub fn insert(
47        &mut self,
48        id: Id,
49        window: Arc<winit::window::Window>,
50        application: &P,
51        compositor: &mut C,
52        exit_on_close_request: bool,
53    ) -> &mut Window<P, C> {
54        let state = State::new(application, id, &window);
55        let viewport_version = state.viewport_version();
56        let physical_size = state.physical_size();
57        let surface = compositor.create_surface(
58            window.clone(),
59            physical_size.width,
60            physical_size.height,
61        );
62        let renderer = compositor.create_renderer();
63
64        let _ = self.aliases.insert(window.id(), id);
65
66        let _ = self.entries.insert(
67            id,
68            Window {
69                raw: window,
70                state,
71                viewport_version,
72                exit_on_close_request,
73                surface,
74                renderer,
75                mouse_interaction: mouse::Interaction::None,
76                redraw_at: None,
77                preedit: None,
78                ime_state: None,
79            },
80        );
81
82        self.entries
83            .get_mut(&id)
84            .expect("Get window that was just inserted")
85    }
86
87    pub fn is_empty(&self) -> bool {
88        self.entries.is_empty()
89    }
90
91    pub fn is_idle(&self) -> bool {
92        self.entries
93            .values()
94            .all(|window| window.redraw_at.is_none())
95    }
96
97    pub fn redraw_at(&self) -> Option<Instant> {
98        self.entries
99            .values()
100            .filter_map(|window| window.redraw_at)
101            .min()
102    }
103
104    pub fn first(&self) -> Option<&Window<P, C>> {
105        self.entries.first_key_value().map(|(_id, window)| window)
106    }
107
108    pub fn iter_mut(
109        &mut self,
110    ) -> impl Iterator<Item = (Id, &mut Window<P, C>)> {
111        self.entries.iter_mut().map(|(k, v)| (*k, v))
112    }
113
114    pub fn get(&self, id: Id) -> Option<&Window<P, C>> {
115        self.entries.get(&id)
116    }
117
118    pub fn get_mut(&mut self, id: Id) -> Option<&mut Window<P, C>> {
119        self.entries.get_mut(&id)
120    }
121
122    pub fn get_mut_alias(
123        &mut self,
124        id: winit::window::WindowId,
125    ) -> Option<(Id, &mut Window<P, C>)> {
126        let id = self.aliases.get(&id).copied()?;
127
128        Some((id, self.get_mut(id)?))
129    }
130
131    pub fn last_monitor(&self) -> Option<MonitorHandle> {
132        self.entries.values().last()?.raw.current_monitor()
133    }
134
135    pub fn remove(&mut self, id: Id) -> Option<Window<P, C>> {
136        let window = self.entries.remove(&id)?;
137        let _ = self.aliases.remove(&window.raw.id());
138
139        Some(window)
140    }
141}
142
143impl<P, C> Default for WindowManager<P, C>
144where
145    P: Program,
146    C: Compositor<Renderer = P::Renderer>,
147    P::Theme: theme::Base,
148{
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154#[allow(missing_debug_implementations)]
155pub struct Window<P, C>
156where
157    P: Program,
158    C: Compositor<Renderer = P::Renderer>,
159    P::Theme: theme::Base,
160{
161    pub raw: Arc<winit::window::Window>,
162    pub state: State<P>,
163    pub viewport_version: u64,
164    pub exit_on_close_request: bool,
165    pub mouse_interaction: mouse::Interaction,
166    pub surface: C::Surface,
167    pub renderer: P::Renderer,
168    pub redraw_at: Option<Instant>,
169    preedit: Option<Preedit<P::Renderer>>,
170    ime_state: Option<(Point, input_method::Purpose)>,
171}
172
173impl<P, C> Window<P, C>
174where
175    P: Program,
176    C: Compositor<Renderer = P::Renderer>,
177    P::Theme: theme::Base,
178{
179    pub fn position(&self) -> Option<Point> {
180        self.raw
181            .outer_position()
182            .ok()
183            .map(|position| position.to_logical(self.raw.scale_factor()))
184            .map(|position| Point {
185                x: position.x,
186                y: position.y,
187            })
188    }
189
190    pub fn size(&self) -> Size {
191        let size = self.raw.inner_size().to_logical(self.raw.scale_factor());
192
193        Size::new(size.width, size.height)
194    }
195
196    pub fn request_redraw(&mut self, redraw_request: RedrawRequest) {
197        match redraw_request {
198            RedrawRequest::NextFrame => {
199                self.raw.request_redraw();
200                self.redraw_at = None;
201            }
202            RedrawRequest::At(at) => {
203                self.redraw_at = Some(at);
204            }
205            RedrawRequest::Wait => {}
206        }
207    }
208
209    pub fn request_input_method(&mut self, input_method: InputMethod) {
210        match input_method {
211            InputMethod::Disabled => {
212                self.disable_ime();
213            }
214            InputMethod::Enabled {
215                position,
216                purpose,
217                preedit,
218            } => {
219                self.enable_ime(position, purpose);
220
221                if let Some(preedit) = preedit {
222                    if preedit.content.is_empty() {
223                        self.preedit = None;
224                    } else {
225                        let mut overlay =
226                            self.preedit.take().unwrap_or_else(Preedit::new);
227
228                        overlay.update(
229                            position,
230                            &preedit,
231                            self.state.background_color(),
232                            &self.renderer,
233                        );
234
235                        self.preedit = Some(overlay);
236                    }
237                } else {
238                    self.preedit = None;
239                }
240            }
241        }
242    }
243
244    pub fn draw_preedit(&mut self) {
245        if let Some(preedit) = &self.preedit {
246            preedit.draw(
247                &mut self.renderer,
248                self.state.text_color(),
249                self.state.background_color(),
250                &Rectangle::new(
251                    Point::ORIGIN,
252                    self.state.viewport().logical_size(),
253                ),
254            );
255        }
256    }
257
258    fn enable_ime(&mut self, position: Point, purpose: input_method::Purpose) {
259        if self.ime_state.is_none() {
260            self.raw.set_ime_allowed(true);
261        }
262
263        if self.ime_state != Some((position, purpose)) {
264            self.raw.set_ime_cursor_area(
265                LogicalPosition::new(position.x, position.y),
266                LogicalSize::new(10, 10), // TODO?
267            );
268            self.raw.set_ime_purpose(conversion::ime_purpose(purpose));
269
270            self.ime_state = Some((position, purpose));
271        }
272    }
273
274    fn disable_ime(&mut self) {
275        if self.ime_state.is_some() {
276            self.raw.set_ime_allowed(false);
277            self.ime_state = None;
278        }
279
280        self.preedit = None;
281    }
282}
283
284struct Preedit<Renderer>
285where
286    Renderer: text::Renderer,
287{
288    position: Point,
289    content: Renderer::Paragraph,
290    spans: Vec<text::Span<'static, (), Renderer::Font>>,
291}
292
293impl<Renderer> Preedit<Renderer>
294where
295    Renderer: text::Renderer,
296{
297    fn new() -> Self {
298        Self {
299            position: Point::ORIGIN,
300            spans: Vec::new(),
301            content: Renderer::Paragraph::default(),
302        }
303    }
304
305    fn update(
306        &mut self,
307        position: Point,
308        preedit: &input_method::Preedit,
309        background: Color,
310        renderer: &Renderer,
311    ) {
312        self.position = position;
313
314        let spans = match &preedit.selection {
315            Some(selection) => {
316                vec![
317                    text::Span::new(&preedit.content[..selection.start]),
318                    text::Span::new(if selection.start == selection.end {
319                        "\u{200A}"
320                    } else {
321                        &preedit.content[selection.start..selection.end]
322                    })
323                    .color(background),
324                    text::Span::new(&preedit.content[selection.end..]),
325                ]
326            }
327            _ => vec![text::Span::new(&preedit.content)],
328        };
329
330        if spans != self.spans.as_slice() {
331            use text::Paragraph as _;
332
333            self.content = Renderer::Paragraph::with_spans(Text {
334                content: &spans,
335                bounds: Size::INFINITY,
336                size: preedit
337                    .text_size
338                    .unwrap_or_else(|| renderer.default_size()),
339                line_height: text::LineHeight::default(),
340                font: renderer.default_font(),
341                align_x: text::Alignment::Default,
342                align_y: alignment::Vertical::Top,
343                shaping: text::Shaping::Advanced,
344                wrapping: text::Wrapping::None,
345            });
346
347            self.spans.clear();
348            self.spans
349                .extend(spans.into_iter().map(text::Span::to_static));
350        }
351    }
352
353    fn draw(
354        &self,
355        renderer: &mut Renderer,
356        color: Color,
357        background: Color,
358        viewport: &Rectangle,
359    ) {
360        use text::Paragraph as _;
361
362        if self.content.min_width() < 1.0 {
363            return;
364        }
365
366        let mut bounds = Rectangle::new(
367            self.position - Vector::new(0.0, self.content.min_height()),
368            self.content.min_bounds(),
369        );
370
371        bounds.x = bounds
372            .x
373            .max(viewport.x)
374            .min(viewport.x + viewport.width - bounds.width);
375
376        bounds.y = bounds
377            .y
378            .max(viewport.y)
379            .min(viewport.y + viewport.height - bounds.height);
380
381        renderer.with_layer(bounds, |renderer| {
382            renderer.fill_quad(
383                renderer::Quad {
384                    bounds,
385                    ..Default::default()
386                },
387                background,
388            );
389
390            renderer.fill_paragraph(
391                &self.content,
392                bounds.position(),
393                color,
394                bounds,
395            );
396
397            const UNDERLINE: f32 = 2.0;
398
399            renderer.fill_quad(
400                renderer::Quad {
401                    bounds: bounds.shrink(Padding {
402                        top: bounds.height - UNDERLINE,
403                        ..Default::default()
404                    }),
405                    ..Default::default()
406                },
407                color,
408            );
409
410            for span_bounds in self.content.span_bounds(1) {
411                renderer.fill_quad(
412                    renderer::Quad {
413                        bounds: span_bounds
414                            + (bounds.position() - Point::ORIGIN),
415                        ..Default::default()
416                    },
417                    color,
418                );
419            }
420        });
421    }
422}