use std::time::Duration;
use glfw::{Action, Context};
use super::super::{MouseButton, Time};
#[cfg(feature = "svg")]
use crate::platform::mouse::cursor_glfw::cursor_from_buffer;
#[cfg(feature = "svg")]
use crate::platform::mouse::CursorResolution;
#[cfg(feature = "svg")]
use crate::platform::mouse::LoadCursorError;
use crate::{
extensions::*,
platform::{
keycodes::KeyCode,
windowing::{WindowCreationError, WindowError},
WindowLevel,
},
prelude::GetWindowHandle,
system::action::{Decoration, Default},
};
#[derive(Debug)]
#[cfg_attr(feature = "c_compatible", repr(C))]
pub struct Framework {
glfw: glfw::Glfw,
width: usize,
height: usize,
window: glfw::Window,
events: std::sync::mpsc::Receiver<(f64, glfw::WindowEvent)>,
texture: u32,
shader_program: u32,
vao: u32,
keyboard_manager: super::super::shared::KeyManager,
mouse_manager: super::super::shared::MouseManager,
maximized: bool,
minimized: bool,
}
#[allow(clippy::needless_pass_by_value, clippy::trivially_copy_pass_by_ref)]
fn log_errors(_: glfw::Error, description: String, _: &()) {
println!("GLFW Error: {description}");
}
static LOG_ERRORS: Option<glfw::ErrorCallback<()>> = Some(glfw::Callback {
f: log_errors,
data: (),
});
impl crate::platform::windowing::traits::NewWindow for Framework {
fn new(
title: &str,
settings: super::super::WindowSettings,
) -> Result<Self, WindowError> {
let Ok(mut glfw) = glfw::init(LOG_ERRORS) else {
return Err(WindowError::FailedToOpenWindow(
WindowCreationError::BackendFailedToLoad,
));
};
glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
glfw.window_hint(glfw::WindowHint::OpenGlProfile(
glfw::OpenGlProfileHint::Core,
));
#[cfg(target_os = "macos")]
glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));
let Some((mut window, events)) = glfw.create_window(
settings.size.0 as u32,
settings.size.1 as u32,
title,
glfw::WindowMode::Windowed,
) else {
return Err(WindowError::FailedToOpenWindow(
WindowCreationError::OsFailed,
));
};
window.make_current();
window.set_all_polling(false);
window.set_key_polling(true);
window.set_scroll_polling(true);
window.set_mouse_button_polling(true);
window.set_pos(settings.position.0, settings.position.1);
crate::system::Os::set_window_borderless(
&get_native_window_handle_from_glfw(&window),
settings.borderless,
);
crate::system::Os::set_window_position(
&get_native_window_handle_from_glfw(&window),
settings.position.0,
settings.position.1,
);
crate::system::Os::set_window_level(
&get_native_window_handle_from_glfw(&window),
settings.window_level,
);
gl::load_with(|symbol| window.get_proc_address(symbol).cast());
let shader_program = unsafe { create_shader_program() };
let (vao, _vbo, _ebo) = unsafe { setup_vertices() };
let texture = unsafe { create_texture() };
Ok(Self {
glfw,
window,
events,
width: settings.size.0 as usize,
height: settings.size.1 as usize,
shader_program,
texture,
vao,
keyboard_manager: super::super::shared::KeyManager::new(),
mouse_manager: super::super::shared::MouseManager::new(),
maximized: false,
minimized: false,
})
}
}
impl crate::platform::windowing::traits::Window for Framework {
fn update_raw(
&mut self,
buffer: &[u32],
_width: usize,
_height: usize,
) -> Result<(), WindowError> {
self.glfw.poll_events();
process_events(self);
unsafe {
update_texture(
self.texture,
self.width as u32,
self.height as u32,
buffer,
);
gl::ClearColor(0.2, 0.3, 0.3, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::UseProgram(self.shader_program);
gl::BindVertexArray(self.vao);
gl::BindTexture(gl::TEXTURE_2D, self.texture);
gl::DrawElements(
gl::TRIANGLES,
6,
gl::UNSIGNED_INT,
core::ptr::null(),
);
}
self.window.swap_buffers();
Ok(())
}
fn close_and_clean_up(&mut self) {
unsafe {
gl::DeleteTextures(1, &raw const self.texture);
gl::DeleteProgram(self.shader_program);
}
}
fn is_open(&self) -> bool {
!self.window.should_close()
}
}
impl crate::platform::windowing::traits::Timing for Framework {
#[inline]
fn get_time(&self) -> Box<dyn Time> {
super::super::shared::get_time()
}
#[inline]
fn sleep(&self, time: Duration) {
super::super::shared::sleep(time);
}
}
use crate::system::{action::Iconized, Os};
impl crate::platform::windowing::traits::RenderLayer for Framework {
#[inline]
fn set_render_layer(&mut self, level: WindowLevel) {
crate::system::Os::set_window_level(&self.get_window_handle(), level);
}
}
impl crate::platform::windowing::traits::Visibility for Framework {
#[inline]
fn maximize(&mut self) {
Os::maximize(&self.get_window_handle());
}
#[inline]
fn minimize(&mut self) {
Os::minimize(&self.get_window_handle());
}
#[inline]
fn restore(&mut self) {
Os::restore(&self.get_window_handle());
}
#[inline]
fn is_maximized(&self) -> bool {
self.maximized
}
#[inline]
fn is_minimized(&self) -> bool {
self.minimized
}
}
impl crate::platform::windowing::traits::Control for Framework {
fn get_position(&self) -> (i32, i32) {
self.window.get_pos()
}
fn get_size(&self) -> (i32, i32) {
crate::system::Os::get_window_size(&get_native_window_handle_from_glfw(
&self.window,
))
.try_tuple_into()
.unwrap_or((0, 0))
}
#[allow(clippy::cast_possible_wrap)]
fn set_size(&mut self, buffer: &super::super::Buffer) {
self.window.set_size(buffer.width as i32, buffer.height as i32);
}
fn set_position(&mut self, xy: (i32, i32)) {
self.window.set_pos(xy.0, xy.1);
}
}
impl crate::platform::windowing::traits::MouseInput for Framework {
fn get_mouse_position(&self) -> Option<(f32, f32)> {
self.window.get_cursor_pos().try_tuple_into()
}
fn is_mouse_down(&self, button: MouseButton) -> bool {
self.mouse_manager.is_mouse_button_pressed(button)
}
}
#[cfg(feature = "keyboard_query")]
impl crate::platform::windowing::traits::KeyboardInput for Framework {
fn is_key_down(&self, keycode: KeyCode) -> bool {
self.keyboard_manager.is_key_pressed(keycode)
}
}
impl crate::platform::windowing::traits::ExtendedMouseInput for Framework {
fn get_mouse_scroll(&self) -> (f32, f32) {
self.mouse_manager.get_scroll()
}
}
#[cfg(feature = "keyboard_query")]
impl crate::platform::windowing::traits::ExtendedKeyboardInput for Framework {
fn get_all_keys_down(&self) -> Vec<KeyCode> {
self.keyboard_manager.get_all_pressed_keys()
}
}
const fn action_to_bool(action: Action) -> Option<bool> {
match action {
Action::Press => Some(true),
Action::Release => Some(false),
Action::Repeat => None,
}
}
impl crate::platform::windowing::traits::Output for Framework {
fn log(&self, t: &str) {
super::super::shared::log(t);
}
}
impl crate::platform::windowing::traits::GetWindowHandle for Framework {
fn get_window_handle(&self) -> raw_window_handle::RawWindowHandle {
get_native_window_handle_from_glfw(&self.window)
}
}
impl crate::platform::windowing::traits::ExtendedWindow for Framework {
fn set_title(&mut self, title: &str) {
self.window.set_title(title);
}
}
#[cfg(feature = "svg")]
impl crate::platform::windowing::traits::LoadCursorStyle for Framework {
fn load_custom_cursors(
&mut self,
size: CursorResolution,
main_color: u32,
secondary_color: u32,
) -> Result<super::super::mouse::Cursors, LoadCursorError> {
super::super::mouse::Cursors::load(
size,
main_color,
secondary_color,
super::super::mouse::cursor_glfw::load_base_cursor_with_file,
)
.map_err(|x| {
LoadCursorError::InvalidImageData(format!("Unable to access {x}"))
})
}
fn load_custom_cursor(
&mut self,
image: super::super::Buffer,
hotspot: (u8, u8),
) -> Result<super::super::mouse::Cursor, LoadCursorError> {
Ok(cursor_from_buffer(image, unsafe {
hotspot.try_tuple_into().unwrap_unchecked()
}))
}
}
#[cfg(feature = "svg")]
impl crate::platform::windowing::traits::UseCursorStyle for Framework {
fn set_cursor_style(
&mut self,
style: &super::super::Cursor,
) -> Result<(), WindowError> {
super::super::mouse::use_cursor(style, Some(&mut self.window))
}
}
const VERTEX_SHADER_SOURCE: &str = r"
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main() {
gl_Position = vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
";
const FRAGMENT_SHADER_SOURCE: &str = r"
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main() {
FragColor = texture(ourTexture, TexCoord);
}
";
fn process_events(window: &mut Framework) {
let events: &std::sync::mpsc::Receiver<(f64, glfw::WindowEvent)> =
&window.events;
window.mouse_manager.reset_scroll();
for (_, event) in glfw::flush_messages(events) {
match event {
glfw::WindowEvent::Key(key, _scan_code, action, _mods) => {
if let Some(converted) = action_to_bool(action) {
window
.keyboard_manager
.set_key_state(map_glfw_key_to_keycode(key), converted);
}
}
glfw::WindowEvent::Scroll(x, y) => {
window
.mouse_manager
.add_scroll((x, y).try_tuple_into().unwrap_or_default());
}
glfw::WindowEvent::Close => {
window.window.set_should_close(true);
}
glfw::WindowEvent::MouseButton(button, action, _mods) => {
if let Some(action) = action_to_bool(action) {
window.mouse_manager.set_mouse_button_state(
map_glfw_mouse_button_to_mouse_button(button),
action,
);
}
}
glfw::WindowEvent::Maximize(bool) => {
window.minimized = !bool;
}
glfw::WindowEvent::Iconify(bool) => {
window.minimized = bool;
}
_ => {}
}
}
}
#[must_use]
pub const fn map_glfw_mouse_button_to_mouse_button(
button: glfw::MouseButton,
) -> MouseButton {
match button {
glfw::MouseButton::Button1 => MouseButton::Left,
glfw::MouseButton::Button2 => MouseButton::Right,
glfw::MouseButton::Button3 => MouseButton::Middle,
glfw::MouseButton::Button4
| glfw::MouseButton::Button5
| glfw::MouseButton::Button6
| glfw::MouseButton::Button7
| glfw::MouseButton::Button8 => MouseButton::Unsupported,
}
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub const fn map_glfw_key_to_keycode(key: glfw::Key) -> KeyCode {
match key {
glfw::Key::A => KeyCode::A,
glfw::Key::B => KeyCode::B,
glfw::Key::C => KeyCode::C,
glfw::Key::D => KeyCode::D,
glfw::Key::E => KeyCode::E,
glfw::Key::F => KeyCode::F,
glfw::Key::G => KeyCode::G,
glfw::Key::H => KeyCode::H,
glfw::Key::I => KeyCode::I,
glfw::Key::J => KeyCode::J,
glfw::Key::K => KeyCode::K,
glfw::Key::L => KeyCode::L,
glfw::Key::M => KeyCode::M,
glfw::Key::N => KeyCode::N,
glfw::Key::O => KeyCode::O,
glfw::Key::P => KeyCode::P,
glfw::Key::Q => KeyCode::Q,
glfw::Key::R => KeyCode::R,
glfw::Key::S => KeyCode::S,
glfw::Key::T => KeyCode::T,
glfw::Key::U => KeyCode::U,
glfw::Key::V => KeyCode::V,
glfw::Key::W => KeyCode::W,
glfw::Key::X => KeyCode::X,
glfw::Key::Y => KeyCode::Y,
glfw::Key::Z => KeyCode::Z,
glfw::Key::Escape => KeyCode::Escape,
glfw::Key::F1 => KeyCode::F1,
glfw::Key::F2 => KeyCode::F2,
glfw::Key::F3 => KeyCode::F3,
glfw::Key::F4 => KeyCode::F4,
glfw::Key::F5 => KeyCode::F5,
glfw::Key::F6 => KeyCode::F6,
glfw::Key::F7 => KeyCode::F7,
glfw::Key::F8 => KeyCode::F8,
glfw::Key::F9 => KeyCode::F9,
glfw::Key::F10 => KeyCode::F10,
glfw::Key::F11 => KeyCode::F11,
glfw::Key::F12 => KeyCode::F12,
glfw::Key::Space => KeyCode::Space,
glfw::Key::Enter => KeyCode::Enter,
glfw::Key::Backspace => KeyCode::Backspace,
glfw::Key::Left => KeyCode::LeftArrow,
glfw::Key::Right => KeyCode::RightArrow,
glfw::Key::Up => KeyCode::UpArrow,
glfw::Key::Down => KeyCode::DownArrow,
glfw::Key::Tab => KeyCode::Tab,
glfw::Key::LeftShift => KeyCode::LeftShift,
glfw::Key::RightShift => KeyCode::RightShift,
glfw::Key::LeftControl => KeyCode::LeftControl,
glfw::Key::RightControl => KeyCode::RightControl,
glfw::Key::LeftAlt => KeyCode::LeftAlt,
glfw::Key::RightAlt => KeyCode::RightAlt,
glfw::Key::LeftSuper => KeyCode::LeftSuper,
glfw::Key::RightSuper => KeyCode::RightSuper,
glfw::Key::Comma => KeyCode::Comma,
glfw::Key::Minus => KeyCode::Minus,
glfw::Key::Period => KeyCode::Period,
glfw::Key::Slash => KeyCode::Slash,
glfw::Key::Semicolon => KeyCode::Semicolon,
glfw::Key::LeftBracket => KeyCode::LeftBracket,
glfw::Key::Backslash => KeyCode::Backslash,
glfw::Key::RightBracket => KeyCode::RightBracket,
glfw::Key::F13 => KeyCode::F13,
glfw::Key::F14 => KeyCode::F14,
glfw::Key::F15 => KeyCode::F15,
glfw::Key::F16 => KeyCode::F16,
glfw::Key::F17 => KeyCode::F17,
glfw::Key::F18 => KeyCode::F18,
glfw::Key::F19 => KeyCode::F19,
glfw::Key::F20 => KeyCode::F20,
glfw::Key::F21 => KeyCode::F21,
glfw::Key::F22 => KeyCode::F22,
glfw::Key::F23 => KeyCode::F23,
glfw::Key::F24 => KeyCode::F24,
glfw::Key::F25 => KeyCode::F25,
glfw::Key::Apostrophe => KeyCode::Apostrophe,
glfw::Key::CapsLock => KeyCode::CapsLock,
glfw::Key::Delete => KeyCode::Delete,
glfw::Key::End => KeyCode::End,
glfw::Key::Home => KeyCode::Home,
glfw::Key::Insert => KeyCode::Insert,
glfw::Key::Kp0 => KeyCode::KeyPad0,
glfw::Key::Kp1 => KeyCode::KeyPad1,
glfw::Key::Kp2 => KeyCode::KeyPad2,
glfw::Key::Kp3 => KeyCode::KeyPad3,
glfw::Key::Kp4 => KeyCode::KeyPad4,
glfw::Key::Kp5 => KeyCode::KeyPad5,
glfw::Key::Kp6 => KeyCode::KeyPad6,
glfw::Key::Kp7 => KeyCode::KeyPad7,
glfw::Key::Kp8 => KeyCode::KeyPad8,
glfw::Key::Kp9 => KeyCode::KeyPad9,
glfw::Key::KpDivide => KeyCode::KeyPadDivide,
glfw::Key::KpEnter => KeyCode::KeyPadEnter,
glfw::Key::KpEqual => KeyCode::KeyPadEqual,
glfw::Key::KpMultiply => KeyCode::KeyPadMultiply,
glfw::Key::KpSubtract => KeyCode::KeyPadSubtract,
glfw::Key::GraveAccent => KeyCode::Grave,
glfw::Key::KpAdd => KeyCode::KeyPadAdd,
glfw::Key::KpDecimal => KeyCode::KeyPadDecimal,
glfw::Key::Num0 => KeyCode::Num0,
glfw::Key::Num1 => KeyCode::Num1,
glfw::Key::Num2 => KeyCode::Num2,
glfw::Key::Num3 => KeyCode::Num3,
glfw::Key::Num4 => KeyCode::Num4,
glfw::Key::Num5 => KeyCode::Num5,
glfw::Key::Num6 => KeyCode::Num6,
glfw::Key::Num7 => KeyCode::Num7,
glfw::Key::Num8 => KeyCode::Num8,
glfw::Key::Num9 => KeyCode::Num9,
glfw::Key::Menu => KeyCode::Menu,
glfw::Key::NumLock => KeyCode::NumLock,
glfw::Key::PageDown => KeyCode::PageDown,
glfw::Key::PageUp => KeyCode::PageUp,
glfw::Key::Pause => KeyCode::Pause,
glfw::Key::PrintScreen => KeyCode::PrintScreen,
glfw::Key::World1 => KeyCode::World1,
glfw::Key::World2 => KeyCode::World2,
glfw::Key::Equal => KeyCode::Equal,
glfw::Key::ScrollLock => KeyCode::ScrollLock,
glfw::Key::Unknown => KeyCode::Unknown,
}
}
unsafe fn create_shader_program() -> u32 {
let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
let c_str_vert = std::ffi::CString::new(VERTEX_SHADER_SOURCE.as_bytes())
.unwrap_unchecked();
gl::ShaderSource(vertex_shader, 1, &c_str_vert.as_ptr(), core::ptr::null());
gl::CompileShader(vertex_shader);
check_shader_errors(vertex_shader, "VERTEX");
let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
let c_str_frag = std::ffi::CString::new(FRAGMENT_SHADER_SOURCE.as_bytes())
.unwrap_unchecked();
gl::ShaderSource(
fragment_shader,
1,
&c_str_frag.as_ptr(),
core::ptr::null(),
);
gl::CompileShader(fragment_shader);
check_shader_errors(fragment_shader, "FRAGMENT");
let shader_program = gl::CreateProgram();
gl::AttachShader(shader_program, vertex_shader);
gl::AttachShader(shader_program, fragment_shader);
gl::LinkProgram(shader_program);
check_program_errors(shader_program);
gl::DeleteShader(vertex_shader);
gl::DeleteShader(fragment_shader);
shader_program
}
unsafe fn check_shader_errors(shader: u32, shader_type: &str) {
let mut success = 0;
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &raw mut success);
if success == 0 {
let mut log_length = 0;
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &raw mut log_length);
let mut log = vec![0u8; log_length as usize];
gl::GetShaderInfoLog(
shader,
log_length,
core::ptr::null_mut(),
log.as_mut_ptr().cast::<i8>(),
);
let log_str = String::from_utf8_lossy(&log);
eprintln!("ERROR::SHADER::{shader_type}_COMPILATION_FAILED\n{log_str}");
}
}
unsafe fn check_program_errors(program: u32) {
let mut success = 0;
gl::GetProgramiv(program, gl::LINK_STATUS, &raw mut success);
if success == 0 {
let mut log_length = 0;
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &raw mut log_length);
let mut log = vec![0u8; log_length as usize];
gl::GetProgramInfoLog(
program,
log_length,
core::ptr::null_mut(),
log.as_mut_ptr().cast::<i8>(),
);
if log.last() == Some(&0) {
log.pop();
}
let log_str = String::from_utf8_lossy(&log);
eprintln!("ERROR::PROGRAM::LINKING_FAILED\n{log_str}");
}
}
#[allow(clippy::cast_possible_wrap)]
unsafe fn setup_vertices() -> (u32, u32, u32) {
#[rustfmt::skip]
let vertices: [f32; 20] = [
1.0, 1.0, 0.0, 1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, -1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0.0 ];
let indices: [u32; 6] = [
0, 1, 3, 1, 2, 3, ];
let (mut vao, mut vbo, mut ebo) = (0, 0, 0);
gl::GenVertexArrays(1, &raw mut vao);
gl::GenBuffers(1, &raw mut vbo);
gl::GenBuffers(1, &raw mut ebo);
gl::BindVertexArray(vao);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(
gl::ARRAY_BUFFER,
(vertices.len() * std::mem::size_of::<f32>()) as isize,
vertices.as_ptr().cast::<std::ffi::c_void>(),
gl::STATIC_DRAW,
);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
gl::BufferData(
gl::ELEMENT_ARRAY_BUFFER,
(indices.len() * std::mem::size_of::<u32>()) as isize,
indices.as_ptr().cast::<std::ffi::c_void>(),
gl::STATIC_DRAW,
);
gl::VertexAttribPointer(
0,
3,
gl::FLOAT,
gl::FALSE,
(5 * std::mem::size_of::<f32>()) as i32,
core::ptr::null(),
);
gl::EnableVertexAttribArray(0);
gl::VertexAttribPointer(
1,
2,
gl::FLOAT,
gl::FALSE,
(5 * std::mem::size_of::<f32>()) as i32,
(3 * std::mem::size_of::<f32>()) as *const std::ffi::c_void,
);
gl::EnableVertexAttribArray(1);
(vao, vbo, ebo)
}
#[allow(clippy::cast_possible_wrap)]
#[inline]
unsafe fn create_texture() -> u32 {
let mut texture = 0;
gl::GenTextures(1, &raw mut texture);
gl::BindTexture(gl::TEXTURE_2D, texture);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_MIN_FILTER,
gl::NEAREST as i32,
);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_MAG_FILTER,
gl::NEAREST as i32,
);
texture
}
#[inline]
#[allow(clippy::cast_possible_wrap)]
unsafe fn update_texture(
texture: u32,
width: u32,
height: u32,
buffer: &[u32],
) {
gl::BindTexture(gl::TEXTURE_2D, texture);
let rgba_buffer = crate::graphics::switch_red_and_blue_list(buffer);
gl::TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as i32,
width as i32,
height as i32,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
rgba_buffer.as_ptr().cast::<std::ffi::c_void>(),
);
}
#[allow(
clippy::cast_possible_wrap,
clippy::unwrap_used,
clippy::missing_panics_doc
)]
#[must_use]
pub fn get_native_window_handle_from_glfw(
window: &glfw::Window,
) -> raw_window_handle::RawWindowHandle {
#[cfg(target_os = "windows")]
{
let handle = raw_window_handle::Win32WindowHandle::new(
core::num::NonZero::new(window.get_win32_window() as isize)
.unwrap(),
);
raw_window_handle::RawWindowHandle::Win32(handle)
}
#[cfg(target_os = "macos")]
{
let mut handle = raw_window_handle::AppKitWindowHandle::empty();
handle.ns_view = window.get_cocoa_window();
raw_window_handle::RawWindowHandle::AppKit(handle)
}
}