diff --git a/bracket-terminal/src/hal/native/init.rs b/bracket-terminal/src/hal/native/init.rs index e68dcb4..655f920 100755 --- a/bracket-terminal/src/hal/native/init.rs +++ b/bracket-terminal/src/hal/native/init.rs @@ -1,6 +1,6 @@ use super::BACKEND; use crate::BResult; -use crate::hal::native::{WrappedContext, shader_strings}; +use crate::hal::native::{NativeInitSettings, WrappedContext, shader_strings}; use crate::hal::scaler::ScreenScaler; use crate::hal::{Framebuffer, Shader, setup_quad}; use crate::prelude::{BACKEND_INTERNAL, BTerm, InitHints}; @@ -16,28 +16,99 @@ use std::ffi::CString; use std::num::NonZeroU32; use winit::{ dpi::LogicalSize, - event_loop::EventLoop, + event_loop::{ActiveEventLoop, EventLoop}, raw_window_handle::{HasDisplayHandle, HasWindowHandle}, - window::{Fullscreen, WindowAttributes}, + window::{Fullscreen, Window, WindowAttributes}, }; -#[allow(deprecated)] +pub struct NativeRuntime { + pub window: Window, + pub gl_context: glutin::context::PossiblyCurrentContext, + pub gl_surface: glutin::surface::Surface, +} + pub fn init_raw( width_pixels: u32, height_pixels: u32, window_title: S, platform_hints: InitHints, ) -> BResult { - let mut scaler = ScreenScaler::new(platform_hints.desired_gutter, width_pixels, height_pixels); let el = EventLoop::new()?; + let frame_sleep_time = crate::hal::convert_fps_to_wait(platform_hints.frame_sleep_time); + let resize_scaling = platform_hints.resize_scaling; + let scaler = ScreenScaler::new(platform_hints.desired_gutter, width_pixels, height_pixels); + { + let mut be = BACKEND.lock(); + be.context_wrapper = Some(WrappedContext { + el, + init: NativeInitSettings { + width_pixels, + height_pixels, + window_title: window_title.to_string(), + platform_hints, + }, + }); + be.frame_sleep_time = frame_sleep_time; + be.resize_scaling = resize_scaling; + be.screen_scaler = scaler; + } + + let bterm = BTerm { + width_pixels, + height_pixels, + original_width_pixels: width_pixels, + original_height_pixels: height_pixels, + fps: 0.0, + frame_time_ms: 0.0, + active_console: 0, + key: None, + mouse_pos: (0, 0), + left_click: false, + shift: false, + control: false, + alt: false, + web_button: None, + quitting: false, + post_scanlines: false, + post_screenburn: false, + screen_burn_color: bracket_color::prelude::RGB::from_f32(0.0, 1.0, 1.0), + mouse_visible: true, + }; + Ok(bterm) +} + +pub(super) fn init_runtime( + event_loop: &ActiveEventLoop, + init: NativeInitSettings, +) -> BResult { + let NativeInitSettings { + width_pixels, + height_pixels, + window_title, + platform_hints, + } = init; + let InitHints { + vsync, + fullscreen, + gl_version, + gl_profile, + hardware_acceleration, + srgb, + frame_sleep_time, + resize_scaling, + desired_gutter, + fitscreen, + } = platform_hints; + + let mut scaler = ScreenScaler::new(desired_gutter, width_pixels, height_pixels); let window_size = scaler.new_window_size(); let window_size = LogicalSize::new(window_size.width, window_size.height); let window_attributes = WindowAttributes::default() - .with_title(window_title.to_string()) - .with_resizable(platform_hints.fitscreen) + .with_title(window_title) + .with_resizable(fitscreen) .with_min_inner_size(window_size) .with_inner_size(window_size); - let window = el.create_window(window_attributes)?; + let window = event_loop.create_window(window_attributes)?; let raw_display = window.display_handle()?.as_raw(); #[cfg(target_os = "macos")] @@ -53,7 +124,7 @@ pub fn init_raw( .with_alpha_size(8) .with_transparency(false) .with_surface_type(ConfigSurfaceTypes::WINDOW); - if platform_hints.hardware_acceleration { + if hardware_acceleration { template_builder = template_builder.prefer_hardware_accelerated(Some(true)); } @@ -72,8 +143,8 @@ pub fn init_raw( }; let context_attributes = ContextAttributesBuilder::new() - .with_profile(platform_hints.gl_profile) - .with_context_api(platform_hints.gl_version) + .with_profile(gl_profile) + .with_context_api(gl_version) .build(Some(raw_window_handle)); let not_current_gl_context = unsafe { gl_display.create_context(&config, &context_attributes)? }; @@ -82,20 +153,20 @@ pub fn init_raw( let width = NonZeroU32::new(physical_size.width.max(1)).unwrap(); let height = NonZeroU32::new(physical_size.height.max(1)).unwrap(); let attrs = SurfaceAttributesBuilder::::new() - .with_srgb(Some(platform_hints.srgb)) + .with_srgb(Some(srgb)) .build(raw_window_handle, width, height); let gl_surface = unsafe { gl_display.create_window_surface(&config, &attrs)? }; let gl_context = not_current_gl_context.make_current(&gl_surface)?; - if platform_hints.vsync { + if vsync { let _ = gl_surface .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())); } else { let _ = gl_surface.set_swap_interval(&gl_context, SwapInterval::DontWait); } - if platform_hints.fullscreen { - if let Some(mh) = window.available_monitors().next() { + if fullscreen { + if let Some(mh) = event_loop.available_monitors().next() { window.set_fullscreen(Some(Fullscreen::Borderless(Some(mh)))); } else { return Err("No available monitor found".into()); @@ -119,7 +190,6 @@ pub fn init_raw( ); } - // Load our basic shaders let shaders: Vec = vec![ Shader::new( &gl, @@ -149,7 +219,6 @@ pub fn init_raw( ), ]; - // Build the backing frame-buffer let initial_dpi_factor = window.scale_factor(); scaler.change_logical_size(width_pixels, height_pixels, initial_dpi_factor as f32); let backing_fbo = Framebuffer::build_fbo( @@ -157,46 +226,21 @@ pub fn init_raw( scaler.logical_size.0 as i32, scaler.logical_size.1 as i32, )?; - - // Build a simple quad rendering VAO let quad_vao = setup_quad(&gl); let mut be = BACKEND.lock(); be.gl = Some(gl); be.quad_vao = Some(quad_vao); - be.context_wrapper = Some(WrappedContext { - el, - window, - gl_context, - gl_surface, - }); be.backing_buffer = Some(backing_fbo); - be.frame_sleep_time = crate::hal::convert_fps_to_wait(platform_hints.frame_sleep_time); - be.resize_scaling = platform_hints.resize_scaling; + be.frame_sleep_time = crate::hal::convert_fps_to_wait(frame_sleep_time); + be.resize_scaling = resize_scaling; be.screen_scaler = scaler; BACKEND_INTERNAL.lock().shaders = shaders; - let bterm = BTerm { - width_pixels, - height_pixels, - original_width_pixels: width_pixels, - original_height_pixels: height_pixels, - fps: 0.0, - frame_time_ms: 0.0, - active_console: 0, - key: None, - mouse_pos: (0, 0), - left_click: false, - shift: false, - control: false, - alt: false, - web_button: None, - quitting: false, - post_scanlines: false, - post_screenburn: false, - screen_burn_color: bracket_color::prelude::RGB::from_f32(0.0, 1.0, 1.0), - mouse_visible: true, - }; - Ok(bterm) + Ok(NativeRuntime { + window, + gl_context, + gl_surface, + }) } diff --git a/bracket-terminal/src/hal/native/mainloop.rs b/bracket-terminal/src/hal/native/mainloop.rs index 13f1d0f..9ea077f 100755 --- a/bracket-terminal/src/hal/native/mainloop.rs +++ b/bracket-terminal/src/hal/native/mainloop.rs @@ -1,4 +1,6 @@ use super::BACKEND; +use super::NativeInitSettings; +use super::init::{NativeRuntime, init_runtime}; use crate::gl_error_wrap; use crate::hal::*; use crate::prelude::{BACKEND_INTERNAL, BEvent, BTerm, GameState, INPUT}; @@ -10,10 +12,12 @@ use std::num::NonZeroU32; use std::rc::Rc; use std::time::Instant; use winit::{ + application::ApplicationHandler, dpi::PhysicalSize, - event::{ElementState, Event, MouseButton, WindowEvent}, - event_loop::ControlFlow, + event::{ElementState, MouseButton, StartCause, WindowEvent}, + event_loop::{ActiveEventLoop, ControlFlow}, keyboard::{KeyCode, PhysicalKey}, + window::WindowId, }; const TICK_TYPE: ControlFlow = ControlFlow::Poll; @@ -111,194 +115,320 @@ struct ResizeEvent { send_event: bool, } -pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult<()> { - let now = Instant::now(); - let mut prev_seconds = now.elapsed().as_secs(); - let mut prev_ms = now.elapsed().as_millis(); - let mut frames = 0; +pub fn main_loop(bterm: BTerm, gamestate: GS) -> BResult<()> { + let wrap = { BACKEND.lock().context_wrapper.take() }; + let wrap = wrap.ok_or("Native OpenGL context was not initialized")?; - { - let be = BACKEND.lock(); - let gl = be.gl.as_ref().unwrap(); - let mut bit = BACKEND_INTERNAL.lock(); - for f in bit.fonts.iter_mut() { - f.setup_gl_texture(gl)?; - } + let mut app = NativeApp::new(bterm, gamestate, wrap.init); + wrap.el.run_app(&mut app)?; - for s in bit.sprite_sheets.iter_mut() { - let mut f = Font::new(s.filename.to_string(), 1, 1, (1, 1)); - f.setup_gl_texture(gl)?; - s.backing = Some(Rc::new(Box::new(f))); - } + if let Some(error) = app.error { + Err(error) + } else { + Ok(()) } +} - // We're doing a little dance here to get around lifetime/borrow checking. - // Removing the context data from BTerm in an atomic swap, so it isn't borrowed after move. - let wrap = { BACKEND.lock().context_wrapper.take() }; - let unwrap = wrap.unwrap(); +struct NativeApp { + bterm: BTerm, + gamestate: GS, + init: Option, + runtime: Option, + queued_resize_event: Option, + now: Instant, + prev_seconds: u64, + prev_ms: u128, + frames: i32, + error: Option>, + #[cfg(feature = "low_cpu")] + spin_sleeper: spin_sleep::SpinSleeper, +} - let el = unwrap.el; - let window = unwrap.window; - let gl_context = unwrap.gl_context; - let mut gl_surface = unwrap.gl_surface; +impl NativeApp { + fn new(bterm: BTerm, gamestate: GS, init: NativeInitSettings) -> Self { + let now = Instant::now(); + Self { + bterm, + gamestate, + init: Some(init), + runtime: None, + queued_resize_event: None, + now, + prev_seconds: now.elapsed().as_secs(), + prev_ms: now.elapsed().as_millis(), + frames: 0, + error: None, + #[cfg(feature = "low_cpu")] + spin_sleeper: spin_sleep::SpinSleeper::default(), + } + } - on_resize(&mut bterm, window.inner_size(), window.scale_factor(), true)?; // Additional resize to handle some X11 cases + fn fail( + &mut self, + event_loop: &ActiveEventLoop, + error: Box, + ) { + self.error = Some(error); + event_loop.exit(); + } - let mut queued_resize_event: Option = None; - #[cfg(feature = "low_cpu")] - let spin_sleeper = spin_sleep::SpinSleeper::default(); - let my_window_id = window.id(); + fn initialize(&mut self, event_loop: &ActiveEventLoop) -> BResult<()> { + if self.runtime.is_some() { + return Ok(()); + } - #[allow(deprecated)] - el.run(move |event, event_loop| { - event_loop.set_control_flow(TICK_TYPE); - let wait_time = BACKEND.lock().frame_sleep_time.unwrap_or(33); // Hoisted to reduce locks + let init = self + .init + .take() + .ok_or("Native OpenGL context was already initialized")?; + let runtime = init_runtime(event_loop, init)?; + setup_gl_resources()?; + on_resize( + &mut self.bterm, + runtime.window.inner_size(), + runtime.window.scale_factor(), + true, + )?; + self.runtime = Some(runtime); + Ok(()) + } - if bterm.quitting { - event_loop.exit(); + fn handle_window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) -> BResult<()> { + let runtime_window_id = match self.runtime.as_ref() { + Some(runtime) => runtime.window.id(), + None => return Ok(()), + }; + if window_id != runtime_window_id { + return Ok(()); } match event { - Event::AboutToWait => { - window.set_cursor_visible(bterm.mouse_visible); - window.request_redraw(); + WindowEvent::RedrawRequested => self.redraw()?, + WindowEvent::Moved(physical_position) => { + self.bterm.on_event(BEvent::Moved { + new_position: Point::new(physical_position.x, physical_position.y), + }); + self.queue_current_resize(true); } - Event::WindowEvent { window_id, event } => { - if window_id != my_window_id { - return; + WindowEvent::Resized(physical_size) => { + self.queue_resize(physical_size, true); + } + WindowEvent::CloseRequested => { + if !INPUT.lock().use_events { + event_loop.exit(); + } else { + self.bterm.on_event(BEvent::CloseRequested); } - - match event { - WindowEvent::RedrawRequested => { - let frame_timer = Instant::now(); - if window.inner_size().width == 0 { - return; - } - - let execute_ms = now.elapsed().as_millis() as u64 - prev_ms as u64; - if execute_ms >= wait_time { - if let Some(resize) = &queued_resize_event { - resize_surface(&mut gl_surface, &gl_context, resize.physical_size); - on_resize( - &mut bterm, - resize.physical_size, - resize.dpi_scale_factor, - resize.send_event, - ) - .unwrap(); - } - queued_resize_event = None; - - tock( - &mut bterm, - window.scale_factor() as f32, - &mut gamestate, - &mut frames, - &mut prev_seconds, - &mut prev_ms, - &now, - ); - gl_surface.swap_buffers(&gl_context).unwrap(); - clear_input_state(&mut bterm); - } - - let time_since_last_frame = frame_timer.elapsed().as_millis() as u64; - if time_since_last_frame < wait_time { - let delay = u64::min(33, wait_time - time_since_last_frame); - #[cfg(not(feature = "low_cpu"))] - { - std::thread::sleep(std::time::Duration::from_millis(delay)); - } - #[cfg(feature = "low_cpu")] - spin_sleeper.sleep(std::time::Duration::from_millis(delay)); - } - } - WindowEvent::Moved(physical_position) => { - bterm.on_event(BEvent::Moved { - new_position: Point::new(physical_position.x, physical_position.y), - }); - - let scale_factor = window.scale_factor(); - let physical_size = window.inner_size(); - resize_surface(&mut gl_surface, &gl_context, physical_size); - queued_resize_event = Some(ResizeEvent { - physical_size, - dpi_scale_factor: scale_factor, - send_event: true, - }); - } - WindowEvent::Resized(physical_size) => { - let scale_factor = window.scale_factor(); - resize_surface(&mut gl_surface, &gl_context, physical_size); - queued_resize_event = Some(ResizeEvent { - physical_size, - dpi_scale_factor: scale_factor, - send_event: true, - }); - } - WindowEvent::CloseRequested => { - if !INPUT.lock().use_events { - event_loop.exit(); - } else { - bterm.on_event(BEvent::CloseRequested); - } - } - WindowEvent::Focused(focused) => { - bterm.on_event(BEvent::Focused { focused }); - } - WindowEvent::CursorMoved { position: pos, .. } => { - bterm.on_mouse_position(pos.x, pos.y); - } - WindowEvent::CursorEntered { .. } => bterm.on_event(BEvent::CursorEntered), - WindowEvent::CursorLeft { .. } => bterm.on_event(BEvent::CursorLeft), - WindowEvent::MouseInput { button, state, .. } => { - let button = match button { - MouseButton::Left => 0, - MouseButton::Right => 1, - MouseButton::Middle => 2, - MouseButton::Back => 3, - MouseButton::Forward => 4, - MouseButton::Other(num) => 5 + num as usize, - }; - bterm.on_mouse_button(button, state == ElementState::Pressed); - } - WindowEvent::ScaleFactorChanged { - scale_factor, - mut inner_size_writer, - .. - } => { - let physical_size = window.inner_size(); - let _ = inner_size_writer.request_inner_size(physical_size); - resize_surface(&mut gl_surface, &gl_context, physical_size); - on_resize(&mut bterm, physical_size, scale_factor, false).unwrap(); - bterm.on_event(BEvent::ScaleFactorChanged { - new_size: Point::new(physical_size.width, physical_size.height), - dpi_scale_factor: scale_factor as f32, - }) - } - WindowEvent::KeyboardInput { event, .. } => { - if let Some(key) = physical_key_to_virtual_keycode(&event.physical_key) { - bterm.on_key(key, 0, event.state == ElementState::Pressed); - } - if event.state == ElementState::Pressed - && let Some(text) = event.text.as_ref() - { - for ch in text.chars() { - bterm.on_event(BEvent::Character { c: ch }); - } - } - } - WindowEvent::ModifiersChanged(modifiers) => { - bterm.shift = modifiers.state().shift_key(); - bterm.alt = modifiers.state().alt_key(); - bterm.control = modifiers.state().control_key(); + } + WindowEvent::Focused(focused) => { + self.bterm.on_event(BEvent::Focused { focused }); + } + WindowEvent::CursorMoved { position: pos, .. } => { + self.bterm.on_mouse_position(pos.x, pos.y); + } + WindowEvent::CursorEntered { .. } => self.bterm.on_event(BEvent::CursorEntered), + WindowEvent::CursorLeft { .. } => self.bterm.on_event(BEvent::CursorLeft), + WindowEvent::MouseInput { button, state, .. } => { + let button = match button { + MouseButton::Left => 0, + MouseButton::Right => 1, + MouseButton::Middle => 2, + MouseButton::Back => 3, + MouseButton::Forward => 4, + MouseButton::Other(num) => 5 + num as usize, + }; + self.bterm + .on_mouse_button(button, state == ElementState::Pressed); + } + WindowEvent::ScaleFactorChanged { + scale_factor, + mut inner_size_writer, + .. + } => { + let physical_size = self + .runtime + .as_ref() + .map(|runtime| runtime.window.inner_size()) + .unwrap_or_default(); + let _ = inner_size_writer.request_inner_size(physical_size); + self.resize_surface(physical_size); + on_resize(&mut self.bterm, physical_size, scale_factor, false)?; + self.bterm.on_event(BEvent::ScaleFactorChanged { + new_size: Point::new(physical_size.width, physical_size.height), + dpi_scale_factor: scale_factor as f32, + }); + } + WindowEvent::KeyboardInput { event, .. } => { + if let Some(key) = physical_key_to_virtual_keycode(&event.physical_key) { + self.bterm + .on_key(key, 0, event.state == ElementState::Pressed); + } + if event.state == ElementState::Pressed + && let Some(text) = event.text.as_ref() + { + for ch in text.chars() { + self.bterm.on_event(BEvent::Character { c: ch }); } - _ => {} } } + WindowEvent::ModifiersChanged(modifiers) => { + self.bterm.shift = modifiers.state().shift_key(); + self.bterm.alt = modifiers.state().alt_key(); + self.bterm.control = modifiers.state().control_key(); + } _ => {} } - })?; + + Ok(()) + } + + fn redraw(&mut self) -> BResult<()> { + let frame_timer = Instant::now(); + let wait_time = BACKEND.lock().frame_sleep_time.unwrap_or(33); + + if self + .runtime + .as_ref() + .map(|runtime| runtime.window.inner_size().width == 0) + .unwrap_or(true) + { + return Ok(()); + } + + let execute_ms = self.now.elapsed().as_millis() as u64 - self.prev_ms as u64; + if execute_ms >= wait_time { + if let Some(resize) = self.queued_resize_event.take() { + self.resize_surface(resize.physical_size); + on_resize( + &mut self.bterm, + resize.physical_size, + resize.dpi_scale_factor, + resize.send_event, + )?; + } + + let scale_factor = self + .runtime + .as_ref() + .map(|runtime| runtime.window.scale_factor() as f32) + .unwrap_or(1.0); + tock( + &mut self.bterm, + scale_factor, + &mut self.gamestate, + &mut self.frames, + &mut self.prev_seconds, + &mut self.prev_ms, + &self.now, + )?; + + if let Some(runtime) = self.runtime.as_mut() { + runtime.gl_surface.swap_buffers(&runtime.gl_context)?; + } + clear_input_state(&mut self.bterm); + } + + let time_since_last_frame = frame_timer.elapsed().as_millis() as u64; + if time_since_last_frame < wait_time { + let delay = u64::min(33, wait_time - time_since_last_frame); + #[cfg(not(feature = "low_cpu"))] + { + std::thread::sleep(std::time::Duration::from_millis(delay)); + } + #[cfg(feature = "low_cpu")] + self.spin_sleeper + .sleep(std::time::Duration::from_millis(delay)); + } + + Ok(()) + } + + fn queue_current_resize(&mut self, send_event: bool) { + let Some(runtime) = self.runtime.as_ref() else { + return; + }; + let physical_size = runtime.window.inner_size(); + self.queue_resize(physical_size, send_event); + } + + fn queue_resize(&mut self, physical_size: PhysicalSize, send_event: bool) { + let Some(runtime) = self.runtime.as_ref() else { + return; + }; + let scale_factor = runtime.window.scale_factor(); + self.resize_surface(physical_size); + self.queued_resize_event = Some(ResizeEvent { + physical_size, + dpi_scale_factor: scale_factor, + send_event, + }); + } + + fn resize_surface(&mut self, physical_size: PhysicalSize) { + if let Some(runtime) = self.runtime.as_mut() { + resize_surface(&mut runtime.gl_surface, &runtime.gl_context, physical_size); + } + } +} + +impl ApplicationHandler for NativeApp { + fn new_events(&mut self, event_loop: &ActiveEventLoop, _cause: StartCause) { + event_loop.set_control_flow(TICK_TYPE); + if self.bterm.quitting { + event_loop.exit(); + } + } + + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + event_loop.set_control_flow(TICK_TYPE); + if let Err(error) = self.initialize(event_loop) { + self.fail(event_loop, error); + } + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) { + if let Err(error) = self.handle_window_event(event_loop, window_id, event) { + self.fail(event_loop, error); + } + } + + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + event_loop.set_control_flow(TICK_TYPE); + if self.bterm.quitting { + event_loop.exit(); + return; + } + + if let Some(runtime) = self.runtime.as_ref() { + runtime.window.set_cursor_visible(self.bterm.mouse_visible); + runtime.window.request_redraw(); + } + } +} + +fn setup_gl_resources() -> BResult<()> { + let be = BACKEND.lock(); + let gl = be.gl.as_ref().unwrap(); + let mut bit = BACKEND_INTERNAL.lock(); + for f in bit.fonts.iter_mut() { + f.setup_gl_texture(gl)?; + } + + for s in bit.sprite_sheets.iter_mut() { + let mut f = Font::new(s.filename.to_string(), 1, 1, (1, 1)); + f.setup_gl_texture(gl)?; + s.backing = Some(Rc::new(Box::new(f))); + } Ok(()) } @@ -459,7 +589,7 @@ fn tock( prev_seconds: &mut u64, prev_ms: &mut u128, now: &Instant, -) { +) -> BResult<()> { // Check that the console backings match our actual consoles check_console_backing(); @@ -509,7 +639,7 @@ fn tock( gamestate.tick(bterm); // Tell each console to draw itself - render_consoles().unwrap(); + render_consoles()?; // If there is a GL callback, call it now { @@ -601,9 +731,10 @@ fn tock( w, h, image::ColorType::Rgba8, - ) - .expect("Failed to save buffer to the specified path"); + )?; } be.request_screenshot = None; } + + Ok(()) } diff --git a/bracket-terminal/src/hal/native/mod.rs b/bracket-terminal/src/hal/native/mod.rs index 59b7d41..cf053e2 100755 --- a/bracket-terminal/src/hal/native/mod.rs +++ b/bracket-terminal/src/hal/native/mod.rs @@ -51,9 +51,14 @@ unsafe impl Sync for PlatformGL {} pub struct WrappedContext { pub el: winit::event_loop::EventLoop<()>, - pub window: winit::window::Window, - pub gl_context: glutin::context::PossiblyCurrentContext, - pub gl_surface: glutin::surface::Surface, + pub init: NativeInitSettings, +} + +pub struct NativeInitSettings { + pub width_pixels: u32, + pub height_pixels: u32, + pub window_title: String, + pub platform_hints: InitHints, } pub struct InitHints {