engine/presentation/src/wsi/windowsystemintegration.rs
2025-02-26 14:51:44 +01:00

455 lines
13 KiB
Rust

// needed since RLS won't accept #[repr(C)]
#![allow(improper_ctypes)]
use anyhow::Error;
use sdl2;
use sdl2::Sdl;
use sdl2::clipboard::ClipboardUtil;
use sdl2::messagebox::{MessageBoxFlag, show_simple_message_box};
use sdl2::mouse::Cursor;
use sdl2::surface::Surface as SDL_Surface;
use sdl2::sys::SDL_Window;
use sdl2::video::{FullscreenType, WindowPos};
use crate::Result;
use vulkan_rs::prelude::*;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::path::Path;
use std::sync::{
Arc, Mutex,
atomic::{AtomicI32, AtomicU32, Ordering::SeqCst},
};
const SDL_SYSWM_WINDOWS: u32 = 0x1;
const SDL_SYSWM_X11: u32 = 0x2;
const SDL_SYSWM_COCOA: u32 = 0x4;
const SDL_SYSWM_WAYLAND: u32 = 0x6;
const SDL_SYSWM_ANDROID: u32 = 0x9;
#[repr(C)]
struct SdlSysWmInfo {
version: sdl2::version::Version,
subsystem: u32,
info: [u64; 32],
}
unsafe extern "C" {
fn SDL_GetWindowWMInfo(window: *const sdl2::sys::SDL_Window, info: *mut SdlSysWmInfo) -> bool;
}
#[derive(Default, Debug, Clone)]
pub struct WindowCreateInfo {
pub title: String,
pub width: u32,
pub height: u32,
pub fullscreen: bool,
pub requested_display: Option<String>,
}
#[derive(Debug)]
pub struct Display {
pub name: String,
// bounds
pub x: i32,
pub y: i32,
pub w: u32,
pub h: u32,
pub dpi: [f32; 3],
}
#[derive(Default, Debug)]
struct CellRect {
x: AtomicI32,
y: AtomicI32,
width: AtomicU32,
height: AtomicU32,
}
impl CellRect {
fn update_from_window(&self, window: &sdl2::video::Window) {
let (w, h) = window.size();
let (x, y) = window.position();
self.x.store(x, SeqCst);
self.y.store(y, SeqCst);
self.width.store(w, SeqCst);
self.height.store(h, SeqCst);
}
fn update_to_window(&self, window: &mut sdl2::video::Window) -> Result<()> {
set_window_size(window, self.width.load(SeqCst), self.height.load(SeqCst))?;
window.set_position(
WindowPos::Positioned(self.x.load(SeqCst)),
WindowPos::Positioned(self.y.load(SeqCst)),
);
Ok(())
}
}
pub struct WindowSystemIntegration {
// sdl
_video_subsystem: Mutex<sdl2::VideoSubsystem>,
window: Mutex<sdl2::video::Window>,
clipboard_util: ClipboardUtil,
cursor: Mutex<Option<Cursor>>,
displays: Vec<Display>,
_enabled_display_index: usize,
pre_fullscreen_rect: CellRect,
surface: Mutex<Option<Arc<Surface>>>,
}
unsafe impl Send for WindowSystemIntegration {}
unsafe impl Sync for WindowSystemIntegration {}
impl WindowSystemIntegration {
pub(crate) fn new<'a>(
create_info: &WindowCreateInfo,
context: &Sdl,
) -> Result<Arc<WindowSystemIntegration>> {
// create video subsystem
let video_subsystem = context.video().map_err(|s| anyhow::Error::msg(s))?;
// query display count
let display_count = match video_subsystem.num_video_displays() {
Ok(num_displays) => num_displays,
Err(_) => 0,
};
if display_count == 0 {
return Err(anyhow::Error::msg("No display where detected!"));
}
// query display information
let mut displays = Vec::with_capacity(display_count as usize);
for i in 0..display_count {
let rect = video_subsystem
.display_bounds(i)
.map_err(|s| anyhow::Error::msg(s))?;
let name = video_subsystem
.display_name(i)
.map_err(|s| anyhow::Error::msg(s))?;
let (dpi0, dpi1, dpi2) = match video_subsystem.display_dpi(i) {
Ok(dpis) => dpis,
Err(msg) => {
println!("failed getting dpi for display {} ({}): {}", i, name, msg);
(0.0, 0.0, 0.0)
}
};
let display = Display {
name,
x: rect.x(),
y: rect.y(),
w: rect.width(),
h: rect.height(),
dpi: [dpi0, dpi1, dpi2],
};
displays.push(display);
}
// check if there is an preferred display
let mut display_index = None;
if let Some(requested_display) = &create_info.requested_display {
match displays
.iter()
.position(|display| display.name == *requested_display)
{
Some(index) => display_index = Some(index),
None => {
println!("could not find display: {}", requested_display);
println!("defaulting to display 0 ({})", displays[0].name);
}
}
}
// build window
let mut window_builder =
video_subsystem.window(&create_info.title, create_info.width, create_info.height);
window_builder.resizable().vulkan();
match display_index {
Some(index) => {
let display = &displays[index];
window_builder.position(
display.x + display.w as i32 / 2,
display.y + display.h as i32 / 2,
);
}
None => {
window_builder.position_centered();
}
}
let window = window_builder.build()?;
display_index = Some(window.display_index().map_err(|s| anyhow::Error::msg(s))? as usize);
let rect = CellRect::default();
rect.update_from_window(&window);
let wsi = WindowSystemIntegration {
clipboard_util: video_subsystem.clipboard(),
_video_subsystem: Mutex::new(video_subsystem),
window: Mutex::new(window),
cursor: Mutex::new(None),
displays,
_enabled_display_index: display_index.expect("display index not set"),
pre_fullscreen_rect: rect,
surface: Mutex::new(None),
};
if create_info.fullscreen {
wsi.set_fullscreen(true)?;
}
Ok(Arc::new(wsi))
}
pub fn window_size(&self) -> (u32, u32) {
self.window.lock().unwrap().size()
}
pub fn is_fullscreen(&self) -> bool {
match self.window.lock().unwrap().fullscreen_state() {
FullscreenType::Desktop => false,
FullscreenType::True => true,
FullscreenType::Off => false,
}
}
pub fn set_fullscreen(&self, fullscreen: bool) -> Result<()> {
let mut window = self.window.lock().unwrap();
if fullscreen {
// store window information
self.pre_fullscreen_rect.update_from_window(&window);
// set fullscreen size to fit display
let display =
&self.displays[window.display_index().map_err(|s| anyhow::Error::msg(s))? as usize];
set_window_size(&mut window, display.w, display.h)?;
window.set_bordered(false);
// change fullscreen mode
window
.set_fullscreen(FullscreenType::True)
.map_err(|s| anyhow::Error::msg(s))?;
} else {
// change fullscreen mode
window
.set_fullscreen(FullscreenType::Off)
.map_err(|s| anyhow::Error::msg(s))?;
// force window borders
window.set_bordered(true);
// update window values
self.pre_fullscreen_rect.update_to_window(&mut window)?;
}
Ok(())
}
pub fn show_simple_info_box(&self, title: &str, message: &str) -> Result<()> {
self.show_simple_message_box(MessageBoxFlag::INFORMATION, title, message)
}
pub fn show_simple_warning_box(&self, title: &str, message: &str) -> Result<()> {
self.show_simple_message_box(MessageBoxFlag::WARNING, title, message)
}
pub fn show_simple_error_box(&self, title: &str, message: &str) -> Result<()> {
self.show_simple_message_box(MessageBoxFlag::ERROR, title, message)
}
pub fn clipboard_content(&self) -> Result<Option<String>> {
Ok(if self.clipboard_util.has_clipboard_text() {
Some(
self.clipboard_util
.clipboard_text()
.map_err(|err| Error::msg(err))?,
)
} else {
None
})
}
#[inline]
fn show_simple_message_box(
&self,
flags: MessageBoxFlag,
title: &str,
message: &str,
) -> Result<()> {
let window = self.window.lock().unwrap();
show_simple_message_box(flags, title, message, Some(window.deref()))?;
Ok(())
}
pub fn set_opacity(&self, opacity: f32) -> Result<()> {
self.window
.lock()
.unwrap()
.set_opacity(opacity)
.map_err(|s| anyhow::Error::msg(s))?;
Ok(())
}
pub fn set_icon<T: AsRef<Path>>(&self, path: T) -> Result<()> {
// let texture = match image::open(path) {
// Ok(tex) => tex.to_rgba(),
// Err(err) => create_error!(format!("error loading image: {}", err)),
// };
// let (width, height) = texture.dimensions();
// let mut texture_data = texture.into_raw();
// let surface = SDL_Surface::from_data(
// &mut texture_data,
// width,
// height,
// width * 4,
// PixelFormatEnum::RGBA8888,
// )?;
let surface = SDL_Surface::load_bmp(path).map_err(|s| anyhow::Error::msg(s))?;
self.window.lock().unwrap().set_icon(surface);
Ok(())
}
pub fn set_cursor<T: AsRef<Path>>(&self, path: T) -> Result<()> {
// let texture = match image::open(path) {
// Ok(tex) => tex.to_rgba(),
// Err(err) => create_error!(format!("error loading image: {}", err)),
// };
// let (width, height) = texture.dimensions();
// let mut texture_data = texture.into_raw();
// let surface = SDL_Surface::from_data(
// &mut texture_data,
// width,
// height,
// width * 4,
// PixelFormatEnum::RGBA8888,
// )?;
let surface = SDL_Surface::load_bmp(path).map_err(|s| anyhow::Error::msg(s))?;
let cursor = Cursor::from_surface(surface, 0, 0).map_err(|s| anyhow::Error::msg(s))?;
cursor.set();
*self.cursor.lock().unwrap() = Some(cursor);
Ok(())
}
fn raw_sdl2_window(&self) -> *mut SDL_Window {
self.window.lock().unwrap().raw()
}
pub fn displays(&self) -> &[Display] {
&self.displays
}
pub fn create_vulkan_surface(&self, instance: &Arc<Instance>) -> Result<()> {
let vk_surface = self
.window
.lock()
.unwrap()
.vulkan_create_surface(instance.vk_handle().raw())
.map_err(|s| anyhow::Error::msg(s))?
.into();
*self.surface.lock().unwrap() = Some(Surface::from_vk_surface(vk_surface, instance));
Ok(())
}
pub fn surface(&self) -> Arc<Surface> {
self.surface.lock().unwrap().as_ref().unwrap().clone()
}
pub(crate) fn activate_vulkan_instance_extensions(
&self,
extensions: &mut InstanceExtensions,
) -> Result<()> {
let sys_wm_info: SdlSysWmInfo = unsafe {
let tmp = MaybeUninit::zeroed();
let mut ret: SdlSysWmInfo = tmp.assume_init();
ret.version = sdl2::version::version();
SDL_GetWindowWMInfo(self.raw_sdl2_window(), &mut ret);
ret
};
match sys_wm_info.subsystem {
SDL_SYSWM_ANDROID => extensions.android_surface = true,
SDL_SYSWM_COCOA => extensions.macos_surface = true,
SDL_SYSWM_WAYLAND => extensions.wayland_surface = true,
SDL_SYSWM_WINDOWS => extensions.win32_surface = true,
SDL_SYSWM_X11 => extensions.xlib_surface = true,
_ => {
return Err(anyhow::Error::msg(format!(
"Unsupported window subsystem flag {}",
sys_wm_info.subsystem
)));
}
}
extensions.surface = true;
Ok(())
}
pub(crate) fn activate_vulkan_device_extensions(
&self,
extensions: &mut DeviceExtensions,
) -> Result<()> {
extensions.swapchain = true;
Ok(())
}
}
impl std::fmt::Debug for WindowSystemIntegration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "WindowSystemIntegration {{ }}")
}
}
/// helper function to wrap SDL2 error types
#[inline]
fn set_window_size(window: &mut sdl2::video::Window, width: u32, height: u32) -> Result<()> {
window.set_size(width, height)?;
Ok(())
}