iced_winit/program/
state.rs

1use crate::conversion;
2use crate::core::{mouse, window};
3use crate::core::{Color, Size};
4use crate::graphics::Viewport;
5use crate::program::{self, Program};
6use std::fmt::{Debug, Formatter};
7
8use winit::dpi::LogicalPosition;
9use winit::event::{Touch, WindowEvent};
10use winit::window::Window;
11
12/// The state of a multi-windowed [`Program`].
13pub struct State<P: Program>
14where
15    P::Theme: program::DefaultStyle,
16{
17    pub(crate) 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    appearance: program::Appearance,
25}
26
27impl<P: Program> Debug for State<P>
28where
29    P::Theme: program::DefaultStyle,
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("appearance", &self.appearance)
39            .finish()
40    }
41}
42
43impl<P: Program> State<P>
44where
45    P::Theme: program::DefaultStyle,
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: &dyn 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 appearance = application.style(&theme);
57
58        let viewport = {
59            let physical_size = window.surface_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            appearance,
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    pub fn set_logical_cursor_pos(&mut self, pos: LogicalPosition<f64>) {
107        let physical = pos.to_physical(self.scale_factor());
108        self.cursor_position = Some(physical);
109    }
110
111    /// Returns the current cursor position of the [`State`].
112    pub fn cursor(&self) -> mouse::Cursor {
113        self.cursor_position
114            .map(|cursor_position| {
115                conversion::cursor_position(
116                    cursor_position,
117                    self.viewport.scale_factor(),
118                )
119            })
120            .map(mouse::Cursor::Available)
121            .unwrap_or(mouse::Cursor::Unavailable)
122    }
123
124    /// Returns the current keyboard modifiers of the [`State`].
125    pub fn modifiers(&self) -> winit::keyboard::ModifiersState {
126        self.modifiers
127    }
128
129    /// Returns the current theme of the [`State`].
130    pub fn theme(&self) -> &P::Theme {
131        &self.theme
132    }
133
134    /// Returns the current background [`Color`] of the [`State`].
135    pub fn background_color(&self) -> Color {
136        self.appearance.background_color
137    }
138
139    /// Returns the current text [`Color`] of the [`State`].
140    pub fn text_color(&self) -> Color {
141        self.appearance.text_color
142    }
143
144    /// Returns the current icon [`Color`] of the [`State`].
145    pub fn icon_color(&self) -> Color {
146        self.appearance.icon_color
147    }
148
149    /// Update the scale factor
150    pub(crate) fn update_scale_factor(&mut self, new_scale_factor: f64) {
151        let size = self.viewport.physical_size();
152
153        self.viewport = Viewport::with_physical_size(
154            size,
155            new_scale_factor * self.scale_factor,
156        );
157
158        self.viewport_version = self.viewport_version.wrapping_add(1);
159    }
160
161    /// Processes the provided window event and updates the [`State`] accordingly.
162    pub fn update(
163        &mut self,
164        window: &dyn Window,
165        event: &WindowEvent,
166        _debug: &mut crate::runtime::Debug,
167    ) {
168        match event {
169            WindowEvent::SurfaceResized(new_size) => {
170                let size = Size::new(new_size.width, new_size.height);
171
172                self.viewport = Viewport::with_physical_size(
173                    size,
174                    window.scale_factor() * self.scale_factor,
175                );
176
177                self.viewport_version = self.viewport_version.wrapping_add(1);
178            }
179            WindowEvent::ScaleFactorChanged {
180                scale_factor: new_scale_factor,
181                ..
182            } => {
183                self.update_scale_factor(*new_scale_factor);
184            }
185            WindowEvent::CursorMoved { position, .. }
186            | WindowEvent::Touch(Touch {
187                location: position, ..
188            }) => {
189                self.cursor_position = Some(*position);
190            }
191            WindowEvent::CursorLeft { .. } => {
192                self.cursor_position = None;
193            }
194            WindowEvent::ModifiersChanged(new_modifiers) => {
195                self.modifiers = new_modifiers.state();
196            }
197            #[cfg(feature = "debug")]
198            WindowEvent::KeyboardInput {
199                event:
200                    winit::event::KeyEvent {
201                        logical_key:
202                            winit::keyboard::Key::Named(
203                                winit::keyboard::NamedKey::F12,
204                            ),
205                        state: winit::event::ElementState::Pressed,
206                        ..
207                    },
208                ..
209            } => _debug.toggle(),
210            _ => {}
211        }
212    }
213
214    /// Synchronizes the [`State`] with its [`Program`] and its respective
215    /// window.
216    ///
217    /// Normally, a [`Program`] should be synchronized with its [`State`]
218    /// and window after calling [`State::update`].
219    pub fn synchronize(
220        &mut self,
221        application: &P,
222        window_id: window::Id,
223        window: &dyn Window,
224    ) {
225        // Update window title
226        let new_title = application.title(window_id);
227
228        if self.title != new_title {
229            window.set_title(&new_title);
230            self.title = new_title;
231        }
232
233        // Update scale factor and size
234        let new_scale_factor = application.scale_factor(window_id);
235        let mut new_size = window.surface_size();
236        let current_size = self.viewport.physical_size();
237        if self.scale_factor != new_scale_factor
238            || (current_size.width, current_size.height)
239                != (new_size.width, new_size.height)
240                && !(new_size.width == 0 && new_size.height == 0)
241        {
242            if new_size.width == 0 {
243                new_size.width = current_size.width;
244            }
245            if new_size.height == 0 {
246                new_size.height = current_size.height;
247            }
248            self.viewport = Viewport::with_physical_size(
249                Size::new(new_size.width, new_size.height),
250                window.scale_factor() * new_scale_factor,
251            );
252            self.viewport_version = self.viewport_version.wrapping_add(1);
253
254            self.scale_factor = new_scale_factor;
255        }
256
257        // Update theme and appearance
258        self.theme = application.theme(window_id);
259        self.appearance = application.style(&self.theme);
260    }
261}