Skip to main content

cat_engine/window/
window_base.rs

1use crate::graphics::{
2    Graphics,
3    GraphicsSettings,
4    two_dimensions::Graphics2D,
5};
6
7#[cfg(feature="3D")]
8use crate::graphics::three_dimensions::Graphics3D;
9
10#[cfg(feature="fps_counter")]
11use super::fps;
12
13#[cfg(feature="ups_counter")]
14use super::ups;
15
16use super::{
17    // statics
18    window_width,
19    window_height,
20    window_center,
21    // enums
22    InnerWindowEvent,
23    // structs
24    GeneralSettings,
25};
26
27use glium::{
28    Display,
29    Surface,
30    Frame,
31    Version,
32    draw_parameters::{
33        DrawParameters,
34        Blend,
35        BlendingFunction,
36        LinearBlendingFactor,
37        BackfaceCullingMode,
38    },
39    texture::RawImage2d,
40    backend::glutin::DisplayCreationError,
41    SwapBuffersError,
42};
43
44use glium::glutin::{
45    ContextBuilder,
46    NotCurrent,
47    event_loop::{EventLoop,EventLoopClosed,EventLoopProxy},
48    window::WindowBuilder,
49};
50
51use image::{
52    ImageFormat,
53    ImageBuffer,
54    DynamicImage
55};
56
57use std::{
58    path::Path,
59    time::{Instant,Duration}
60};
61
62/// Основа для окон для создания более сложных окон.
63/// A window base for creating more complex windows.
64pub struct WindowBase{
65    /// A window with a GL context.
66    pub display:Display,
67
68    /// An event loop.
69    pub event_loop:EventLoop<InnerWindowEvent>,
70
71    /// Used to send custom events to the event loop.
72    pub event_loop_proxy:EventLoopProxy<InnerWindowEvent>,
73
74    /// feature != "lazy"
75    #[cfg(not(feature="lazy"))]
76    pub update_interval:Duration,
77    /// feature != "lazy"
78    #[cfg(not(feature="lazy"))]
79    pub next_update:Instant,
80
81    /// feature = "fps_counter"
82    #[cfg(feature="fps_counter")]
83    pub frames_passed:u32,
84    /// feature = "ups_counter"
85    #[cfg(feature="ups_counter")]
86    pub updates_passed:u32,
87    /// feature = "fps_counter"
88    #[cfg(any(feature="fps_counter",feature="ups_counter"))]
89    pub time:Instant,
90}
91
92impl WindowBase{
93    pub fn raw(
94        window_builder:WindowBuilder,
95        context_builder:ContextBuilder<NotCurrent>,
96        graphics_settings:GraphicsSettings,
97        event_loop:EventLoop<InnerWindowEvent>,
98        general_settings:GeneralSettings,
99    )->Result<(WindowBase,Graphics2D),DisplayCreationError>{
100        // Создание окна и привязывание графической библиотеки
101        let display=Display::new(window_builder,context_builder,&event_loop)?;
102
103        let size=display.gl_window().window().inner_size();
104        unsafe{
105            window_width=size.width as f32;
106            window_height=size.height as f32;
107            window_center=[window_width/2f32,window_height/2f32];
108        }
109
110        // Опреление поддерживаемой версии GLSL
111        let Version(..,m,l)=display.get_supported_glsl_version();
112        let glsl=match m{
113            1 if l<3 =>{
114                120
115            }
116            _=>{
117                140
118            }
119        };
120
121        if let Some([r,g,b,a])=general_settings.initial_colour{
122            let mut frame=display.draw();   //
123            frame.clear_color(r,g,b,a);     // Заполнение окна
124            frame.finish().unwrap();        //
125        }
126
127        let graphics2d=Graphics2D::new(&display,graphics_settings,glsl);
128
129        let proxy=event_loop.create_proxy();
130
131        #[cfg(not(feature="lazy"))]
132        let update_interval=Duration::from_secs(1).checked_div(general_settings.updates_per_second).expect("UPD = 0");
133        // #[cfg(not(feature="lazy"))]
134        // println!("{:?}",update_interval);
135
136        Ok((
137            Self{
138                display,
139
140                event_loop,
141                event_loop_proxy:proxy,
142
143                #[cfg(not(feature="lazy"))]
144                update_interval,
145                #[cfg(not(feature="lazy"))]
146                next_update:Instant::now(),
147
148                #[cfg(feature="fps_counter")]
149                frames_passed:0u32,
150                #[cfg(feature="ups_counter")]
151                updates_passed:0u32,
152                #[cfg(any(feature="fps_counter",feature="ups_counter"))]
153                time:Instant::now(),
154            },
155            graphics2d,
156        ))
157    }
158
159    /// Останавливает цикл событий,
160    /// отправляя событие для остановки.
161    /// 
162    /// Возвращает `Err`, если цикл уже остановлен.
163    /// 
164    /// Stops the event loop
165    /// by sending the stopping event.
166    /// 
167    /// Returns `Err` if the loop has been already stopped.
168    #[inline(always)]
169    pub fn request_event_loop_close(&self)->Result<(),EventLoopClosed<InnerWindowEvent>>{
170        self.event_loop_proxy.send_event(InnerWindowEvent::EventLoopCloseRequested)
171    }
172}
173
174/// Функции для рисования. Drawing functions.
175impl WindowBase{
176    /// Выполняет замыкание.
177    /// 
178    /// Executes the closure.
179    pub fn draw<F:FnOnce(&mut Graphics<Frame>)>(&self,graphics_base:&Graphics2D,f:F)->Result<(),SwapBuffersError>{
180        let mut draw_parameters=default_draw_parameters();
181
182        let mut frame=self.display.draw();
183
184        let mut g=Graphics::new(
185            graphics_base,
186            draw_parameters,
187            &mut frame
188        );
189
190        f(&mut g);
191
192        frame.finish()
193    }
194}
195
196/// # Дополнительные функции. Additional functions.
197impl WindowBase{
198    /// Возвращает скриншот.
199    /// 
200    /// Returns a screenshot.
201    pub fn screenshot(&self)->Option<DynamicImage>{
202        // Копирование буфера окна
203        let image:RawImage2d<u8>=match self.display.read_front_buffer(){
204            Ok(t)=>t,
205            Err(_)=>return Option::None
206        };
207        // Перевод в буфер изображения
208        let image=match ImageBuffer::from_raw(image.width,image.height,image.data.into_owned()){
209            Option::Some(i)=>i,
210            Option::None=>return Option::None
211        };
212        // Перевод в изображение
213        Some(DynamicImage::ImageRgba8(image).flipv())
214    }
215
216    /// Сохраняет скриншот в формате png.
217    /// 
218    /// Saves a screenshot in png format.
219    pub fn save_screenshot<P:AsRef<Path>>(&self,path:P){
220        // Копирование буфера окна
221        let image:RawImage2d<u8>=match self.display.read_front_buffer(){
222            Ok(t)=>t,
223            Err(_)=>return
224        };
225        // Перевод в буфер изображения
226        let image=match ImageBuffer::from_raw(image.width,image.height,image.data.into_owned()){
227            Option::Some(i)=>i,
228            Option::None=>return
229        };
230        // Перевод в изображение
231        let image=DynamicImage::ImageRgba8(image).flipv();
232        // Сохранение
233        if let Err(_)=image.save_with_format(path,ImageFormat::Png){
234            return
235        }
236    }
237}
238
239
240//                     \\
241//  ЛОКАЛЬНЫЕ ФУНКЦИИ  \\
242//                     \\
243impl WindowBase{
244    #[cfg(feature="fps_counter")]
245    pub (crate) fn count_fps(&mut self){
246        self.frames_passed+=1;
247    }
248
249    #[cfg(feature="ups_counter")]
250    pub (crate) fn count_ups(&mut self){
251        self.updates_passed+=1;
252    }
253
254    #[cfg(not(feature="lazy"))]
255    pub (crate) fn update_check(&mut self){
256        let now=Instant::now();
257        if self.next_update<=now{
258            self.event_loop_proxy
259                    .send_event(InnerWindowEvent::Update)
260                            .expect("Dead event loop");
261
262            self.next_update+=self.update_interval;
263        }
264    }
265
266    #[cfg(any(feature="fps_counter",feature="ups_counter"))]
267    pub fn check_counters(&mut self){
268        let current_time=Instant::now();
269        let time_passed=current_time.duration_since(self.time);
270
271        if Duration::from_secs(1)<time_passed{
272            #[cfg(feature="fps_counter")]
273            unsafe{
274                fps=self.frames_passed;
275                self.frames_passed=0;
276            }
277            #[cfg(feature="ups_counter")]
278            unsafe{
279                ups=self.updates_passed;
280                self.updates_passed=0;
281            }
282
283            self.time=current_time;
284        }
285    }
286}
287
288
289pub fn default_draw_parameters<'a>()->DrawParameters<'a>{
290    DrawParameters{
291        blend:Blend{
292            color:BlendingFunction::Addition{
293                source:LinearBlendingFactor::SourceAlpha,
294                destination:LinearBlendingFactor::OneMinusSourceAlpha,
295            },
296            alpha:BlendingFunction::Addition{
297                source:LinearBlendingFactor::One,
298                destination:LinearBlendingFactor::One,
299            },
300            constant_value:(1f32,1f32,1f32,1f32),
301        },
302
303        backface_culling:BackfaceCullingMode::CullingDisabled,
304
305        ..DrawParameters::default()
306    }
307}