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