Skip to main content

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}