Early UI work.
This commit is contained in:
11
lib/ui_platform_wayland/Cargo.toml
Normal file
11
lib/ui_platform_wayland/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ruin_ui_platform_wayland"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
raw-window-handle = "0.6"
|
||||
ruin_ui = { path = "../ui" }
|
||||
wayland-backend = { version = "0.3", features = ["client_system"] }
|
||||
wayland-client = "0.31"
|
||||
wayland-protocols = { version = "0.32", features = ["client"] }
|
||||
271
lib/ui_platform_wayland/src/lib.rs
Normal file
271
lib/ui_platform_wayland/src/lib.rs
Normal file
@@ -0,0 +1,271 @@
|
||||
use std::error::Error;
|
||||
use std::ffi::c_void;
|
||||
use std::num::NonZeroU32;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use raw_window_handle::{
|
||||
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
|
||||
RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, WindowHandle,
|
||||
};
|
||||
use ruin_ui::{UiSize, WindowSpec};
|
||||
use wayland_client::globals::{GlobalListContents, registry_queue_init};
|
||||
use wayland_client::protocol::{wl_compositor, wl_registry, wl_surface};
|
||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle, delegate_noop};
|
||||
use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WaylandSurfaceTarget {
|
||||
connection: Connection,
|
||||
surface: wl_surface::WlSurface,
|
||||
}
|
||||
|
||||
impl HasDisplayHandle for WaylandSurfaceTarget {
|
||||
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
|
||||
let ptr = NonNull::new(self.connection.backend().display_ptr().cast::<c_void>())
|
||||
.ok_or(HandleError::Unavailable)?;
|
||||
let raw = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(ptr));
|
||||
Ok(unsafe { DisplayHandle::borrow_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWindowHandle for WaylandSurfaceTarget {
|
||||
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
|
||||
let ptr = NonNull::new(self.surface.id().as_ptr().cast::<c_void>())
|
||||
.ok_or(HandleError::Unavailable)?;
|
||||
let raw = RawWindowHandle::Wayland(WaylandWindowHandle::new(ptr));
|
||||
Ok(unsafe { WindowHandle::borrow_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct FrameRequest {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub resized: bool,
|
||||
}
|
||||
|
||||
pub struct WaylandWindow {
|
||||
event_queue: wayland_client::EventQueue<State>,
|
||||
surface_target: WaylandSurfaceTarget,
|
||||
state: State,
|
||||
}
|
||||
|
||||
struct State {
|
||||
running: bool,
|
||||
_connection: Connection,
|
||||
_compositor: wl_compositor::WlCompositor,
|
||||
_surface: wl_surface::WlSurface,
|
||||
_xdg_surface: xdg_surface::XdgSurface,
|
||||
_toplevel: xdg_toplevel::XdgToplevel,
|
||||
_wm_base: xdg_wm_base::XdgWmBase,
|
||||
current_size: (u32, u32),
|
||||
configured: bool,
|
||||
pending_size: Option<(u32, u32)>,
|
||||
needs_redraw: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn request_redraw(&mut self) {
|
||||
self.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandWindow {
|
||||
pub fn open(spec: WindowSpec) -> Result<Self, Box<dyn Error>> {
|
||||
let connection = Connection::connect_to_env()?;
|
||||
let (globals, event_queue) = registry_queue_init::<State>(&connection)?;
|
||||
let qh = event_queue.handle();
|
||||
|
||||
let compositor: wl_compositor::WlCompositor = globals.bind(&qh, 4..=6, ())?;
|
||||
let wm_base: xdg_wm_base::XdgWmBase = globals.bind(&qh, 1..=6, ())?;
|
||||
let surface = compositor.create_surface(&qh, ());
|
||||
let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ());
|
||||
let toplevel = xdg_surface.get_toplevel(&qh, ());
|
||||
toplevel.set_title(spec.title.clone());
|
||||
if let Some(app_id) = spec.app_id.as_ref() {
|
||||
toplevel.set_app_id(app_id.clone());
|
||||
}
|
||||
|
||||
apply_size_constraints(&toplevel, &spec);
|
||||
if spec.maximized {
|
||||
toplevel.set_maximized();
|
||||
}
|
||||
if spec.fullscreen {
|
||||
toplevel.set_fullscreen(None);
|
||||
}
|
||||
|
||||
surface.commit();
|
||||
connection.flush()?;
|
||||
|
||||
let initial_size = spec
|
||||
.requested_inner_size
|
||||
.unwrap_or_else(|| UiSize::new(800.0, 500.0));
|
||||
let initial_width = initial_size.width.max(1.0).round() as u32;
|
||||
let initial_height = initial_size.height.max(1.0).round() as u32;
|
||||
let surface_target = WaylandSurfaceTarget {
|
||||
connection: connection.clone(),
|
||||
surface: surface.clone(),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
event_queue,
|
||||
surface_target,
|
||||
state: State {
|
||||
running: true,
|
||||
_connection: connection,
|
||||
_compositor: compositor,
|
||||
_surface: surface,
|
||||
_xdg_surface: xdg_surface,
|
||||
_toplevel: toplevel,
|
||||
_wm_base: wm_base,
|
||||
current_size: (initial_width, initial_height),
|
||||
configured: false,
|
||||
pending_size: None,
|
||||
needs_redraw: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn surface_target(&self) -> WaylandSurfaceTarget {
|
||||
self.surface_target.clone()
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.state.running
|
||||
}
|
||||
|
||||
pub fn dispatch(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
self.event_queue.blocking_dispatch(&mut self.state)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn request_redraw(&mut self) {
|
||||
self.state.request_redraw();
|
||||
}
|
||||
|
||||
pub fn prepare_frame(&mut self) -> Option<FrameRequest> {
|
||||
if !self.state.configured {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((width, height)) = self.state.pending_size.take() {
|
||||
self.state.current_size = (width, height);
|
||||
self.state.needs_redraw = false;
|
||||
return Some(FrameRequest {
|
||||
width,
|
||||
height,
|
||||
resized: true,
|
||||
});
|
||||
}
|
||||
|
||||
if self.state.needs_redraw {
|
||||
self.state.needs_redraw = false;
|
||||
return Some(FrameRequest {
|
||||
width: self.state.current_size.0,
|
||||
height: self.state.current_size.1,
|
||||
resized: false,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &wl_registry::WlRegistry,
|
||||
_event: wl_registry::Event,
|
||||
_data: &GlobalListContents,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
delegate_noop!(State: ignore wl_compositor::WlCompositor);
|
||||
delegate_noop!(State: ignore wl_surface::WlSurface);
|
||||
|
||||
impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
wm_base: &xdg_wm_base::XdgWmBase,
|
||||
event: xdg_wm_base::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let xdg_wm_base::Event::Ping { serial } = event {
|
||||
wm_base.pong(serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<xdg_surface::XdgSurface, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
xdg_surface: &xdg_surface::XdgSurface,
|
||||
event: xdg_surface::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let xdg_surface::Event::Configure { serial } = event {
|
||||
xdg_surface.ack_configure(serial);
|
||||
state.configured = true;
|
||||
state.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &xdg_toplevel::XdgToplevel,
|
||||
event: xdg_toplevel::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
xdg_toplevel::Event::Close => {
|
||||
state.running = false;
|
||||
}
|
||||
xdg_toplevel::Event::Configure {
|
||||
width,
|
||||
height,
|
||||
states: _,
|
||||
} => {
|
||||
let width = NonZeroU32::new(width as u32)
|
||||
.map(NonZeroU32::get)
|
||||
.unwrap_or(state.current_size.0);
|
||||
let height = NonZeroU32::new(height as u32)
|
||||
.map(NonZeroU32::get)
|
||||
.unwrap_or(state.current_size.1);
|
||||
state.pending_size = Some((width, height));
|
||||
state.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_size_constraints(toplevel: &xdg_toplevel::XdgToplevel, spec: &WindowSpec) {
|
||||
if spec.resizable {
|
||||
let min = spec.min_inner_size.unwrap_or_else(|| UiSize::new(0.0, 0.0));
|
||||
let max = spec.max_inner_size.unwrap_or_else(|| UiSize::new(0.0, 0.0));
|
||||
toplevel.set_min_size(min.width.round() as i32, min.height.round() as i32);
|
||||
toplevel.set_max_size(max.width.round() as i32, max.height.round() as i32);
|
||||
return;
|
||||
}
|
||||
|
||||
let fixed = spec
|
||||
.requested_inner_size
|
||||
.or(spec.min_inner_size)
|
||||
.or(spec.max_inner_size)
|
||||
.unwrap_or_else(|| UiSize::new(800.0, 500.0));
|
||||
let width = fixed.width.max(1.0).round() as i32;
|
||||
let height = fixed.height.max(1.0).round() as i32;
|
||||
toplevel.set_min_size(width, height);
|
||||
toplevel.set_max_size(width, height);
|
||||
}
|
||||
Reference in New Issue
Block a user