From ec8149b0847104e9247635d361c9c7a7ba030355 Mon Sep 17 00:00:00 2001 From: Drakulix Date: Tue, 7 Mar 2017 11:53:57 +0100 Subject: [PATCH] Initial glutin backend implementation --- Cargo.toml | 7 +- examples/simple.rs | 36 ++++ src/backend/glium.rs | 38 ++++ src/backend/glutin.rs | 359 +++++++++++++++++++++++++++++++ src/backend/graphics/mod.rs | 6 + src/backend/graphics/opengl.rs | 87 ++++++++ src/backend/graphics/software.rs | 17 ++ src/backend/input.rs | 277 ++++++++++++++++++++++++ src/backend/mod.rs | 28 +++ src/lib.rs | 4 + 10 files changed, 858 insertions(+), 1 deletion(-) create mode 100644 examples/simple.rs create mode 100644 src/backend/glium.rs create mode 100644 src/backend/glutin.rs create mode 100644 src/backend/graphics/mod.rs create mode 100644 src/backend/graphics/opengl.rs create mode 100644 src/backend/graphics/software.rs create mode 100644 src/backend/input.rs create mode 100644 src/backend/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 025a343..c373267 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,16 @@ version = "0.1.0" authors = ["Victor Berger "] [dependencies] -wayland-server = "0.8.4" +wayland-server = { version = "0.8.4", features = ["dlopen"] } nix = "0.7.0" +glutin = { version = "~0.7.4", optional = true } +glium = { version = "~0.16.0", optional = true } slog = { version = "~1.5.2", features = ["max_level_trace", "release_max_level_info"] } slog-stdlog = "~1.1.0" clippy = { version = "*", optional = true } [dev-dependencies] slog-term = "~1.5" + +[features] +default = ["glutin"] diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..c37f0fa --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,36 @@ +extern crate wayland_server; +extern crate smithay; + +use smithay::shm::ShmGlobal; +use smithay::backend::glutin; +use wayland_server::protocol::wl_shm; + +fn main() { + let (_, mut event_loop) = wayland_server::create_display(); + + // Insert the ShmGlobal as a handler to your event loop + // Here, we specify tha the standard Argb8888 and Xrgb8888 is the only supported. + let handler_id = event_loop.add_handler_with_init(ShmGlobal::new( + vec![], + None // we don't provide a logger here + )); + + // Register this handler to advertise a wl_shm global of version 1 + let shm_global = event_loop.register_global::(handler_id, 1); + + // Retrieve the shm token for later use to access the buffers + let shm_token = { + let state = event_loop.state(); + state.get_handler::(handler_id).get_token() + }; + + // Initialize a simple backend for testing + let (mut renderer, mut input) = glutin::init_windowed().unwrap(); + + //TODO render stuff + + //TODO put input handling on the event loop + input.process_new_events(); + + event_loop.run(); +} diff --git a/src/backend/glium.rs b/src/backend/glium.rs new file mode 100644 index 0000000..541ebfe --- /dev/null +++ b/src/backend/glium.rs @@ -0,0 +1,38 @@ +use glium::backend::Backend; +use glium::SwapBuffersError as GliumSwapBuffersError; + +use ::backend::graphics::opengl::{OpenglRenderer, SwapBuffersError}; + +impl From for GliumSwapBuffersError +{ + fn from(error: SwapBuffersError) -> Self { + match error { + SwapBuffersError::ContextLost => GliumSwapBuffersError::ContextLost, + SwapBuffersError::AlreadySwapped => GliumSwapBuffersError::AlreadySwapped, + } + } +} + +impl Backend for T +{ + fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + self.swap_buffers().map_err(|x| x.into) + } + + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void + { + self.get_proc_address(symbol) + } + + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + self.get_framebuffer_dimensions() + } + + fn is_current(&self) -> bool { + self.is_current() + } + + unsafe fn make_current(&self) { + self.make_current() + } +} diff --git a/src/backend/glutin.rs b/src/backend/glutin.rs new file mode 100644 index 0000000..c133e86 --- /dev/null +++ b/src/backend/glutin.rs @@ -0,0 +1,359 @@ +//! Implementation of backend traits for types provided by `glutin` + +use glutin::{ContextError, CreationError, Event, ElementState, MouseScrollDelta, Touch, TouchPhase, GlContext, HeadlessRendererBuilder, HeadlessContext, WindowBuilder, Window}; +use glutin::{Api as GlutinApi, PixelFormat as GlutinPixelFormat, MouseButton as GlutinMouseButton}; +use nix::c_void; +use std::rc::Rc; + +use backend::NewIdType; +use backend::graphics::opengl::{Api, OpenglRenderer, PixelFormat, SwapBuffersError}; +use backend::input::{InputBackend, InputHandler, Seat, KeyState, MouseButton, MouseButtonState, Axis, AxisSource, TouchEvent, TouchSlot}; + +/// Create a new `GlutinHeadlessRenderer` which implements the `OpenglRenderer` graphics +/// backend trait +pub fn init_headless_renderer() -> Result +{ + init_headless_renderer_from_builder(HeadlessRendererBuilder::new(1024, 600)) +} + +/// Create a new `GlutinHeadlessRenderer`, which implements the `OpenglRenderer` graphics +/// backend trait, with a given already configured `HeadlessRendererBuilder` for +/// customization +pub fn init_headless_renderer_from_builder(builder: HeadlessRendererBuilder) -> Result +{ + let (w, h) = builder.dimensions; + let context = builder.build_strict()?; + + Ok(GlutinHeadlessRenderer::new(context, w, h)) +} + +/// Create a new `GlutinWindowedRenderer`, which implements the `OpenglRenderer` graphics +/// backend trait +pub fn init_windowed_renderer() -> Result +{ + init_windowed_renderer_from_builder(WindowBuilder::new()) +} + +/// Create a new `GlutinWindowedRenderer`, which implements the `OpenglRenderer` graphics +/// backend trait, with a given already configured `WindowBuilder` for customization. +pub fn init_windowed_renderer_from_builder(builder: WindowBuilder) -> Result +{ + let window = Rc::new(builder.build_strict()?); + Ok(GlutinWindowedRenderer::new(window)) +} + +/// Create a new `glutin` `Window`. Returns a `GlutinWindowedRenderer` implementing +/// the `OpenglRenderer` graphics backend trait and a `GlutinInputBackend` implementing +/// the `InputBackend` trait. +pub fn init_windowed() -> Result<(GlutinWindowedRenderer, GlutinInputBackend), CreationError> +{ + init_windowed_from_builder(WindowBuilder::new()) +} + +/// Create a new `glutin` `Window` with a given already configured `WindowBuilder` for +/// customization. Returns a `GlutinWindowedRenderer` implementing +/// the `OpenglRenderer` graphics backend trait and a `GlutinInputBackend` implementing +/// the `InputBackend` trait. +pub fn init_windowed_from_builder(builder: WindowBuilder) -> Result<(GlutinWindowedRenderer, GlutinInputBackend), CreationError> +{ + let window = Rc::new(builder.build_strict()?); + Ok(( + GlutinWindowedRenderer::new(window.clone()), + GlutinInputBackend::new(window) + )) +} + +/// Headless Opengl Context created by `glutin`. Implements the `OpenglRenderer` graphics +/// backend trait. +pub struct GlutinHeadlessRenderer +{ + context: HeadlessContext, + w: u32, + h: u32, +} + +impl GlutinHeadlessRenderer +{ + fn new(context: HeadlessContext, w: u32, h: u32) -> GlutinHeadlessRenderer { + GlutinHeadlessRenderer { + context: context, + w: w, + h: h, + } + } +} + +impl OpenglRenderer for GlutinHeadlessRenderer +{ + #[inline] + fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + match self.context.swap_buffers() { + Ok(()) => Ok(()), + Err(ContextError::IoError(e)) => panic!("Error while swapping buffers: {:?}", e), + Err(ContextError::ContextLost) => Err(SwapBuffersError::ContextLost), + } + } + + #[inline] + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.context.get_proc_address(symbol) as *const _ + } + + #[inline] + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + (self.w, self.h) + } + + #[inline] + fn is_current(&self) -> bool { + self.context.is_current() + } + + #[inline] + unsafe fn make_current(&self) { + self.context.make_current().unwrap(); + } + + fn get_api(&self) -> Api { + self.context.get_api().into() + } + + fn get_pixel_format(&self) -> PixelFormat { + self.context.get_pixel_format().into() + } +} + +/// Window with an active Opengl Context created by `glutin`. Implements the +/// `OpenglRenderer` graphics backend trait. +pub struct GlutinWindowedRenderer +{ + window: Rc +} + +impl GlutinWindowedRenderer +{ + fn new(window: Rc) -> GlutinWindowedRenderer { + GlutinWindowedRenderer { + window: window, + } + } +} + +impl OpenglRenderer for GlutinWindowedRenderer +{ + #[inline] + fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + match self.window.swap_buffers() { + Ok(()) => Ok(()), + Err(ContextError::IoError(e)) => panic!("Error while swapping buffers: {:?}", e), + Err(ContextError::ContextLost) => Err(SwapBuffersError::ContextLost), + } + } + + #[inline] + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.window.get_proc_address(symbol) as *const _ + } + + #[inline] + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + let (width, height) = self.window.get_inner_size().unwrap_or((800, 600)); // TODO: 800x600 ? + let scale = self.window.hidpi_factor(); + ((width as f32 * scale) as u32, (height as f32 * scale) as u32) + } + + #[inline] + fn is_current(&self) -> bool { + self.window.is_current() + } + + #[inline] + unsafe fn make_current(&self) { + self.window.make_current().unwrap(); + } + + fn get_api(&self) -> Api { + self.window.get_api().into() + } + + fn get_pixel_format(&self) -> PixelFormat { + self.window.get_pixel_format().into() + } +} + +/// Errors that may happen when driving the event loop of `GlutinInputBackend` +pub enum GlutinInputError +{ + /// The underlying `glutin` `Window` was closed. No further events can be processed. + /// + /// See `GlutinInputBackend::process_new_events`. + WindowClosed +} + +/// Abstracted event loop of a `glutin` `Window` implementing the `InputBackend` trait +/// +/// You need to call `process_new_events` periodically to receive any events. +pub struct GlutinInputBackend +{ + window: Rc, + time_counter: u32, + seat: Seat, + handler: Option>, +} + +impl InputBackend for GlutinInputBackend +{ + fn set_handler(&mut self, mut handler: H) { + if self.handler.is_some() { + self.clear_handler(); + } + handler.on_seat_created(&self.seat); + self.handler = Some(Box::new(handler)); + } + + fn get_handler(&mut self) -> Option<&mut InputHandler> + { + self.handler.as_mut().map(|handler| handler as &mut InputHandler) + } + + fn clear_handler(&mut self) { + if let Some(ref mut handler) = self.handler { + handler.on_seat_destroyed(&self.seat); + } + self.handler = None; + } + + fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()> { + if let Some((win_x, win_y)) = self.window.get_position() { + self.window.set_cursor_position(win_x + x as i32, win_y + y as i32) + } else { + Err(()) + } + } +} + +impl GlutinInputBackend +{ + fn new(window: Rc) -> GlutinInputBackend + { + GlutinInputBackend { + window: window, + time_counter: 0, + seat: Seat::new(0), + handler: None, + } + } + + /// Processes new events of the underlying event loop to drive the set `InputHandler`. + /// + /// You need to periodically call this function to keep the underlying event loop and + /// `Window` active. Otherwise the window may no respond to user interaction and no + /// input events will be received by a set `InputHandler`. + /// + /// Returns an error if the `Window` the window has been closed. Calling + /// `process_new_events` again after the `Window` has been closed is considered an + /// application error and unspecified baviour may occur. + /// + /// The linked `GlutinWindowedRenderer` will error with a lost Context and should + /// not be used anymore as well. + pub fn process_new_events(&mut self) -> Result<(), GlutinInputError> + { + for event in self.window.poll_events() + { + if let Some(ref mut handler) = self.handler { + match event { + Event::KeyboardInput(state, key_code/*TODO: Is this really the keycode? glutins docs don't tell*/, _) => handler.on_keyboard_key(&self.seat, self.time_counter, key_code as u32, state.into(), 1), + Event::MouseMoved(x, y) => handler.on_pointer_move(&self.seat, self.time_counter, (x as u32, y as u32)), + Event::MouseWheel(delta, _) => match delta { + MouseScrollDelta::LineDelta(x, y) => { + if x != 0.0 { + handler.on_pointer_scroll(&self.seat, self.time_counter, Axis::Horizontal, AxisSource::Wheel, x as f64); + } + if y != 0.0 { + handler.on_pointer_scroll(&self.seat, self.time_counter, Axis::Vertical, AxisSource::Wheel, y as f64); + } + }, + MouseScrollDelta::PixelDelta(x, y) => { + if x != 0.0 { + handler.on_pointer_scroll(&self.seat, self.time_counter, Axis::Vertical, AxisSource::Continous, x as f64); + } + if y != 0.0 { + handler.on_pointer_scroll(&self.seat, self.time_counter, Axis::Horizontal, AxisSource::Continous, y as f64); + } + }, + }, + Event::MouseInput(state, button) => handler.on_pointer_button(&self.seat, self.time_counter, button.into(), state.into()), + Event::Touch(Touch { phase: TouchPhase::Started, location: (x, y), id}) => handler.on_touch(&self.seat, self.time_counter, TouchEvent::Down { slot: Some(TouchSlot::new(id as u32)), x: x, y: y }), + Event::Touch(Touch { phase: TouchPhase::Moved, location: (x, y), id}) => handler.on_touch(&self.seat, self.time_counter, TouchEvent::Motion { slot: Some(TouchSlot::new(id as u32)), x: x, y: y }), + Event::Touch(Touch { phase: TouchPhase::Ended, location: (x, y), id }) => { + handler.on_touch(&self.seat, self.time_counter, TouchEvent::Motion { slot: Some(TouchSlot::new(id as u32)), x: x, y: y }); + handler.on_touch(&self.seat, self.time_counter, TouchEvent::Up { slot: Some(TouchSlot::new(id as u32)) }); + } + Event::Touch(Touch { phase: TouchPhase::Cancelled, id, ..}) => handler.on_touch(&self.seat, self.time_counter, TouchEvent::Cancel { slot: Some(TouchSlot::new(id as u32)) }), + Event::Closed => return Err(GlutinInputError::WindowClosed), + _ => {}, + } + self.time_counter += 1; + } + } + Ok(()) + } +} + +impl From for Api { + fn from(api: GlutinApi) -> Self { + match api { + GlutinApi::OpenGl => Api::OpenGl, + GlutinApi::OpenGlEs => Api::OpenGlEs, + GlutinApi::WebGl => Api::WebGl, + } + } +} + +impl From for PixelFormat { + fn from(format: GlutinPixelFormat) -> Self { + PixelFormat { + hardware_accelerated: format.hardware_accelerated, + color_bits: format.color_bits, + alpha_bits: format.alpha_bits, + depth_bits: format.depth_bits, + stencil_bits: format.stencil_bits, + stereoscopy: format.stereoscopy, + double_buffer: format.double_buffer, + multisampling: format.multisampling, + srgb: format.srgb, + } + } +} + +impl From for MouseButton +{ + fn from(button: GlutinMouseButton) -> MouseButton { + match button { + GlutinMouseButton::Left => MouseButton::Left, + GlutinMouseButton::Right => MouseButton::Right, + GlutinMouseButton::Middle => MouseButton::Middle, + GlutinMouseButton::Other(num) => MouseButton::Other(num), + } + } +} + +impl From for KeyState +{ + fn from(state: ElementState) -> Self { + match state { + ElementState::Pressed => KeyState::Pressed, + ElementState::Released => KeyState::Released, + } + } +} + +impl From for MouseButtonState +{ + fn from(state: ElementState) -> Self { + match state { + ElementState::Pressed => MouseButtonState::Pressed, + ElementState::Released => MouseButtonState::Released, + } + } +} diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs new file mode 100644 index 0000000..75b2ee6 --- /dev/null +++ b/src/backend/graphics/mod.rs @@ -0,0 +1,6 @@ +//! Common traits for various ways to renderer on a given graphics backend. +//! +//! Note: Not every api may be supported by every backend + +pub mod software; +pub mod opengl; diff --git a/src/backend/graphics/opengl.rs b/src/backend/graphics/opengl.rs new file mode 100644 index 0000000..e29521b --- /dev/null +++ b/src/backend/graphics/opengl.rs @@ -0,0 +1,87 @@ +//! Common traits and types for opengl rendering on graphics backends + +use nix::c_void; + +/// Error that can happen when swapping buffers. +#[derive(Debug, Clone)] +pub enum SwapBuffersError { + /// The OpenGL context has been lost and needs to be recreated. + /// + /// All the objects associated to it (textures, buffers, programs, etc.) + /// need to be recreated from scratch. + /// + /// Operations will have no effect. Functions that read textures, buffers, etc. + /// from OpenGL will return uninitialized data instead. + /// + /// A context loss usually happens on mobile devices when the user puts the + /// application on sleep and wakes it up later. However any OpenGL implementation + /// can theoretically lose the context at any time. + ContextLost, + /// The buffers have already been swapped. + /// + /// This error can be returned when `swap_buffers` has been called multiple times + AlreadySwapped, +} + +/// All APIs related to OpenGL that you can possibly get +/// through OpenglRenderer implementations +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Api { + /// The classical OpenGL. Available on Windows, Linux, OS/X. + OpenGl, + /// OpenGL embedded system. Available on Linux, Android. + OpenGlEs, + /// OpenGL for the web. Very similar to OpenGL ES. + WebGl, +} + +/// Describes the pixel format of the main framebuffer +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PixelFormat { + /// Is the format hardware accelerated + pub hardware_accelerated: bool, + /// bits used for colors + pub color_bits: u8, + /// bits used for alpha channel + pub alpha_bits: u8, + /// bits used for depth channel + pub depth_bits: u8, + /// bits used for stencil buffer + pub stencil_bits: u8, + /// is stereoscopy enabled + pub stereoscopy: bool, + /// is double buffering enabled + pub double_buffer: bool, + /// multisampling format used if enabled + pub multisampling: Option, + /// if the format has srgb enabled + pub srgb: bool, +} + +/// Trait that describes objects that have an OpenGl context +/// and can be used to render upon +pub trait OpenglRenderer +{ + /// Swaps buffers at the end of a frame. + fn swap_buffers(&self) -> Result<(), SwapBuffersError>; + + /// Returns the address of an OpenGL function. + /// + /// Supposes that the context has been made current before this function is called. + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void; + + /// Returns the dimensions of the window, or screen, etc. + fn get_framebuffer_dimensions(&self) -> (u32, u32); + + /// Returns true if the OpenGL context is the current one in the thread. + fn is_current(&self) -> bool; + + /// Makes the OpenGL context the current context in the current thread. + unsafe fn make_current(&self); + + /// Returns the OpenGL API being used. + fn get_api(&self) -> Api; + + /// Returns the pixel format of the main framebuffer of the context. + fn get_pixel_format(&self) -> PixelFormat; +} diff --git a/src/backend/graphics/software.rs b/src/backend/graphics/software.rs new file mode 100644 index 0000000..d8bb6e0 --- /dev/null +++ b/src/backend/graphics/software.rs @@ -0,0 +1,17 @@ +//! Common traits and types used for software rendering on graphics backends + +use wayland_server::protocol::wl_shm::Format; +use std::error::Error; + +/// Trait that describes objects providing a software rendering implementation +pub trait CpuRender { + /// Render a given buffer of a given format at a specified place in the framebuffer + /// + /// # Error + /// Returns an error if the buffer size does not match the required amount of pixels + /// for the given size or if the position and size is out of scope of the framebuffer. + fn render(&mut self, buffer: &[u8], format: Format, at: (u32, u32), size: (u32, u32)) -> Result<(), E>; + + /// Returns the dimensions of the Framebuffer + fn get_framebuffer_dimensions(&self) -> (u32, u32); +} diff --git a/src/backend/input.rs b/src/backend/input.rs new file mode 100644 index 0000000..d5712f4 --- /dev/null +++ b/src/backend/input.rs @@ -0,0 +1,277 @@ +//! Common traits for input backends to receive input from. + +use backend::NewIdType; + +/// A seat describes a group of input devices and at least one +/// graphics device belonging together. +/// +/// By default only one seat exists for most systems and smithay backends +/// however multiseat configurations are possible and should be treated as +/// separated users, all with their own focus, input and cursor available. +/// +/// Seats can be checked for equality and hashed for differentiation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Seat { id: u32 } + +impl NewIdType for Seat +{ + fn new(id: u32) -> Seat + { + Seat { id: id } + } +} + +/// State of key on a keyboard. Either pressed or released +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum KeyState { + /// Key is released + Released, + /// Key is pressed + Pressed, +} + +/// A particular mouse button +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum MouseButton { + /// Left mouse button + Left, + /// Middle mouse button + Middle, + /// Right mouse button + Right, + /// Other mouse button with index + Other(u8), +} + +/// State of a button on a mouse. Either pressed or released +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum MouseButtonState { + /// Button is released + Released, + /// Button is pressed + Pressed, +} + +/// Axis when scrolling +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Axis +{ + /// Vertical axis + Vertical, + /// Horizonal axis + Horizontal, +} + +/// Source of an axis when scrolling +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum AxisSource +{ + /// Finger. Mostly used for trackpads. + /// + /// Guarantees that a scroll sequence is terminated with a scroll value of 0. + /// A caller may use this information to decide on whether kinetic scrolling should + /// be triggered on this scroll sequence. + /// + /// The coordinate system is identical to the + /// cursor movement, i.e. a scroll value of 1 represents the equivalent relative + /// motion of 1. + Finger, + /// Continous scrolling device. Almost identical to `Finger` + /// + /// No terminating event is guaranteed (though it may happen). + /// + /// The coordinate system is identical to + /// the cursor movement, i.e. a scroll value of 1 represents the equivalent relative + /// motion of 1. + Continous, + /// Scroll wheel. + /// + /// No terminating event is guaranteed (though it may happen). Scrolling is in + /// discrete steps. It is up to the caller how to interpret such different step sizes. + Wheel, + /// Scrolling through tilting the scroll wheel. + /// + /// No terminating event is guaranteed (though it may happen). Scrolling is in + /// discrete steps. It is up to the caller how to interpret such different step sizes. + WheelTilt, +} + +/// Slot of a different touch event. +/// +/// Touch events are groubed by slots, usually to identify different +/// fingers on a multi-touch enabled input device. Events should only +/// be interpreted in the context of other events on the same slot. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TouchSlot { id: u32 } + +impl NewIdType for TouchSlot +{ + fn new(id: u32) -> TouchSlot + { + TouchSlot { id: id } + } +} + +/// Touch event +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum TouchEvent +{ + /// The start of an event at a given position (x, y). + /// + /// If the device has multi-touch capabilities a slot is given. + Down { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option, + /// Absolute x-coordinate of the touch position. + x: f64, + /// Absolute y-coordinate of the touch position. + y: f64 + }, + /// Movement of a touch on the device surface to a given position (x, y). + /// + /// If the device has multi-touch capabilities a slot is given. + Motion { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option, + /// Absolute x-coordinate of the final touch position after the motion. + x: f64, + /// Absolute y-coordinate of the final touch position after the motion. + y: f64 }, + /// Stop of an event chain. + /// + /// If the device has multi-touch capabilities a slot is given. + Up { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option + }, + /// Cancel of an event chain. All previous events in the chain should be ignored. + /// + /// If the device has multi-touch capabilities a slot is given. + Cancel { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option + }, + /// Signals the end of a set of touchpoints at one device sample time. + Frame +} + +/// Trait that describes objects providing a source of input events. All input backends +/// need to implemenent this and provide the same base gurantees about the presicion of +/// given events. +pub trait InputBackend { + /// Sets a new handler for this `InputBackend` + fn set_handler(&mut self, handler: H); + /// Get a reference to the currently set handler, if any + fn get_handler(&mut self) -> Option<&mut InputHandler>; + /// Clears the currently handler, if one is set + fn clear_handler(&mut self); + /// Sets the cursor position, useful for e.g. pointer wrapping. + /// + /// Not guaranteed to be supported on every backend. The result usually + /// depends on the capability of the backend, but may also fail for certain + /// specific actions. See the backends documentation. + fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()>; +} + +/// Implement to receive input events from any `InputBackend`. +pub trait InputHandler { + /// Called when a new `Seat` has been created + fn on_seat_created(&mut self, seat: &Seat); + /// Called when an existing `Seat` has been destroyed. + fn on_seat_destroyed(&mut self, seat: &Seat); + /// Called when a new keyboard event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `key_code` - Code of the pressed key. See linux/input-event-codes.h + /// - `state` - `KeyState` of the event + /// - `count` - Amount of key presses registered + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_keyboard_key(&mut self, seat: &Seat, time: u32, key_code: u32, state: KeyState, count: u32); + /// Called when a new keyboard event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `to` - Absolute screen coordinates of the pointer moved to. + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_pointer_move(&mut self, seat: &Seat, time: u32, to: (u32, u32)); + /// Called when a new keyboard event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `button` - Which button was pressed.. + /// - `state` - `MouseButtonState` of the event + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_pointer_button(&mut self, seat: &Seat, time: u32, button: MouseButton, state: MouseButtonState); + /// Called when a new keyboard event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `axis` - `Axis` this event was generated for. + /// - `source` - Source of the scroll event. Important for interpretation of `amount`. + /// - `amount` - Amount of scrolling on the given `Axis`. See `source` for interpretation. + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_pointer_scroll(&mut self, seat: &Seat, time: u32, axis: Axis, source: AxisSource, amount: f64); + /// Called when a new keyboard event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `event` - Touch event recieved. See `TouchEvent`. + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_touch(&mut self, seat: &Seat, time: u32, event: TouchEvent); +} + +impl InputHandler for Box { + fn on_seat_created(&mut self, seat: &Seat) { + (**self).on_seat_created(seat) + } + + fn on_seat_destroyed(&mut self, seat: &Seat) { + (**self).on_seat_destroyed(seat) + } + + fn on_keyboard_key(&mut self, seat: &Seat, time: u32, key_code: u32, state: KeyState, count: u32) { + (**self).on_keyboard_key(seat, time, key_code, state, count) + } + + fn on_pointer_move(&mut self, seat: &Seat, time: u32, to: (u32, u32)) { + (**self).on_pointer_move(seat, time, to) + } + + fn on_pointer_button(&mut self, seat: &Seat, time: u32, button: MouseButton, state: MouseButtonState) { + (**self).on_pointer_button(seat, time, button, state) + } + + fn on_pointer_scroll(&mut self, seat: &Seat, time: u32, axis: Axis, source: AxisSource, amount: f64) { + (**self).on_pointer_scroll(seat, time, axis, source, amount) + } + + fn on_touch(&mut self, seat: &Seat, time: u32, event: TouchEvent) { + (**self).on_touch(seat, time, event) + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..1793dde --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,28 @@ +//! Backend (rendering/input) creation helpers +//! +//! Collection of common traits and implementation about +//! rendering onto various targets and receiving input +//! from various sources. +//! +//! Supported graphics backends: +//! +//! - glutin (headless/windowed) +//! +//! Supported input backends: +//! +//! - glutin (windowed) + +pub mod input; +pub mod graphics; + +#[cfg(feature = "glutin")] +pub mod glutin; + +#[cfg(feature = "glium")] +mod glium; +#[cfg(feature = "glium")] +pub use glium::*; + +trait NewIdType { + fn new(id: u32) -> Self; +} diff --git a/src/lib.rs b/src/lib.rs index b4eed74..1bb9ed5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,12 @@ extern crate wayland_server; extern crate nix; +#[cfg(feature = "glutin")] +extern crate glutin; + #[macro_use] extern crate slog; extern crate slog_stdlog; pub mod shm; +pub mod backend;