candelabre_windowing/lib.rs
1//! # Welcome!
2//!
3//! The purpose of this crate is to provide a way to easily handle windows,
4//! just one or a multitude of them. After creating the window, it's simple to
5//! use the candelabre-core and candelabre-widget to quickly construct a
6//! beautiful GUI.
7//!
8//! # A bit of history
9//!
10//! At the beginning, this crate was designed to use glutin v0.22 with
11//! [luminance](https://github.com/phaazon/luminance-rs/), but due to some
12//! foolish idea from the developper, the initial goal slide a bit. Now the
13//! purpose of candelabre-windowing is mostly to support candelabre-widget, but
14//! you can still use it solely with `gl`, `rgl`, or any lib who play with
15//! OpenGL.
16//!
17//! # What's inside this crate?
18//!
19//! This crate provide a two elements:
20//!
21//! * `CandlSurface`, a window type who generate a surface for using OpenGL
22//! * `CandlManager`, a window manager to enable using multiple windows in a
23//! single application / thread
24//! * `CandlWindow`, a trait on which `CandlSurface` is based on
25//!
26//! ## `CandlSurface`
27//!
28//! Core of this lib, it's a simple way to get an OpenGL context and then use
29//! luminance [luminance](https://github.com/phaazon/luminance-rs/). Initially
30//! it's a copy of luminance-glutin lib, modified to be able to work with the
31//! CandlManager.
32//!
33//! ## `CandlManager`
34//!
35//! When dealing with multiple windows in a single application, it quickly
36//! become complex and error prone. You can only use one OpenGL context at a
37//! time, and must so you need to swap between each contexts when you update
38//! what you display. With `CandlManager`, you have what you need to help you
39//! in this tedious task. It take the responsability to make the swap for you,
40//! and track each window you link to it.
41//!
42//! ## `CandlWindow`
43//!
44//! This trait was added to let developpers create their own implementation of
45//! `CandlSurface`, like using luminance to handle the OpenGL context, or get
46//! rid of the stateful capability of the `CandlSurface`. If an application
47//! need a simple way to handle a window, maybe the `CandlWindow` is the right
48//! tool to do it.
49//!
50//! ## About state in `CandlSurface` and `CandlManager`
51//!
52//! It's possible to add state into the `CandlSurface` and the `CandlManager`.
53//! The purpose of this state is to handle all the OpenGL data in a structure
54//! inside the surface, and enable a direct access between the renderer and the
55//! state of the surface. In the examples, it works a little bit like a FRP,
56//! with the state acting like a store to handle a model, pass to the renderer.
57//! Take a look.
58
59#![deny(missing_docs)]
60
61use gl;
62pub use glutin;
63use glutin::{
64 Api, ContextBuilder, GlProfile, GlRequest, NotCurrent,
65 PossiblyCurrent, WindowedContext
66};
67use glutin::{ContextError, CreationError};
68use glutin::dpi::{LogicalSize, PhysicalSize};
69use glutin::event_loop::EventLoopWindowTarget;
70use glutin::monitor::VideoMode;
71use glutin::window::{Fullscreen, WindowBuilder, Window, WindowId};
72use std::collections::HashMap;
73use std::fmt;
74use std::marker::PhantomData;
75use std::os::raw::c_void;
76
77
78// =======================================================================
79// =======================================================================
80// CandlRenderer & CandlUpdate
81// =======================================================================
82// =======================================================================
83
84/// Renderer Trait
85///
86/// This trait must be used by any structure which want to fill the gap between
87/// a `CandlWindow` and OpenGL.
88pub trait CandlRenderer<R, S: CandlUpdate<M>, M> {
89 /// init the renderer
90 fn init() -> R;
91
92 /// call from `CandlSurface` after the gl initialization
93 fn finalize(&mut self);
94
95 /// set the scale factor when it changed
96 fn set_scale_factor(&mut self, scale_factor: f64);
97
98 /// set the size of the window / surface holding the OpenGL context
99 fn set_size(&mut self, nsize: (u32, u32));
100
101 /// call for redraw the current OpenGL context
102 fn draw_frame(&mut self, state: &S);
103}
104
105/// Update Trait
106///
107/// When a surface become stateful, there is a way to do it, and it goes with
108/// this trait. It's the bridge between the state and the renderer, as it force
109/// the state handler to define an update method which then can be use by the
110/// surface.
111pub trait CandlUpdate<M> {
112 /// the state handler of
113 fn update(&mut self, message: M);
114}
115
116// =======================================================================
117// =======================================================================
118// Candelabre windowing utilities
119// =======================================================================
120// =======================================================================
121
122
123/// The error of Candelabre Windowing
124///
125/// All the possible errors you can meet with this crate are from this type.
126/// One type to rule them all, one type to find them.
127#[derive(Debug)]
128pub enum CandlError {
129 /// OpenGL context creation error
130 CreationError(CreationError),
131 /// OpenGL context usage error
132 ContextError(ContextError),
133 /// Candelabre internal error
134 InternalError(&'static str)
135}
136
137impl fmt::Display for CandlError {
138 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
139 match *self {
140 CandlError::CreationError(ref e) =>
141 write!(f, "Candelabre Surface creation error: {}", e),
142 CandlError::ContextError(ref e) =>
143 write!(f, "OpenGL context usage error: {}", e),
144 CandlError::InternalError(e) =>
145 write!(f, "Candelabre internal error: {}", e)
146 }
147 }
148}
149
150impl From<CreationError> for CandlError {
151 fn from(e: CreationError) -> Self { CandlError::CreationError(e) }
152}
153
154impl From<ContextError> for CandlError {
155 fn from(e: ContextError) -> Self { CandlError::ContextError(e) }
156}
157
158/// Window dimensions
159///
160/// This type is an extract from
161/// [luminance-windowing](https://docs.rs/luminance-windowing/0.8.1/luminance_windowing/)
162/// to avoid the call of this crate and separate a little bit more luminance from
163/// candelabre. The idea is to maybe be able to use candelabre without luminance.
164#[derive(Clone, Copy, Debug, Eq, PartialEq)]
165pub enum CandlDimension {
166 /// cassic windowed mode
167 Classic(u32, u32),
168 /// fullscreen (for only one monitor)
169 Fullscreen,
170 /// fullscreen mode but with specific dimensions
171 FullscreenSpecific(u32, u32)
172}
173
174/// Cursor mode
175///
176/// This type is an extract from
177/// [luminance-windowing](https://docs.rs/luminance-windowing/0.8.1/luminance_windowing/)
178/// simplify to better match glutin cursor visibility.
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180pub enum CursorMode {
181 /// cursor visible
182 Visible,
183 /// cursor invisible
184 Invisible
185}
186
187/// Window options
188///
189/// This type is an extract from
190/// [luminance-windowing](https://docs.rs/luminance-windowing/0.8.1/luminance_windowing/)
191#[derive(Clone, Copy, Debug, Eq, PartialEq)]
192pub struct CandlOptions {
193 cursor_mode: CursorMode,
194 decorations: bool,
195 max_size: Option<(u32, u32)>,
196 min_size: Option<(u32, u32)>,
197 on_top: bool,
198 samples: Option<u32>,
199 transparent: bool,
200 vsync: bool
201}
202
203impl Default for CandlOptions {
204 /// Default:
205 ///
206 /// Default options for a window, with cursor visible
207 fn default() -> Self {
208 CandlOptions {
209 cursor_mode: CursorMode::Visible,
210 decorations: true,
211 max_size: None,
212 min_size: None,
213 on_top: false,
214 samples: None,
215 transparent: false,
216 vsync: false
217 }
218 }
219}
220
221impl CandlOptions {
222 /// get the cursor current visiblity
223 pub fn cursor_mode(&self) -> CursorMode { self.cursor_mode }
224
225 /// choose the cursor visibility
226 pub fn set_cursor_mode(self, cursor_mode: CursorMode) -> Self {
227 Self { cursor_mode, ..self }
228 }
229
230 /// get the window current decoration status (default true)
231 pub fn decorations(&self) -> bool { self.decorations }
232
233 /// set if the window use decoration
234 pub fn set_decorations(self, decorations: bool) -> Self {
235 Self { decorations, ..self }
236 }
237
238 /// get the maximal size, if set, or none otherwise
239 pub fn max_size(&self) -> Option<(u32, u32)> { self.max_size }
240
241 /// set the maximal size of the window
242 pub fn set_maw_size(self, max_size: Option<(u32, u32)>) -> Self {
243 Self { max_size, ..self }
244 }
245
246 /// get the minimal size, if set, or none otherwise
247 pub fn min_size(&self) -> Option<(u32, u32)> { self.min_size }
248
249 /// set the minimal size of the window
250 pub fn set_min_size(self, min_size: Option<(u32, u32)>) -> Self {
251 Self { min_size, ..self }
252 }
253
254 /// get if the window must be always on top, or not
255 pub fn on_top(&self) -> bool { self.on_top }
256
257 /// set if the window is always on top or not
258 pub fn set_on_top(self, on_top: bool) -> Self {
259 Self { on_top, ..self }
260 }
261
262 /// get the number of samples for multisampling
263 pub fn samples(&self) -> Option<u32> { self.samples }
264
265 /// choose the number of samples for multisampling
266 pub fn set_samples<S: Into<Option<u32>>>(self, samples: S) -> Self {
267 Self { samples: samples.into(), ..self }
268 }
269
270 /// get if the window can be transparent (default false)
271 pub fn transparent(&self) -> bool { self.transparent }
272
273 /// set the window transparency
274 pub fn set_transparent(self, transparent: bool) -> Self {
275 Self { transparent, ..self }
276 }
277
278 /// get actual vsync configuration
279 pub fn vsync(&self) -> bool { self.vsync }
280
281 /// set the vsync (false by default)
282 pub fn set_vsync(self, vsync: bool) -> Self {
283 Self { vsync, ..self }
284 }
285}
286
287/// Tracking the context status
288///
289/// When working with OpenGL context it's important to know if the context you
290/// working with is the current one or not. If you're using only one window,
291/// it's ok to avoid this enum and only use `PossiblyCurrent`, because the
292/// context status will never change. But if you need multiple windows, you
293/// need to know if the context you want to work with is the current one, and
294/// if not you need to change that. The `CandlManager` is here to do that for
295/// you, and use `CandlCurrentWrapper` to do so.
296#[derive(Debug)]
297pub enum CandlCurrentWrapper {
298 /// OpenGL context is probably current
299 PossiblyCurrent(WindowedContext<PossiblyCurrent>),
300 /// OpenGL context is not current
301 NotCurrent(WindowedContext<NotCurrent>)
302}
303
304/// No state
305///
306/// This object has only one goal: to handle the case we don't want to have a
307/// state for the `CandlSurface`
308pub struct CandlNoState {}
309
310impl CandlUpdate<()> for CandlNoState {
311 fn update(&mut self, _: ()) {}
312}
313
314// =======================================================================
315// =======================================================================
316// CandlWindow
317// =======================================================================
318// =======================================================================
319
320/// Window trait
321///
322/// This trait can be used to create a new kind of `CandlSurface`, with a
323/// deeper connection with your code.
324pub trait CandlWindow {
325 /// code to init the basis of a window with an OpenGL context
326 fn init<T>(
327 el: &EventLoopWindowTarget<T>,
328 video_mode: VideoMode,
329 dim: CandlDimension,
330 title: &str,
331 options: CandlOptions
332 ) -> Result<WindowedContext<PossiblyCurrent>, CandlError> where T: 'static {
333 let mut win_builder = WindowBuilder::new()
334 .with_title(title)
335 .with_transparent(options.transparent())
336 .with_decorations(options.decorations())
337 .with_always_on_top(options.on_top());
338 if let Some((w, h)) = options.max_size() {
339 win_builder = win_builder.with_max_inner_size(LogicalSize::new(w, h));
340 }
341 if let Some((w, h)) = options.min_size() {
342 win_builder = win_builder.with_min_inner_size(LogicalSize::new(w, h));
343 }
344 win_builder = match dim {
345 CandlDimension::Classic(w, h) =>
346 win_builder.with_inner_size(LogicalSize::new(w, h)),
347 CandlDimension::Fullscreen =>
348 win_builder.with_fullscreen(
349 Some(Fullscreen::Exclusive(video_mode))
350 ),
351 CandlDimension::FullscreenSpecific(w, h) =>
352 win_builder.with_inner_size(LogicalSize::new(w, h))
353 .with_fullscreen(
354 Some(Fullscreen::Exclusive(video_mode))
355 )
356 };
357 let ctx = ContextBuilder::new()
358 .with_gl(GlRequest::Specific(Api::OpenGl, (3, 3)))
359 .with_gl_profile(GlProfile::Core)
360 .with_vsync(options.vsync())
361 .with_multisampling(options.samples().unwrap_or(0) as u16)
362 .with_double_buffer(Some(true))
363 .build_windowed(win_builder, &el)?;
364 let ctx = unsafe { ctx.make_current().map_err(|(_, e)| e)? };
365 ctx.window().set_cursor_visible(match options.cursor_mode() {
366 CursorMode::Visible => true,
367 CursorMode::Invisible => false
368 });
369 gl::load_with(|s| ctx.get_proc_address(s) as *const c_void);
370 Ok(ctx)
371 }
372
373 /// get the OpenGL context wrapper
374 fn ctx(&mut self) -> CandlCurrentWrapper;
375
376 /// get a reference to the OpenGL context wrapper
377 fn ctx_ref(&self) -> &CandlCurrentWrapper;
378
379 /// change the OpenGL context
380 fn set_ctx(&mut self, nctx: CandlCurrentWrapper);
381
382 /// handle resize event
383 fn resize(&mut self, nsize: PhysicalSize<u32>);
384
385 /// swap the buffer
386 fn swap_buffers(&mut self);
387}
388
389// =======================================================================
390// =======================================================================
391// CandlSurfaceBuilder
392// =======================================================================
393// =======================================================================
394
395/// Surface builder
396///
397/// This builder help create a new `CandlSurface` in a more idiomatic way
398pub struct CandlSurfaceBuilder<'a, R, D, M>
399where R: CandlRenderer<R, D, M>, D: CandlUpdate<M> {
400 dim: CandlDimension,
401 title: &'a str,
402 options: CandlOptions,
403 render: Option<R>,
404 state: Option<D>,
405 video_mode: Option<VideoMode>,
406 message: PhantomData<M>
407}
408
409impl<R> CandlSurfaceBuilder<'_, R, CandlNoState, ()>
410where R: CandlRenderer<R, CandlNoState, ()> {
411 /// specify to not use state for the surface
412 pub fn no_state(self) -> Self {
413 Self {state: Some(CandlNoState {}), ..self}
414 }
415}
416
417impl<'a, R, D, M> CandlSurfaceBuilder<'a, R, D, M>
418where R: CandlRenderer<R, D, M>, D: CandlUpdate<M> {
419 /// builder constructor
420 ///
421 /// By default, the builder set the window dimension to Classic(800, 400)
422 /// and with no name
423 pub fn new() -> Self {
424 CandlSurfaceBuilder {
425 dim: CandlDimension::Classic(800, 400),
426 title: "",
427 options: CandlOptions::default(),
428 render: None,
429 state: None,
430 video_mode: None,
431 message: PhantomData
432 }
433 }
434
435 /// modify the starting dimension
436 pub fn dim(self, dim: CandlDimension) -> Self { Self {dim, ..self} }
437
438 /// set a title ("" by default)
439 pub fn title(self, title: &'a str) -> Self { Self {title, ..self} }
440
441 /// modify the options
442 pub fn options(self, options: CandlOptions) -> Self { Self {options, ..self} }
443
444 /// set render object
445 pub fn render(self, render: R) -> Self {
446 Self {render: Some(render), ..self}
447 }
448
449 /// change the initial state
450 pub fn state(self, init_state: D) -> Self {
451 Self {state: Some(init_state), ..self}
452 }
453
454 /// set the video mode for the window (from the monitor handle)
455 pub fn video_mode(self, video_mode: VideoMode) -> Self {
456 Self {video_mode: Some(video_mode), ..self}
457 }
458
459 /// try to build the surface
460 pub fn build<T>(self, el: &EventLoopWindowTarget<T>) -> Result<CandlSurface<R, D, M>, CandlError> {
461 match (self.render, self.state, self.video_mode) {
462 (None, None, _) =>
463 Err(CandlError::InternalError("You must specify the renderer and the state!")),
464 (None, Some(_), _) =>
465 Err(CandlError::InternalError("You must specify the renderer!")),
466 (Some(_), None, _) =>
467 Err(CandlError::InternalError("You must specify the state! (use 'nostate'?)")),
468 (_, _, None) =>
469 Err(CandlError::InternalError("Please specify the video mode for the window")),
470 (Some(render), Some(state), Some(video_mode)) =>
471 CandlSurface::window_builder(
472 el,
473 video_mode,
474 self.dim,
475 self.title,
476 self.options,
477 render,
478 state
479 )
480 }
481 }
482}
483
484// =======================================================================
485// =======================================================================
486// CandlSurface
487// =======================================================================
488// =======================================================================
489
490/// The display surface
491///
492/// The first core element of this crate, the CandlSurface is a window with an
493/// OpenGL context, and some options. It sounds very simple, and in fact it is.
494/// Look for the example to see how to use it.
495///
496/// A state type can be associated to the surface, to make it stateful. It
497/// isn't mandatory, but useful.
498///
499/// The basic constructor automatically associate the type `CandlNoState` to
500/// the state type of the surface, and a second constructor called
501/// `new_with_state()` is here to let the advanced user specify the state type
502/// and the initial state associated with the surface.
503#[derive(Debug)]
504pub struct CandlSurface<R, D, M>
505where R: CandlRenderer<R, D, M>, D: CandlUpdate<M> {
506 ctx: Option<CandlCurrentWrapper>,
507 render: R,
508 state: D,
509 message: PhantomData<M>,
510 redraw: bool
511}
512
513impl<R, D, M> CandlWindow for CandlSurface<R, D, M>
514where R: CandlRenderer<R, D, M>, D: CandlUpdate<M> {
515 /// get the OpenGL context from the surface
516 fn ctx(&mut self) -> CandlCurrentWrapper { self.ctx.take().unwrap() }
517
518 /// get the reference to the OpenGL context
519 fn ctx_ref(&self) -> &CandlCurrentWrapper { self.ctx.as_ref().unwrap() }
520
521 /// change the OpenGL context (make current or not current)
522 fn set_ctx(&mut self, nctx: CandlCurrentWrapper) { self.ctx = Some(nctx); }
523
524 /// swap the OpenGL back buffer and current buffer
525 fn swap_buffers(&mut self) {
526 if let CandlCurrentWrapper::PossiblyCurrent(ctx) = self.ctx.as_ref().unwrap() {
527 ctx.swap_buffers().unwrap();
528 }
529 }
530
531 /// handle resize event
532 fn resize(&mut self, nsize: PhysicalSize<u32>) {
533 if let CandlCurrentWrapper::PossiblyCurrent(ctx) = &self.ctx_ref() {
534 ctx.resize(nsize);
535 self.render.set_size((nsize.width, nsize.height));
536 }
537 }
538}
539
540impl<'a, R> CandlElement<CandlSurface<R, CandlNoState, ()>> for CandlSurface<R, CandlNoState, ()>
541where R: CandlRenderer<R, CandlNoState, ()> {
542 /// build method to make `CandlSurface` compatible with the `CandlManager`
543 ///
544 /// WARNING: avoid at all cost the use of this method, prefer the builder
545 /// instead.
546 fn build<T>(
547 el: &EventLoopWindowTarget<T>,
548 video_mode: VideoMode,
549 dim: CandlDimension,
550 title: &str,
551 options: CandlOptions
552 ) -> Result<CandlSurface<R, CandlNoState, ()>, CandlError> {
553 <CandlSurface<R, CandlNoState, ()>>::window_builder(
554 el, video_mode, dim, title, options, R::init(), CandlNoState {}
555 )
556 }
557}
558
559impl<'a, R> CandlSurface<R, CandlNoState, ()>
560where R: CandlRenderer<R, CandlNoState, ()> {
561 /// standard creation of a CandlSurface
562 pub fn new<T>(
563 el: &EventLoopWindowTarget<T>,
564 video_mode: VideoMode,
565 dim: CandlDimension,
566 title: &str,
567 options: CandlOptions,
568 render: R
569 ) -> Result<Self, CandlError> {
570 CandlSurface::window_builder(
571 el,
572 video_mode,
573 dim,
574 title,
575 options,
576 render,
577 CandlNoState {}
578 )
579 }
580}
581
582impl<'a, R, D, M> CandlSurface<R, D, M>
583where R: CandlRenderer<R, D, M>, D: CandlUpdate<M> {
584 /// constructor with state
585 ///
586 /// This constructor can be used to associate a state type to the window.
587 /// The state type must be specified.
588 pub fn new_with_state<T>(
589 el: &EventLoopWindowTarget<T>,
590 video_mode: VideoMode,
591 dim: CandlDimension,
592 title: &str,
593 options: CandlOptions,
594 render: R,
595 init_state: D
596 ) -> Result<Self, CandlError> {
597 CandlSurface::window_builder(
598 el,
599 video_mode,
600 dim,
601 title,
602 options,
603 render,
604 init_state
605 )
606 }
607
608 /// internal builder for the window
609 fn window_builder<T>(
610 el: &EventLoopWindowTarget<T>,
611 video_mode: VideoMode,
612 dim: CandlDimension,
613 title: &str,
614 options: CandlOptions,
615 mut render: R,
616 init_state: D
617 ) -> Result<Self, CandlError> {
618 let ctx = <CandlSurface<R, D, M>>::init(el, video_mode, dim, title, options)?;
619 render.set_scale_factor(ctx.window().scale_factor());
620 let ipsize = ctx.window().inner_size();
621 render.set_size((ipsize.width, ipsize.height));
622 let ctx = Some(CandlCurrentWrapper::PossiblyCurrent(ctx));
623 render.finalize();
624 Ok(CandlSurface {
625 ctx,
626 render,
627 state: init_state,
628 message: PhantomData,
629 redraw: false
630 })
631 }
632
633 /// change the title of the window
634 pub fn title(&mut self, new_title: &str) {
635 match self.ctx.as_ref().unwrap() {
636 CandlCurrentWrapper::PossiblyCurrent(ctx) =>
637 ctx.window().set_title(new_title),
638 CandlCurrentWrapper::NotCurrent(ctx) =>
639 ctx.window().set_title(new_title)
640 };
641 }
642
643 /// get the render object (immutable way)
644 pub fn render(&self) -> &R { &self.render }
645
646 /// get the render object (mutable way)
647 pub fn render_mut(&mut self) -> &mut R { &mut self.render }
648
649 /// get the state as a immutable reference
650 pub fn state(&self) -> &D { &self.state }
651
652 /// get the state as a mutable reference
653 pub fn state_mut(&mut self) -> &mut D { &mut self.state }
654
655 /// call the update method of the state (mandatory by CandlUpdate trait)
656 pub fn update(&mut self, message: M) {
657 self.state.update(message);
658 }
659
660 /// requesting the window to handle a redraw
661 pub fn ask_redraw(&mut self) { if !self.redraw { self.redraw = true; } }
662
663 /// check if redraw is requested
664 pub fn check_redraw(&self) -> bool { self.redraw.clone() }
665
666 /// requesting redraw for the window
667 pub fn request_redraw(&mut self) {
668 match self.ctx.as_ref().unwrap() {
669 CandlCurrentWrapper::PossiblyCurrent(ctx) =>
670 ctx.window().request_redraw(),
671 CandlCurrentWrapper::NotCurrent(_) => ()
672 }
673 self.redraw = false;
674 }
675
676 /// draw on the surface
677 pub fn draw(&mut self) {
678 //
679 self.render.draw_frame(&self.state);
680 //
681 self.swap_buffers();
682 }
683}
684
685impl<R, D, M> CandlSurface<R, D, M>
686where R: CandlRenderer<R, D, M>, D: CandlUpdate<M> {
687 /// get the window linked to the surface OpenGL context
688 pub fn get_window(&self) -> Result<&Window, CandlError> {
689 match self.ctx_ref() {
690 CandlCurrentWrapper::PossiblyCurrent(ctx) => Ok(ctx.window()),
691 CandlCurrentWrapper::NotCurrent(_) =>
692 Err(CandlError::InternalError("The context of this surface is not the current context"))
693 }
694 }
695}
696
697// =======================================================================
698// =======================================================================
699// CandlManager
700// =======================================================================
701// =======================================================================
702
703/// `CandlElement` trait
704///
705/// This trait's purpose is to enable using `CandlWindow` in the manager
706/// without too many boilerplate in the surface side. With this code it's easy
707/// to just implement both `CandlWindow` and `CandlElement` traits on a single
708/// type to make it usable with the `CandlManager`.
709pub trait CandlElement<W: CandlWindow> {
710 /// to use `CandlWindow` with `CandlManager` the type implementing this
711 /// trait must implement the window_builder method
712 fn build<T>(
713 el: &EventLoopWindowTarget<T>,
714 video_mode: VideoMode,
715 dim: CandlDimension,
716 title: &str,
717 options: CandlOptions) -> Result<W, CandlError>;
718}
719
720/// The window manager
721///
722/// Second core element of this lib, the `CandlManager` is the tool to bring
723/// all your app's windows under its command. It's main purpose is to remove
724/// the burden of OpenGL contexts swapping, and a way to easily manage multiple
725/// windows in a application. Its usage is pretty simple:
726///
727/// 1. create it
728/// 2. insert new window in it
729/// 3. call `get_current()` to swap contexts and get the window you can work with
730/// 4. done
731///
732/// Check the
733/// [candelabre examples](https://github.com/othelarian/candelabre/tree/master/candelabre-examples)
734/// to see it in action.
735pub struct CandlManager<W: CandlWindow, S> {
736//pub struct CandlManager<D, M> {
737 current: Option<WindowId>,
738 surfaces: HashMap<WindowId, Option<W>>,
739 state: S
740}
741
742impl<W: CandlWindow> CandlManager<W, ()> {
743 /// most default constructor for the manager
744 pub fn new() -> Self {
745 CandlManager { current: None, surfaces: HashMap::default(), state: () }
746 }
747}
748
749impl<R, D, M, S> CandlManager<CandlSurface<R, D, M>, S>
750where R: CandlRenderer<R, D, M>, D: CandlUpdate<M> {
751 /// create a new window from a CandlSurfaceBuilder
752 pub fn create_window_from_builder<T>(
753 &mut self,
754 builder: CandlSurfaceBuilder<R, D, M>,
755 el: &EventLoopWindowTarget<T>
756 ) -> Result<WindowId, CandlError> {
757 let surface = builder.build(el)?;
758 self.add_window(surface)
759 }
760
761 /// create a new window with surface associated state type
762 pub fn create_window_with_state<T>(
763 &mut self,
764 el: &EventLoopWindowTarget<T>,
765 video_mode: VideoMode,
766 dim: CandlDimension,
767 title: &str,
768 options: CandlOptions,
769 render: R,
770 init_state: D
771 ) -> Result<WindowId, CandlError> {
772 let surface = CandlSurface::window_builder(el, video_mode, dim, title, options, render, init_state)?;
773 self.add_window(surface)
774 }
775}
776
777impl<W: CandlWindow, S> CandlManager<W, S> {
778 /// create a new window, tracked by the manager
779 ///
780 /// For internal reason, it isn't possible to add a `CandlSurface` manually
781 /// created to the manager, it's mandatory to use the `create_window()`
782 /// method instead.
783 ///
784 /// This method is the most basic one, creating a surface with no state
785 /// associated.
786 ///
787 /// WARNING : the first surface created with the manager decide of all the
788 /// surfaces state type of the manager, it isn't in the scope of this lib to
789 /// handle the complexity of multiple state type across an hashmap of
790 /// surfaces.
791 pub fn create_window<T, E: CandlElement<W>>(
792 &mut self,
793 el: &EventLoopWindowTarget<T>,
794 video_mode: VideoMode,
795 dim: CandlDimension,
796 title: &str,
797 options: CandlOptions,
798 ) -> Result<WindowId, CandlError> {
799 let surface = E::build(el, video_mode, dim, title, options)?;
800 self.add_window(surface)
801 }
802
803 /// constructor for the manager with state type link to it
804 pub fn new_with_state(init_state: S) -> Self {
805 CandlManager {
806 current: None,
807 surfaces: HashMap::default(),
808 state: init_state
809 }
810 }
811
812 /// internal method to truly add the new window
813 fn add_window(&mut self, mut surface: W) -> Result<WindowId, CandlError> {
814 let surface_ctx = surface.ctx();
815 match &surface_ctx {
816 CandlCurrentWrapper::PossiblyCurrent(ctx) => {
817 let win_id = ctx.window().id();
818 if let Some(old_id) = self.current.take() {
819 if let Some(old_surface) = self.surfaces.get_mut(&old_id) {
820 let mut old_win = old_surface.take().unwrap();
821 let ctx_wrapper = old_win.ctx();
822 if let CandlCurrentWrapper::PossiblyCurrent(ctx) = ctx_wrapper {
823 let nctx = unsafe { ctx.treat_as_not_current() };
824 old_win.set_ctx(CandlCurrentWrapper::NotCurrent(nctx));
825 } else {
826 old_win.set_ctx(ctx_wrapper);
827 }
828 old_surface.replace(old_win);
829 }
830 }
831 surface.set_ctx(surface_ctx);
832 self.surfaces.insert(win_id, Some(surface));
833 self.current = Some(win_id);
834 Ok(win_id)
835 }
836 CandlCurrentWrapper::NotCurrent(_) => {
837 Err(CandlError::InternalError(
838 "Surface creation from manager generated a not current context"
839 ))
840 }
841 }
842 }
843
844 /// vector with all the WindowId managed by the CandlManager
845 pub fn list_window_ids(&self) -> Vec<WindowId> { self.surfaces.keys().cloned().collect() }
846
847 /// remove a window from the manager
848 ///
849 /// If you don't call this method after closing a window, the OpenGL
850 /// context continue to exist, and can lead to memory leaks.
851 pub fn remove_window(&mut self, id: WindowId) {
852 if Some(id) == self.current { self.current.take(); }
853 self.surfaces.remove(&id);
854 }
855
856 /// check if there is still living windows, or if the manager is empty
857 ///
858 /// The purpose of this method is to check if the application can be close,
859 /// from an OpenGL perspective.
860 pub fn is_empty(&self) -> bool { self.surfaces.is_empty() }
861
862 /// get a mutable reference to the current surface
863 ///
864 /// This method is the most important of the manager. At first, there is a
865 /// check for the asked window to see if it's the current one, and if not
866 /// the method try to swap the OpenGL contexts to make the asked window
867 /// current, and make the old current context not current.
868 pub fn get_current(&mut self, id: WindowId)
869 -> Result<&mut W, CandlError> {
870 let res = if Some(id) != self.current {
871 let ncurr_ref = self.surfaces.get_mut(&id).unwrap();
872 let mut ncurr_surface = ncurr_ref.take().unwrap();
873 let nctx_wrapper = ncurr_surface.ctx();
874 match nctx_wrapper {
875 CandlCurrentWrapper::PossiblyCurrent(nctx) => {
876 ncurr_surface.set_ctx(CandlCurrentWrapper::PossiblyCurrent(nctx));
877 ncurr_ref.replace(ncurr_surface);
878 Ok(())
879 }
880 CandlCurrentWrapper::NotCurrent(nctx) => unsafe {
881 match nctx.make_current() {
882 Err((rctx, err)) => {
883 match rctx.make_not_current() {
884 Ok(rctx) => {
885 ncurr_surface.set_ctx(CandlCurrentWrapper::NotCurrent(rctx));
886 ncurr_ref.replace(ncurr_surface);
887 Err(CandlError::from(err))
888 }
889 Err((_, err2)) => {
890 panic!("Couldn't make current and not current: {}, {}", err, err2);
891 }
892 }
893 }
894 Ok(rctx) => {
895 ncurr_surface.set_ctx(CandlCurrentWrapper::PossiblyCurrent(rctx));
896 ncurr_ref.replace(ncurr_surface);
897 Ok(())
898 }
899 }
900 }
901 }
902 }
903 else {
904 let ncurr_ref = self.surfaces.get_mut(&id).unwrap();
905 let ncurr_surface = ncurr_ref.take().unwrap();
906 match ncurr_surface.ctx_ref() {
907 CandlCurrentWrapper::PossiblyCurrent(_) => {
908 ncurr_ref.replace(ncurr_surface);
909 Ok(())
910 }
911 CandlCurrentWrapper::NotCurrent(_) => panic!()
912 }
913 };
914 match res {
915 Ok(()) => {
916 if Some(id) != self.current {
917 if let Some(old_id) = self.current.take() {
918 let old_ref = self.surfaces.get_mut(&old_id).unwrap();
919 let mut old_surface = old_ref.take().unwrap();
920 let octx_wrapper = old_surface.ctx();
921 let noctx = match octx_wrapper {
922 CandlCurrentWrapper::PossiblyCurrent(octx) =>
923 unsafe { octx.treat_as_not_current() },
924 CandlCurrentWrapper::NotCurrent(octx) => octx
925 };
926 old_surface.set_ctx(CandlCurrentWrapper::NotCurrent(noctx));
927 old_ref.replace(old_surface);
928 }
929 self.current = Some(id);
930 }
931 Ok(self.surfaces.get_mut(&id).unwrap().as_mut().unwrap())
932 }
933 Err(err) => {
934 if let Some(old_id) = self.current.take() {
935 let old_ref = self.surfaces.get_mut(&old_id).unwrap();
936 let mut old_surface = old_ref.take().unwrap();
937 let octx_wrapper = old_surface.ctx();
938 if let CandlCurrentWrapper::PossiblyCurrent(octx) = octx_wrapper {
939 unsafe {
940 match octx.make_not_current() {
941 Err((_, err2)) =>
942 panic!("make current and make not current panic: {}, {}", err, err2),
943 Ok(octx) =>
944 old_surface.set_ctx(CandlCurrentWrapper::NotCurrent(octx))
945 }
946 }
947 }
948 old_ref.replace(old_surface);
949 }
950 Err(err)
951 }
952 }
953 }
954
955 /// get the state from the manager as an immutable reference
956 pub fn state(&self) -> &S { &self.state }
957
958 /// get the state from the manager as a mutable reference
959 pub fn state_mut(&mut self) -> &mut S { &mut self.state }
960}