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}