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), );
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}