iced_winit/
window.rs

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