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 draw_preedit(&mut self) {
250        if let Some(preedit) = &self.preedit {
251            preedit.draw(
252                &mut self.renderer,
253                self.state.text_color(),
254                self.state.background_color(),
255                &Rectangle::new(
256                    Point::ORIGIN,
257                    self.state.viewport().logical_size(),
258                ),
259            );
260        }
261    }
262
263    fn enable_ime(&mut self, position: Point, purpose: input_method::Purpose) {
264        if self.ime_state.is_none() {
265            self.raw.set_ime_allowed(true);
266        }
267
268        if self.ime_state != Some((position, purpose)) {
269            self.raw.set_ime_cursor_area(
270                LogicalPosition::new(position.x, position.y),
271                LogicalSize::new(10, 10), // TODO?
272            );
273            self.raw.set_ime_purpose(conversion::ime_purpose(purpose));
274
275            self.ime_state = Some((position, purpose));
276        }
277    }
278
279    fn disable_ime(&mut self) {
280        if self.ime_state.is_some() {
281            self.raw.set_ime_allowed(false);
282            self.ime_state = None;
283        }
284
285        self.preedit = None;
286    }
287}
288
289struct Preedit<Renderer>
290where
291    Renderer: text::Renderer,
292{
293    position: Point,
294    content: Renderer::Paragraph,
295    spans: Vec<text::Span<'static, (), Renderer::Font>>,
296}
297
298impl<Renderer> Preedit<Renderer>
299where
300    Renderer: text::Renderer,
301{
302    fn new() -> Self {
303        Self {
304            position: Point::ORIGIN,
305            spans: Vec::new(),
306            content: Renderer::Paragraph::default(),
307        }
308    }
309
310    fn update(
311        &mut self,
312        position: Point,
313        preedit: &input_method::Preedit,
314        background: Color,
315        renderer: &Renderer,
316    ) {
317        self.position = position;
318
319        let spans = match &preedit.selection {
320            Some(selection) => {
321                vec![
322                    text::Span::new(&preedit.content[..selection.start]),
323                    text::Span::new(if selection.start == selection.end {
324                        "\u{200A}"
325                    } else {
326                        &preedit.content[selection.start..selection.end]
327                    })
328                    .color(background),
329                    text::Span::new(&preedit.content[selection.end..]),
330                ]
331            }
332            _ => vec![text::Span::new(&preedit.content)],
333        };
334
335        if spans != self.spans.as_slice() {
336            use text::Paragraph as _;
337
338            self.content = Renderer::Paragraph::with_spans(Text {
339                content: &spans,
340                bounds: Size::INFINITY,
341                size: preedit
342                    .text_size
343                    .unwrap_or_else(|| renderer.default_size()),
344                line_height: text::LineHeight::default(),
345                font: renderer.default_font(),
346                align_x: text::Alignment::Default,
347                align_y: alignment::Vertical::Top,
348                shaping: text::Shaping::Advanced,
349                wrapping: text::Wrapping::None,
350            });
351
352            self.spans.clear();
353            self.spans
354                .extend(spans.into_iter().map(text::Span::to_static));
355        }
356    }
357
358    fn draw(
359        &self,
360        renderer: &mut Renderer,
361        color: Color,
362        background: Color,
363        viewport: &Rectangle,
364    ) {
365        use text::Paragraph as _;
366
367        if self.content.min_width() < 1.0 {
368            return;
369        }
370
371        let mut bounds = Rectangle::new(
372            self.position - Vector::new(0.0, self.content.min_height()),
373            self.content.min_bounds(),
374        );
375
376        bounds.x = bounds
377            .x
378            .max(viewport.x)
379            .min(viewport.x + viewport.width - bounds.width);
380
381        bounds.y = bounds
382            .y
383            .max(viewport.y)
384            .min(viewport.y + viewport.height - bounds.height);
385
386        renderer.with_layer(bounds, |renderer| {
387            renderer.fill_quad(
388                renderer::Quad {
389                    bounds,
390                    ..Default::default()
391                },
392                background,
393            );
394
395            renderer.fill_paragraph(
396                &self.content,
397                bounds.position(),
398                color,
399                bounds,
400            );
401
402            const UNDERLINE: f32 = 2.0;
403
404            renderer.fill_quad(
405                renderer::Quad {
406                    bounds: bounds.shrink(Padding {
407                        top: bounds.height - UNDERLINE,
408                        ..Default::default()
409                    }),
410                    ..Default::default()
411                },
412                color,
413            );
414
415            for span_bounds in self.content.span_bounds(1) {
416                renderer.fill_quad(
417                    renderer::Quad {
418                        bounds: span_bounds
419                            + (bounds.position() - Point::ORIGIN),
420                        ..Default::default()
421                    },
422                    color,
423                );
424            }
425        });
426    }
427}