1extern crate sdl2;
2extern crate tea;
3extern crate latte;
4
5pub mod audio;
6pub mod render;
7pub mod keyboard;
8pub mod mouse;
9pub mod joystick;
10
11use keyboard::Scancode;
12pub use tea::vec::*;
13use render::Render;
14use sdl2::{video::{Window, WindowBuilder, GLContext, GLProfile}, EventPump, event::{Event, WindowEvent}, TimerSubsystem, keyboard::{Keycode, Mod}};
15
16pub use sdl2::joystick::HatState;
17pub use sdl2::controller::{Axis, Button};
18
19pub type Point2D = Vec2;
20pub type Point3D = Vec3;
21
22#[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq)]
23pub struct Rect {
24 pub x: f32, pub y: f32,
25 pub w: f32, pub h: f32
26}
27
28impl Rect {
29 pub fn new(x: f32, y: f32, w: f32, h: f32) -> Rect {
30 Rect { x, y, w, h }
31 }
32}
33
34#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
35pub struct Color(u8, u8, u8, u8);
36
37impl Color {
38 pub fn rgb(r: u8, g: u8, b: u8) -> Color { Color(r, g, b, 255) }
39 pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Color { Color(r, g, b, a) }
40
41 pub fn black() -> Self { Self::rgb(0, 0, 0) }
42 pub fn white() -> Self { Self::rgb(255, 255, 255) }
43
44 pub fn to_vec4(&self) -> Vec4 {
45 let r = self.0 as f32 / 255.0;
46 let g = self.1 as f32 / 255.0;
47 let b = self.2 as f32 / 255.0;
48 let a = self.3 as f32 / 255.0;
49 Vec4::new(r, g, b, a)
50 }
51}
52
53pub struct Cafe<T: Game> {
54 running: bool,
55 window: Window,
56 timer: TimerSubsystem,
57 _gl_context: GLContext,
58 render: Option<T::Render>,
59 event_pump: EventPump,
60
61 current_time: u32,
62 }
64
65#[derive(Default, Debug)]
66pub struct GameSettings {
67 pub window: WindowSettings
68}
69
70#[derive(Debug)]
71pub struct WindowSettings {
72 pub title: String,
73 pub width: i32, pub height: i32,
74 pub resizable: bool,
75 pub borderless: bool
76}
77
78impl Default for WindowSettings {
79 fn default() -> Self {
80 WindowSettings {
81 title: "cafe 0.1.0".to_string(),
82 width: 640, height: 380,
83 resizable: false,
84 borderless: false,
85 }
86 }
87}
88
89pub struct CafeBuilder {
90 sdl: sdl2::Sdl,
91 video: sdl2::VideoSubsystem,
92 window: WindowBuilder
93}
94
95impl CafeBuilder {
96 pub fn new(title: &str, width: u32, height: u32) -> Self {
97 let sdl = sdl2::init().unwrap();
98 let video = sdl.video().unwrap();
99
100 let mut window = video.window(title, width, height);
101 window.position_centered().opengl();
102
103 let gl_attr = video.gl_attr();
104 gl_attr.set_context_profile(GLProfile::Core);
105 gl_attr.set_context_version(3, 3);
106
107 CafeBuilder {
108 sdl,
109 video,
110 window,
111 }
112 }
113
114 #[doc = "Resizable window"]
115 pub fn resizable(&mut self) -> &mut Self {
116 self.window.resizable();
117 self
118 }
119
120 #[doc = "Borderless window"]
121 pub fn borderless(&mut self) -> &mut Self {
122 self.window.borderless();
123 self
124 }
125
126 #[doc = "Start window as hidden"]
127 pub fn hidden(&mut self) -> &mut Self {
128 self.window.hidden();
129 self
130 }
131
132 #[doc = "Real fullscreen mode"]
133 pub fn fullscreen(&mut self) -> &mut Self {
134 self.window.fullscreen();
135 self
136 }
137
138 #[doc = "Fake fullscreen mode (borderless maximized window)"]
139 pub fn fullscreen_desktop(&mut self) -> &mut Self {
140 self.window.fullscreen_desktop();
141 self
142 }
143
144 #[doc = "Build Cafe context"]
145 pub fn build<T: Game>(&mut self) -> Result<Cafe<T>, String> {
146
147 let sdl_context = &self.sdl;
148 let video_subsystem = &self.video;
149
150 let window = self.window.build().unwrap();
151
152 let sz = window.size();
153 let size = (sz.0 as i32, sz.1 as i32);
154
155 let _gl_context = window.gl_create_context().expect("Failed to create GLContext");
156 tea::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _);
157
158 let event_pump = sdl_context.event_pump().unwrap();
159
160 let mut render = Some(T::create_render());
161 render.as_mut().expect("Failed to create render").on_resize(size.0, size.1);
162
163 tea::viewport(0, 0, size.0, size.1);
164
165 crate::keyboard::init();
166 crate::audio::init();
167
168 let timer = sdl_context.timer().unwrap();
169
170 Ok(Cafe {
172 running: true,
173 window,
174 render,
175 _gl_context,
176 event_pump,
177 timer,
178 current_time: 0
179 })
180 }
181}
182
183pub trait Game {
184 type Render: Render;
185 fn new() -> Self;
186 fn create_render() -> Self::Render { Self::Render::new() }
187 fn run(&mut self, _dt: f32, _render: &mut Self::Render) -> bool { false }
188 fn quit(&self) -> bool { true }
192 fn window_close(&self) {}
193 fn window_resized(&mut self, _width: i32, _height: i32) {}
194 fn window_moved(&mut self, _x: i32, _y: i32) {}
195 fn window_minimized(&mut self) {}
196 fn window_maximized(&mut self) {}
197
198 fn key_down(&mut self, _keycode: Option<Keycode>, _scancode: Option<Scancode>, _repeat: bool, _keymod: Mod) {}
199 fn key_up(&mut self, _keycode: Option<Keycode>, _scancode: Option<Scancode>, _repeat: bool, _keymod: Mod) {}
200 fn joystick_added(&mut self, _which: u32) {}
201 fn joystick_removed(&mut self, _which: u32) {}
202 fn controller_added(&mut self, _which: u32) {}
203 fn controller_removed(&mut self, _which: u32) {}
204
205 fn mouse_motion(&mut self, _x: i32, _y: i32, _xrel: i32, _yrel: i32) {}
206 fn mouse_wheel(&mut self, _scroll_x: f32, _scroll_y: f32, _flipped: bool) {}
207
208 fn text_editing(&mut self, _text: String, _start: i32, _length: i32) {}
209 fn text_input(&mut self, _text: String) {}
210
211 fn joy_button_up(&mut self, _which: u32, _button: u8) {}
212 fn joy_button_down(&mut self, _which: u32, _button: u8) {}
213 fn joy_hat_motion(&mut self, _which: u32, _hat: u8, _state: HatState) {}
214 fn joy_ball_motion(&mut self, _which: u32, _ball: u8, _xrel: i16, _yrel: i16) {}
215 fn joy_axis_motion(&mut self, _which: u32, _axis: u8, _value: i16) {}
216
217 fn controller_button_up(&mut self, _which: u32, _button: Button) {}
218 fn controller_button_down(&mut self, _which: u32, _button: Button) {}
219 fn controller_axis_motion(&mut self, _which: u32, _axis: Axis, _value: i16) {}
220 fn controller_remapped(&mut self, _which: u32) {}
221
222 fn drop_file(&mut self, _filename: String) {}
223 fn drop_text(&mut self, _text: String) {}
224 fn drop_begin(&mut self) {}
225 fn drop_complete(&mut self) {}
226
227 fn finger_up(&mut self, _touch_id: i64, _finger_id: i64, _x: f32, _y: f32, _dx: f32, _dy: f32, _pressure: f32) {}
228 fn finger_down(&mut self, _touch_id: i64, _finger_id: i64, _x: f32, _y: f32, _dx: f32, _dy: f32, _pressure: f32) {}
229 fn finger_motion(&mut self, _touch_id: i64, _finger_id: i64, _x: f32, _y: f32, _dx: f32, _dy: f32, _pressure: f32) {}
230 fn dollar_record(&mut self, _touch_id: i64, _gesture_id: i64, _num_fingers: u32, _error: f32, _x: f32, _y: f32) {}
231 fn dollar_gesture(&mut self, _touch_id: i64, _gesture_id: i64, _num_fingers: u32, _error: f32, _x: f32, _y: f32) {}
232 fn clipboard_update(&mut self) {}
233}
234
235impl<T: Game> Cafe<T> {
236 pub fn run(&mut self) {
237 let mut game = T::new();
238 let render = self.render.as_mut().unwrap();
239 let mut running = true;
240 while running {
241 let last_time = self.current_time;
242 self.current_time = self.timer.ticks();
243
244 let delta = (self.current_time - last_time) as f32 / 1000.0;
245
246 running = game.run(delta, render);
247
248 for event in self.event_pump.poll_iter() {
249 match event {
250 Event::Quit {..} => { running = !game.quit(); },
251 Event::Window { win_event, .. } => {
252 match win_event {
253 WindowEvent::Close => {
254 game.window_close();
255 },
256 WindowEvent::Moved(x, y) => {
257 game.window_moved(x, y);
258 },
259 WindowEvent::Resized(w, h) => {
260 render.on_resize(w, h);
261 game.window_resized(w, h);
262 },
263 WindowEvent::Minimized => {
264 game.window_minimized();
265 },
266 WindowEvent::Maximized => {
267 game.window_maximized();
268 },
269 _ => {}
270 }
271 },
272 Event::KeyDown { keycode, scancode, repeat, keymod, .. } => { game.key_down(keycode, scancode, repeat, keymod); },
273 Event::KeyUp { keycode, scancode, repeat, keymod, .. } => { game.key_up(keycode, scancode, repeat, keymod); },
274 Event::JoyDeviceAdded { which, .. } => { game.joystick_added(which); },
275 Event::JoyDeviceRemoved { which, .. } => { game.joystick_removed(which); },
276 Event::JoyButtonUp { which, button_idx, .. } => { game.joy_button_up(which, button_idx); },
277 Event::JoyButtonDown { which, button_idx, .. } => { game.joy_button_down(which, button_idx); },
278 Event::JoyHatMotion { which, hat_idx, state, .. } => { game.joy_hat_motion(which, hat_idx, state); },
279 Event::JoyBallMotion { which, ball_idx, xrel, yrel, .. } => { game.joy_ball_motion(which, ball_idx, xrel, yrel); },
280 Event::JoyAxisMotion { which, axis_idx, value, .. } => { game.joy_axis_motion(which, axis_idx, value); },
281 Event::ControllerDeviceAdded { which, .. } => { game.controller_added(which); },
282 Event::ControllerDeviceRemoved { which, .. } => { game.controller_removed(which); },
283 Event::ControllerButtonUp { which, button, .. } => { game.controller_button_up(which, button); },
284 Event::ControllerButtonDown { which, button, .. } => { game.controller_button_down(which, button); },
285 Event::ControllerAxisMotion { which, axis, value, .. } => { game.controller_axis_motion(which, axis, value); },
286 Event::ControllerDeviceRemapped { which, .. } => { game.controller_remapped(which); },
287 Event::MouseMotion { x, y, xrel, yrel, .. } => { game.mouse_motion(x, y, xrel, yrel); },
288 Event::MouseWheel { direction, x, y, .. } => { game.mouse_wheel(x as f32, y as f32, direction == sdl2::mouse::MouseWheelDirection::Flipped); },
289 Event::TextEditing { text, start, length, .. } => { game.text_editing(text, start, length); },
290 Event::TextInput { text, .. } => { game.text_input(text); },
291 Event::DropFile { filename, .. } => { game.drop_file(filename); },
292 Event::DropText { filename, .. } => { game.drop_text(filename); },
293 Event::DropBegin { .. } => { game.drop_begin(); },
294 Event::DropComplete { .. } => { game.drop_complete(); },
295 Event::FingerUp { touch_id, finger_id, x, y, dx, dy, pressure, .. } => { game.finger_up(touch_id, finger_id, x, y, dx, dy, pressure); },
296 Event::FingerDown { touch_id, finger_id, x, y, dx, dy, pressure, .. } => { game.finger_down(touch_id, finger_id, x, y, dx, dy, pressure); },
297 Event::FingerMotion { touch_id, finger_id, x, y, dx, dy, pressure, .. } => { game.finger_motion(touch_id, finger_id, x, y, dx, dy, pressure); },
298 Event::DollarRecord { touch_id, gesture_id, num_fingers, error, x, y, .. } => { game.dollar_record(touch_id, gesture_id, num_fingers, error, x, y); },
299 Event::DollarGesture { touch_id, gesture_id, num_fingers, error, x, y, .. } => { game.dollar_gesture(touch_id, gesture_id, num_fingers, error, x, y); },
300 Event::ClipboardUpdate { .. } => { game.clipboard_update(); },
301 _ => {}
302 }
303 }
304
305 self.window.gl_swap_window();
308 }
309 }
310
311 pub fn get_window(&self) -> &Window { &self.window }
312 pub fn get_render(&mut self) -> &mut T::Render { self.render.as_mut().unwrap() }
313
314 pub fn is_running(&self) -> bool { self.running }
315}
316
317impl<T: Game> Drop for Cafe<T> {
318 fn drop(&mut self) {
319 crate::audio::deinit();
320 }
321}