iced_winit/program/
state.rs

1use crate::conversion;
2use crate::core::{Color, Size};
3use crate::core::{mouse, theme, window};
4use crate::graphics::Viewport;
5use crate::program::Program;
6
7use winit::event::{Touch, WindowEvent};
8use winit::window::Window;
9
10use std::fmt::{Debug, Formatter};
11
12/// The state of a multi-windowed [`Program`].
13pub struct State<P: Program>
14where
15    P::Theme: theme::Base,
16{
17    title: String,
18    scale_factor: f64,
19    viewport: Viewport,
20    viewport_version: u64,
21    cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
22    modifiers: winit::keyboard::ModifiersState,
23    theme: P::Theme,
24    style: theme::Style,
25}
26
27impl<P: Program> Debug for State<P>
28where
29    P::Theme: theme::Base,
30{
31    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32        f.debug_struct("multi_window::State")
33            .field("title", &self.title)
34            .field("scale_factor", &self.scale_factor)
35            .field("viewport", &self.viewport)
36            .field("viewport_version", &self.viewport_version)
37            .field("cursor_position", &self.cursor_position)
38            .field("style", &self.style)
39            .finish()
40    }
41}
42
43impl<P: Program> State<P>
44where
45    P::Theme: theme::Base,
46{
47    /// Creates a new [`State`] for the provided [`Program`]'s `window`.
48    pub fn new(
49        application: &P,
50        window_id: window::Id,
51        window: &Window,
52    ) -> Self {
53        let title = application.title(window_id);
54        let scale_factor = application.scale_factor(window_id);
55        let theme = application.theme(window_id);
56        let style = application.style(&theme);
57
58        let viewport = {
59            let physical_size = window.inner_size();
60
61            Viewport::with_physical_size(
62                Size::new(physical_size.width, physical_size.height),
63                window.scale_factor() * scale_factor,
64            )
65        };
66
67        Self {
68            title,
69            scale_factor,
70            viewport,
71            viewport_version: 0,
72            cursor_position: None,
73            modifiers: winit::keyboard::ModifiersState::default(),
74            theme,
75            style,
76        }
77    }
78
79    /// Returns the current [`Viewport`] of the [`State`].
80    pub fn viewport(&self) -> &Viewport {
81        &self.viewport
82    }
83
84    /// Returns the version of the [`Viewport`] of the [`State`].
85    ///
86    /// The version is incremented every time the [`Viewport`] changes.
87    pub fn viewport_version(&self) -> u64 {
88        self.viewport_version
89    }
90
91    /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`].
92    pub fn physical_size(&self) -> Size<u32> {
93        self.viewport.physical_size()
94    }
95
96    /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`].
97    pub fn logical_size(&self) -> Size<f32> {
98        self.viewport.logical_size()
99    }
100
101    /// Returns the current scale factor of the [`Viewport`] of the [`State`].
102    pub fn scale_factor(&self) -> f64 {
103        self.viewport.scale_factor()
104    }
105
106    /// Returns the current cursor position of the [`State`].
107    pub fn cursor(&self) -> mouse::Cursor {
108        self.cursor_position
109            .map(|cursor_position| {
110                conversion::cursor_position(
111                    cursor_position,
112                    self.viewport.scale_factor(),
113                )
114            })
115            .map(mouse::Cursor::Available)
116            .unwrap_or(mouse::Cursor::Unavailable)
117    }
118
119    /// Returns the current keyboard modifiers of the [`State`].
120    pub fn modifiers(&self) -> winit::keyboard::ModifiersState {
121        self.modifiers
122    }
123
124    /// Returns the current theme of the [`State`].
125    pub fn theme(&self) -> &P::Theme {
126        &self.theme
127    }
128
129    /// Returns the current background [`Color`] of the [`State`].
130    pub fn background_color(&self) -> Color {
131        self.style.background_color
132    }
133
134    /// Returns the current text [`Color`] of the [`State`].
135    pub fn text_color(&self) -> Color {
136        self.style.text_color
137    }
138
139    /// Processes the provided window event and updates the [`State`] accordingly.
140    pub fn update(
141        &mut self,
142        window: &Window,
143        event: &WindowEvent,
144        _debug: &mut crate::runtime::Debug,
145    ) {
146        match event {
147            WindowEvent::Resized(new_size) => {
148                let size = Size::new(new_size.width, new_size.height);
149
150                self.viewport = Viewport::with_physical_size(
151                    size,
152                    window.scale_factor() * self.scale_factor,
153                );
154
155                self.viewport_version = self.viewport_version.wrapping_add(1);
156            }
157            WindowEvent::ScaleFactorChanged {
158                scale_factor: new_scale_factor,
159                ..
160            } => {
161                let size = self.viewport.physical_size();
162
163                self.viewport = Viewport::with_physical_size(
164                    size,
165                    new_scale_factor * self.scale_factor,
166                );
167
168                self.viewport_version = self.viewport_version.wrapping_add(1);
169            }
170            WindowEvent::CursorMoved { position, .. }
171            | WindowEvent::Touch(Touch {
172                location: position, ..
173            }) => {
174                self.cursor_position = Some(*position);
175            }
176            WindowEvent::CursorLeft { .. } => {
177                self.cursor_position = None;
178            }
179            WindowEvent::ModifiersChanged(new_modifiers) => {
180                self.modifiers = new_modifiers.state();
181            }
182            #[cfg(feature = "debug")]
183            WindowEvent::KeyboardInput {
184                event:
185                    winit::event::KeyEvent {
186                        logical_key:
187                            winit::keyboard::Key::Named(
188                                winit::keyboard::NamedKey::F12,
189                            ),
190                        state: winit::event::ElementState::Pressed,
191                        ..
192                    },
193                ..
194            } => {
195                _debug.toggle();
196                window.request_redraw();
197            }
198            _ => {}
199        }
200    }
201
202    /// Synchronizes the [`State`] with its [`Program`] and its respective
203    /// window.
204    ///
205    /// Normally, a [`Program`] should be synchronized with its [`State`]
206    /// and window after calling [`State::update`].
207    pub fn synchronize(
208        &mut self,
209        application: &P,
210        window_id: window::Id,
211        window: &Window,
212    ) {
213        // Update window title
214        let new_title = application.title(window_id);
215
216        if self.title != new_title {
217            window.set_title(&new_title);
218            self.title = new_title;
219        }
220
221        // Update scale factor and size
222        let new_scale_factor = application.scale_factor(window_id);
223        let new_size = window.inner_size();
224        let current_size = self.viewport.physical_size();
225
226        if self.scale_factor != new_scale_factor
227            || (current_size.width, current_size.height)
228                != (new_size.width, new_size.height)
229        {
230            self.viewport = Viewport::with_physical_size(
231                Size::new(new_size.width, new_size.height),
232                window.scale_factor() * new_scale_factor,
233            );
234            self.viewport_version = self.viewport_version.wrapping_add(1);
235
236            self.scale_factor = new_scale_factor;
237        }
238
239        // Update theme and appearance
240        self.theme = application.theme(window_id);
241        self.style = application.style(&self.theme);
242    }
243}