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