mirror of
https://github.com/danbulant/appit
synced 2026-06-19 22:31:23 +00:00
Initial commit.
This commit is contained in:
commit
3f099070c2
7 changed files with 1480 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "appit"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[dependencies]
|
||||
winit = "0.28.6"
|
||||
raw-window-handle = "0.5.1"
|
||||
40
README.md
Normal file
40
README.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# appit
|
||||
|
||||
An opinionated wrapper for `winit` that provides a trait-based approach to
|
||||
implementing multi-window applications.
|
||||
|
||||
This crate's main type is `WindowBehavior`, a trait that provides functions for
|
||||
nearly every `winit::event::WindowEvent`. This allows you to implement exactly
|
||||
which events you wish to respond to, and ignore the rest without a large match
|
||||
statement.
|
||||
|
||||
This crate also keeps track of the redraw state of the window, and allows
|
||||
scheduling redraws in the future.
|
||||
|
||||
```rust,no_run
|
||||
use appit::WindowBehavior;
|
||||
|
||||
struct MyWindow;
|
||||
|
||||
impl WindowBehavior for MyWindow {
|
||||
type Context = ();
|
||||
|
||||
fn initialize(_window: &mut appit::RunningWindow, _context: Self::Context) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn redraw(&mut self, window: &mut appit::RunningWindow) {
|
||||
println!("Should redraw");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
MyWindow::run()
|
||||
}
|
||||
```
|
||||
|
||||
## Why not use this crate?
|
||||
|
||||
- Very new, largely untested.
|
||||
- Not all platforms support threads, and a single-window, single-thread code
|
||||
path is not supported yet.
|
||||
1
src/event_loop.rs
Normal file
1
src/event_loop.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub struct App {}
|
||||
220
src/lib.rs
Normal file
220
src/lib.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs, clippy::pedantic)]
|
||||
#![deny(unsafe_code)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
mod private;
|
||||
mod window;
|
||||
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
pub use window::{RunningWindow, Window, WindowBehavior, WindowBuilder};
|
||||
|
||||
use winit::error::OsError;
|
||||
use winit::window::WindowId;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{mpsc, Arc, Mutex, PoisonError};
|
||||
use winit::event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget};
|
||||
use winit::{event::Event, event_loop::EventLoop};
|
||||
|
||||
use crate::private::{AppMessage, WindowEvent, WindowMessage};
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
/// An application that is not yet running.
|
||||
pub struct PendingApp {
|
||||
event_loop: EventLoop<AppMessage>,
|
||||
running: App,
|
||||
}
|
||||
|
||||
impl Default for PendingApp {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingApp {
|
||||
/// Returns a new app with no windows. If no windows are opened before the
|
||||
/// app is run, the app will immediately close.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
let event_loop = EventLoopBuilder::with_user_event().build();
|
||||
let proxy = event_loop.create_proxy();
|
||||
Self {
|
||||
event_loop,
|
||||
running: App {
|
||||
proxy,
|
||||
windows: Windows::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Begins running the application. This function will never return.
|
||||
///
|
||||
/// Internally this runs the [`EventLoop`].
|
||||
pub fn run(self) -> ! {
|
||||
self.event_loop.run(move |event, target, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
let event = WindowEvent::from(event);
|
||||
self.running
|
||||
.windows
|
||||
.send(window_id, WindowMessage::Event(event));
|
||||
}
|
||||
Event::RedrawRequested(window_id) => {
|
||||
self.running.windows.send(window_id, WindowMessage::Redraw);
|
||||
}
|
||||
Event::UserEvent(message) => match message {
|
||||
AppMessage::CloseWindow(window_id) => {
|
||||
if self.running.windows.close(window_id) {
|
||||
*control_flow = ControlFlow::ExitWithCode(0);
|
||||
}
|
||||
}
|
||||
AppMessage::OpenWindow {
|
||||
attrs,
|
||||
sender,
|
||||
open_sender,
|
||||
} => {
|
||||
let result = self.running.windows.open(target, attrs, sender);
|
||||
let _result = open_sender.send(result);
|
||||
}
|
||||
},
|
||||
Event::NewEvents(_)
|
||||
| Event::DeviceEvent { .. }
|
||||
| Event::Suspended
|
||||
| Event::Resumed
|
||||
| Event::MainEventsCleared
|
||||
| Event::RedrawEventsCleared
|
||||
| Event::LoopDestroyed => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a multi-window application.
|
||||
#[derive(Clone)]
|
||||
pub struct App {
|
||||
proxy: EventLoopProxy<AppMessage>,
|
||||
windows: Windows,
|
||||
}
|
||||
|
||||
/// A type that has a handle to the application thread.
|
||||
pub trait Application: private::ApplicationSealed {}
|
||||
|
||||
impl Application for PendingApp {}
|
||||
|
||||
impl private::ApplicationSealed for PendingApp {
|
||||
fn app(&self) -> App {
|
||||
self.running.clone()
|
||||
}
|
||||
|
||||
fn open(
|
||||
&self,
|
||||
window: WindowAttributes,
|
||||
sender: mpsc::SyncSender<WindowMessage>,
|
||||
) -> Result<Option<Arc<winit::window::Window>>, OsError> {
|
||||
self.running
|
||||
.windows
|
||||
.open(&self.event_loop, window, sender)
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct Windows {
|
||||
data: Arc<Mutex<HashMap<WindowId, OpenWindow>>>,
|
||||
}
|
||||
|
||||
impl Windows {
|
||||
fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> {
|
||||
let windows = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
windows.get(&id).map(|w| w.winit.clone())
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn open(
|
||||
&self,
|
||||
target: &EventLoopWindowTarget<AppMessage>,
|
||||
attrs: WindowAttributes,
|
||||
sender: mpsc::SyncSender<WindowMessage>,
|
||||
) -> Result<Arc<winit::window::Window>, OsError> {
|
||||
let mut builder = winit::window::WindowBuilder::new()
|
||||
.with_active(attrs.active)
|
||||
.with_resizable(attrs.resizable)
|
||||
.with_enabled_buttons(attrs.enabled_buttons)
|
||||
.with_title(attrs.title)
|
||||
.with_maximized(attrs.maximized)
|
||||
.with_visible(attrs.visible)
|
||||
.with_transparent(attrs.transparent)
|
||||
.with_decorations(attrs.decorations)
|
||||
.with_window_level(attrs.window_level)
|
||||
.with_content_protected(attrs.content_protected)
|
||||
.with_fullscreen(attrs.fullscreen)
|
||||
.with_window_icon(attrs.window_icon)
|
||||
.with_theme(attrs.preferred_theme);
|
||||
|
||||
if let Some(inner_size) = attrs.inner_size {
|
||||
builder = builder.with_inner_size(inner_size);
|
||||
}
|
||||
if let Some(min_inner_size) = attrs.min_inner_size {
|
||||
builder = builder.with_min_inner_size(min_inner_size);
|
||||
}
|
||||
if let Some(max_inner_size) = attrs.max_inner_size {
|
||||
builder = builder.with_max_inner_size(max_inner_size);
|
||||
}
|
||||
if let Some(position) = attrs.position {
|
||||
builder = builder.with_position(position);
|
||||
}
|
||||
if let Some(resize_increments) = attrs.resize_increments {
|
||||
builder = builder.with_resize_increments(resize_increments);
|
||||
}
|
||||
let mut windows = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
if let Some(parent_window) = attrs.parent_window {
|
||||
let parent_window = windows
|
||||
.get(&parent_window.id())
|
||||
.expect("invalid parent window");
|
||||
// SAFETY: The only way for us to resolve to a winit Window is for
|
||||
// the window to still be in our list of open windows. This
|
||||
// guarantees that the window handle is still valid.
|
||||
unsafe {
|
||||
builder = builder.with_parent_window(Some(parent_window.winit.raw_window_handle()));
|
||||
}
|
||||
}
|
||||
let winit = Arc::new(builder.build(target)?);
|
||||
windows.insert(
|
||||
winit.id(),
|
||||
OpenWindow {
|
||||
winit: winit.clone(),
|
||||
sender,
|
||||
},
|
||||
);
|
||||
Ok(winit)
|
||||
}
|
||||
|
||||
pub fn send(&self, window: WindowId, message: WindowMessage) {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
if let Some(open_window) = data.get(&window) {
|
||||
match open_window.sender.try_send(message) {
|
||||
Ok(()) => {}
|
||||
Err(mpsc::TrySendError::Full(_)) => {
|
||||
eprintln!("Dropping event for {window:?}.");
|
||||
}
|
||||
Err(mpsc::TrySendError::Disconnected(_)) => {
|
||||
// Window no longer active, remove it.
|
||||
data.remove(&window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&self, window: WindowId) -> bool {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
data.remove(&window);
|
||||
data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
struct OpenWindow {
|
||||
winit: Arc<winit::window::Window>,
|
||||
sender: mpsc::SyncSender<WindowMessage>,
|
||||
}
|
||||
335
src/private.rs
Normal file
335
src/private.rs
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use winit::error::OsError;
|
||||
use winit::event::{
|
||||
AxisId, DeviceId, ElementState, Ime, KeyboardInput, ModifiersState, MouseButton,
|
||||
MouseScrollDelta, Touch, TouchPhase,
|
||||
};
|
||||
use winit::window::{Theme, WindowId};
|
||||
|
||||
use crate::window::WindowAttributes;
|
||||
use crate::App;
|
||||
|
||||
pub trait ApplicationSealed {
|
||||
fn app(&self) -> App;
|
||||
fn open(
|
||||
&self,
|
||||
window: WindowAttributes,
|
||||
sender: mpsc::SyncSender<WindowMessage>,
|
||||
) -> Result<Option<Arc<winit::window::Window>>, OsError>;
|
||||
}
|
||||
|
||||
pub enum AppMessage {
|
||||
OpenWindow {
|
||||
attrs: WindowAttributes,
|
||||
sender: mpsc::SyncSender<WindowMessage>,
|
||||
open_sender: mpsc::SyncSender<Result<Arc<winit::window::Window>, OsError>>,
|
||||
},
|
||||
CloseWindow(WindowId),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WindowMessage {
|
||||
Redraw,
|
||||
Event(WindowEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WindowEvent {
|
||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||
Resized(PhysicalSize<u32>),
|
||||
|
||||
/// The position of the window has changed. Contains the window's new position.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Wayland:** Unsupported.
|
||||
Moved(PhysicalPosition<i32>),
|
||||
|
||||
/// The window has been requested to close.
|
||||
CloseRequested,
|
||||
|
||||
/// The window has been destroyed.
|
||||
Destroyed,
|
||||
|
||||
/// A file has been dropped into the window.
|
||||
///
|
||||
/// When the user drops multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
DroppedFile(PathBuf),
|
||||
|
||||
/// A file is being hovered over the window.
|
||||
///
|
||||
/// When the user hovers multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
HoveredFile(PathBuf),
|
||||
|
||||
/// A file was hovered, but has exited the window.
|
||||
///
|
||||
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
|
||||
/// hovered.
|
||||
HoveredFileCancelled,
|
||||
|
||||
/// The window received a unicode character.
|
||||
///
|
||||
/// See also the [`Ime`](Self::Ime) event for more complex character sequences.
|
||||
ReceivedCharacter(char),
|
||||
|
||||
/// The window gained or lost focus.
|
||||
///
|
||||
/// The parameter is true if the window has gained focus, and false if it has lost focus.
|
||||
Focused(bool),
|
||||
|
||||
/// An event from the keyboard has been received.
|
||||
KeyboardInput {
|
||||
device_id: DeviceId,
|
||||
input: KeyboardInput,
|
||||
/// If `true`, the event was generated synthetically by winit
|
||||
/// in one of the following circumstances:
|
||||
///
|
||||
/// * Synthetic key press events are generated for all keys pressed
|
||||
/// when a window gains focus. Likewise, synthetic key release events
|
||||
/// are generated for all keys pressed when a window goes out of focus.
|
||||
/// ***Currently, this is only functional on X11 and Windows***
|
||||
///
|
||||
/// Otherwise, this value is always `false`.
|
||||
is_synthetic: bool,
|
||||
},
|
||||
|
||||
/// The keyboard modifiers have changed.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** This API is currently unimplemented on the web. This isn't by design - it's an
|
||||
/// issue, and it should get fixed - but it's the current state of the API.
|
||||
ModifiersChanged(ModifiersState),
|
||||
|
||||
/// An event from an input method.
|
||||
///
|
||||
/// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web:** Unsupported.
|
||||
Ime(Ime),
|
||||
|
||||
/// The cursor has moved on the window.
|
||||
CursorMoved {
|
||||
device_id: DeviceId,
|
||||
|
||||
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
|
||||
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
|
||||
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
|
||||
position: PhysicalPosition<f64>,
|
||||
},
|
||||
|
||||
/// The cursor has entered the window.
|
||||
CursorEntered {
|
||||
device_id: DeviceId,
|
||||
},
|
||||
|
||||
/// The cursor has left the window.
|
||||
CursorLeft {
|
||||
device_id: DeviceId,
|
||||
},
|
||||
|
||||
/// A mouse wheel movement or touchpad scroll occurred.
|
||||
MouseWheel {
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
},
|
||||
|
||||
/// An mouse button press has been received.
|
||||
MouseInput {
|
||||
device_id: DeviceId,
|
||||
state: ElementState,
|
||||
button: MouseButton,
|
||||
},
|
||||
|
||||
/// Touchpad pressure event.
|
||||
///
|
||||
/// At the moment, only supported on Apple forcetouch-capable macbooks.
|
||||
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
|
||||
/// is being pressed) and stage (integer representing the click level).
|
||||
TouchpadPressure {
|
||||
device_id: DeviceId,
|
||||
pressure: f32,
|
||||
stage: i64,
|
||||
},
|
||||
|
||||
/// Motion on some analog axis. May report data redundant to other, more specific events.
|
||||
AxisMotion {
|
||||
device_id: DeviceId,
|
||||
axis: AxisId,
|
||||
value: f64,
|
||||
},
|
||||
|
||||
/// Touch event has been received
|
||||
Touch(Touch),
|
||||
|
||||
/// The window's scale factor has changed.
|
||||
///
|
||||
/// The following user actions can cause DPI changes:
|
||||
///
|
||||
/// * Changing the display's resolution.
|
||||
/// * Changing the display's scale factor (e.g. in Control Panel on Windows).
|
||||
/// * Moving the window to a display with a different scale factor.
|
||||
///
|
||||
/// After this event callback has been processed, the window will be resized to whatever value
|
||||
/// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested
|
||||
/// by the OS, but it can be changed to any value.
|
||||
///
|
||||
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
|
||||
ScaleFactorChanged {
|
||||
scale_factor: f64,
|
||||
new_inner_size: PhysicalSize<u32>,
|
||||
},
|
||||
|
||||
/// The system window theme has changed.
|
||||
///
|
||||
/// Applications might wish to react to this to change the theme of the content of the window
|
||||
/// when the system changes the window theme.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// At the moment this is only supported on Windows.
|
||||
ThemeChanged(Theme),
|
||||
|
||||
/// The window has been occluded (completely hidden from view).
|
||||
///
|
||||
/// This is different to window visibility as it depends on whether the window is closed,
|
||||
/// minimised, set invisible, or fully occluded by another window.
|
||||
///
|
||||
/// Platform-specific behavior:
|
||||
/// - **iOS / Android / Web / Wayland / Windows:** Unsupported.
|
||||
Occluded(bool),
|
||||
|
||||
TouchpadMagnify {
|
||||
device_id: DeviceId,
|
||||
delta: f64,
|
||||
phase: TouchPhase,
|
||||
},
|
||||
SmartMagnify {
|
||||
device_id: DeviceId,
|
||||
},
|
||||
TouchpadRotate {
|
||||
device_id: DeviceId,
|
||||
delta: f32,
|
||||
phase: TouchPhase,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<winit::event::WindowEvent<'a>> for WindowEvent {
|
||||
#[allow(clippy::too_many_lines)] // it's a match statement
|
||||
fn from(event: winit::event::WindowEvent<'a>) -> Self {
|
||||
match event {
|
||||
winit::event::WindowEvent::Resized(size) => Self::Resized(size),
|
||||
winit::event::WindowEvent::Moved(pos) => Self::Moved(pos),
|
||||
winit::event::WindowEvent::CloseRequested => Self::CloseRequested,
|
||||
winit::event::WindowEvent::Destroyed => Self::Destroyed,
|
||||
winit::event::WindowEvent::DroppedFile(path) => Self::DroppedFile(path),
|
||||
winit::event::WindowEvent::HoveredFile(path) => Self::HoveredFile(path),
|
||||
winit::event::WindowEvent::HoveredFileCancelled => Self::HoveredFileCancelled,
|
||||
winit::event::WindowEvent::ReceivedCharacter(ch) => Self::ReceivedCharacter(ch),
|
||||
winit::event::WindowEvent::Focused(focused) => Self::Focused(focused),
|
||||
winit::event::WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
input,
|
||||
is_synthetic,
|
||||
} => Self::KeyboardInput {
|
||||
device_id,
|
||||
input,
|
||||
is_synthetic,
|
||||
},
|
||||
|
||||
winit::event::WindowEvent::ModifiersChanged(modifiers) => {
|
||||
Self::ModifiersChanged(modifiers)
|
||||
}
|
||||
winit::event::WindowEvent::Ime(ime) => Self::Ime(ime),
|
||||
winit::event::WindowEvent::CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
..
|
||||
} => Self::CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
},
|
||||
winit::event::WindowEvent::CursorEntered { device_id } => {
|
||||
Self::CursorEntered { device_id }
|
||||
}
|
||||
winit::event::WindowEvent::CursorLeft { device_id } => Self::CursorLeft { device_id },
|
||||
winit::event::WindowEvent::MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
..
|
||||
} => Self::MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
},
|
||||
winit::event::WindowEvent::MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
..
|
||||
} => Self::MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
},
|
||||
winit::event::WindowEvent::TouchpadPressure {
|
||||
device_id,
|
||||
pressure,
|
||||
stage,
|
||||
} => Self::TouchpadPressure {
|
||||
device_id,
|
||||
pressure,
|
||||
stage,
|
||||
},
|
||||
winit::event::WindowEvent::AxisMotion {
|
||||
device_id,
|
||||
axis,
|
||||
value,
|
||||
} => Self::AxisMotion {
|
||||
device_id,
|
||||
axis,
|
||||
value,
|
||||
},
|
||||
winit::event::WindowEvent::Touch(touch) => Self::Touch(touch),
|
||||
winit::event::WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size,
|
||||
} => Self::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: *new_inner_size,
|
||||
},
|
||||
winit::event::WindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme),
|
||||
winit::event::WindowEvent::Occluded(occluded) => Self::Occluded(occluded),
|
||||
winit::event::WindowEvent::TouchpadMagnify {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => Self::TouchpadMagnify {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
},
|
||||
winit::event::WindowEvent::SmartMagnify { device_id } => {
|
||||
Self::SmartMagnify { device_id }
|
||||
}
|
||||
winit::event::WindowEvent::TouchpadRotate {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => Self::TouchpadRotate {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
873
src/window.rs
Normal file
873
src/window.rs
Normal file
|
|
@ -0,0 +1,873 @@
|
|||
use std::collections::HashSet;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use winit::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use winit::error::OsError;
|
||||
use winit::event::{
|
||||
AxisId, DeviceId, ElementState, Ime, KeyboardInput, ModifiersState, MouseButton,
|
||||
MouseScrollDelta, Touch, TouchPhase, VirtualKeyCode,
|
||||
};
|
||||
use winit::window::{Fullscreen, Icon, Theme, WindowButtons, WindowId, WindowLevel};
|
||||
|
||||
use crate::private::{self, WindowEvent};
|
||||
use crate::{App, AppMessage, Application, PendingApp, WindowMessage};
|
||||
|
||||
/// A weak reference to a running window.
|
||||
#[derive(Clone)]
|
||||
pub struct Window {
|
||||
app: App,
|
||||
id: WindowId,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
/// Returns the winit id of the window.
|
||||
#[must_use]
|
||||
pub const fn id(&self) -> WindowId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns a clone of the winit [`Window`](winit::window::Window) if the
|
||||
/// window is still open.
|
||||
#[must_use]
|
||||
pub fn winit(&self) -> Option<Arc<winit::window::Window>> {
|
||||
self.app.windows.get(self.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for a window.
|
||||
///
|
||||
/// This type is similar to winit's
|
||||
/// [`WindowBuilder`](winit::window::WindowBuilder), except that it only
|
||||
/// supports the cross-platform interface. Support for additional
|
||||
/// platform-specific settings may be possible as long as all types introduced
|
||||
/// are `Send`.
|
||||
pub struct WindowBuilder<'a, Behavior, Application>
|
||||
where
|
||||
Behavior: self::WindowBehavior,
|
||||
{
|
||||
owner: &'a Application,
|
||||
context: Behavior::Context,
|
||||
attributes: WindowAttributes,
|
||||
}
|
||||
impl<'a, Behavior, Application> Deref for WindowBuilder<'a, Behavior, Application>
|
||||
where
|
||||
Behavior: self::WindowBehavior,
|
||||
{
|
||||
type Target = WindowAttributes;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.attributes
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Behavior, Application> DerefMut for WindowBuilder<'a, Behavior, Application>
|
||||
where
|
||||
Behavior: self::WindowBehavior,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.attributes
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct WindowAttributes {
|
||||
pub inner_size: Option<Size>,
|
||||
pub min_inner_size: Option<Size>,
|
||||
pub max_inner_size: Option<Size>,
|
||||
pub position: Option<Position>,
|
||||
pub resizable: bool,
|
||||
pub enabled_buttons: WindowButtons,
|
||||
pub title: String,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
pub maximized: bool,
|
||||
pub visible: bool,
|
||||
pub transparent: bool,
|
||||
pub decorations: bool,
|
||||
pub window_icon: Option<Icon>,
|
||||
pub preferred_theme: Option<Theme>,
|
||||
pub resize_increments: Option<Size>,
|
||||
pub content_protected: bool,
|
||||
pub window_level: WindowLevel,
|
||||
pub parent_window: Option<Window>,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl Default for WindowAttributes {
|
||||
fn default() -> Self {
|
||||
let defaults = winit::window::WindowAttributes::default();
|
||||
Self {
|
||||
inner_size: defaults.inner_size,
|
||||
min_inner_size: defaults.min_inner_size,
|
||||
max_inner_size: defaults.max_inner_size,
|
||||
position: defaults.position,
|
||||
resizable: defaults.resizable,
|
||||
enabled_buttons: defaults.enabled_buttons,
|
||||
title: defaults.title,
|
||||
fullscreen: defaults.fullscreen,
|
||||
maximized: defaults.maximized,
|
||||
visible: defaults.visible,
|
||||
transparent: defaults.transparent,
|
||||
decorations: defaults.decorations,
|
||||
window_icon: defaults.window_icon,
|
||||
preferred_theme: defaults.preferred_theme,
|
||||
resize_increments: defaults.resize_increments,
|
||||
content_protected: defaults.content_protected,
|
||||
window_level: defaults.window_level,
|
||||
active: defaults.active,
|
||||
parent_window: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Behavior, Application> WindowBuilder<'a, Behavior, Application>
|
||||
where
|
||||
Behavior: self::WindowBehavior,
|
||||
Application: crate::Application,
|
||||
{
|
||||
pub(crate) fn new(owner: &'a Application, context: Behavior::Context) -> Self {
|
||||
Self {
|
||||
owner,
|
||||
context,
|
||||
attributes: WindowAttributes::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the window, if the application is still running or has not started
|
||||
/// running. The events of the window will be processed in a thread spawned
|
||||
/// by this function.
|
||||
///
|
||||
/// If the application has shut down, this function returns None.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The only errors this funciton can return arise from
|
||||
/// [`winit::window::WindowBuilder::build`].
|
||||
pub fn open(self) -> Result<Option<Window>, winit::error::OsError> {
|
||||
// The window's thread shouldn't ever block for long periods of time. To
|
||||
// avoid a "frozen" window causing massive memory allocations, we'll use
|
||||
// a fixed-size channel and be cautious to not block the main event loop
|
||||
// by always using try_send.
|
||||
let (sender, receiver) = mpsc::sync_channel(128);
|
||||
let Some(winit) = self.owner.open(self.attributes, sender)? else {
|
||||
return Ok(None)
|
||||
};
|
||||
let window = Window {
|
||||
id: winit.id(),
|
||||
app: self.owner.app(),
|
||||
};
|
||||
let mut running_window = RunningWindow {
|
||||
messages: receiver,
|
||||
app: self.owner.app(),
|
||||
occluded: winit.is_visible().unwrap_or(false),
|
||||
focused: winit.has_focus(),
|
||||
inner_size: winit.inner_size(),
|
||||
location: winit.inner_position().unwrap_or_default(),
|
||||
scale: winit.scale_factor(),
|
||||
theme: winit.theme().unwrap_or(Theme::Dark),
|
||||
window: winit,
|
||||
next_redraw_target: None,
|
||||
close: false,
|
||||
modifiers: ModifiersState::default(),
|
||||
cursor_location: None,
|
||||
mouse_buttons: HashSet::default(),
|
||||
keys: HashSet::default(),
|
||||
};
|
||||
|
||||
thread::spawn(move || running_window.run_with::<Behavior>(self.context));
|
||||
|
||||
Ok(Some(window))
|
||||
}
|
||||
}
|
||||
|
||||
/// A window that is running in its own thread.
|
||||
pub struct RunningWindow {
|
||||
window: Arc<winit::window::Window>,
|
||||
next_redraw_target: Option<RedrawTarget>,
|
||||
messages: mpsc::Receiver<WindowMessage>,
|
||||
app: App,
|
||||
inner_size: PhysicalSize<u32>,
|
||||
location: PhysicalPosition<i32>,
|
||||
cursor_location: Option<PhysicalPosition<f64>>,
|
||||
mouse_buttons: HashSet<MouseButton>,
|
||||
keys: HashSet<VirtualKeyCode>,
|
||||
scale: f64,
|
||||
close: bool,
|
||||
occluded: bool,
|
||||
focused: bool,
|
||||
theme: Theme,
|
||||
modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
impl RunningWindow {
|
||||
/// Returns a reference to the underlying window.
|
||||
#[must_use]
|
||||
pub fn winit(&self) -> &winit::window::Window {
|
||||
&self.window
|
||||
}
|
||||
|
||||
/// Returns the target for when the window will be redrawn.
|
||||
#[must_use]
|
||||
pub const fn next_redraw_target(&self) -> Option<RedrawTarget> {
|
||||
self.next_redraw_target
|
||||
}
|
||||
|
||||
/// Sets the window to redraw as soon as it can.
|
||||
pub fn set_needs_redraw(&mut self) {
|
||||
self.next_redraw_target = Some(RedrawTarget::Immediate);
|
||||
}
|
||||
|
||||
/// Sets the window to redraw at the provided time.
|
||||
///
|
||||
/// If the window is already set to redraw sooner, this function does
|
||||
/// nothing.
|
||||
pub fn redraw_at(&mut self, instant: Instant) {
|
||||
// Make sure this new scheduled time isn't further out than our current target.
|
||||
match self.next_redraw_target {
|
||||
Some(RedrawTarget::Immediate) => return,
|
||||
Some(RedrawTarget::Scheduled(at)) => {
|
||||
if at < instant {
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
self.next_redraw_target = Some(RedrawTarget::Scheduled(instant));
|
||||
}
|
||||
|
||||
/// Sets the window to redraw after a `duration`.
|
||||
///
|
||||
/// If the window is already set to redraw sooner, this function does
|
||||
/// nothing.
|
||||
pub fn redraw_in(&mut self, duration: Duration) {
|
||||
self.redraw_at(Instant::now() + duration);
|
||||
}
|
||||
|
||||
/// Returns the current size of the interior of the window, in pixels.
|
||||
#[must_use]
|
||||
pub const fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
self.inner_size
|
||||
}
|
||||
|
||||
/// Returns the current location of the window, in pixels.
|
||||
#[must_use]
|
||||
pub const fn location(&self) -> PhysicalPosition<i32> {
|
||||
self.location
|
||||
}
|
||||
|
||||
/// Returns the location of the cursor relative to the window's upper-left
|
||||
/// corner, in pixels.
|
||||
#[must_use]
|
||||
pub const fn cursor_location(&self) -> Option<PhysicalPosition<f64>> {
|
||||
self.cursor_location
|
||||
}
|
||||
|
||||
/// Returns the current scale factor for the window.
|
||||
#[must_use]
|
||||
pub const fn scale(&self) -> f64 {
|
||||
self.scale
|
||||
}
|
||||
|
||||
/// Returns true if the window is currently invisible, hidden behind other
|
||||
/// windows, minimized, or otherwise hidden from the user's view.
|
||||
#[must_use]
|
||||
pub const fn occluded(&self) -> bool {
|
||||
self.occluded
|
||||
}
|
||||
|
||||
/// Returns true if the window is currently focused for keyboard input.
|
||||
#[must_use]
|
||||
pub const fn focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
/// Returns the current theme of the window.
|
||||
#[must_use]
|
||||
pub const fn theme(&self) -> Theme {
|
||||
self.theme
|
||||
}
|
||||
|
||||
/// Returns the current state of the keyboard modifier keys.
|
||||
#[must_use]
|
||||
pub const fn modifiers(&self) -> ModifiersState {
|
||||
self.modifiers
|
||||
}
|
||||
|
||||
fn run_with<Behavior>(&mut self, context: Behavior::Context)
|
||||
where
|
||||
Behavior: self::WindowBehavior,
|
||||
{
|
||||
let mut behavior = Behavior::initialize(self, context);
|
||||
while !self.close && self.process_messages_until_redraw(&mut behavior) {
|
||||
self.next_redraw_target = None;
|
||||
behavior.redraw(self);
|
||||
}
|
||||
drop(behavior);
|
||||
|
||||
// Do not notify the main thread to close the window until after the
|
||||
// behavior is dropped. This upholds the requirement for RawWindowHandle
|
||||
// by making sure that any resources required by the behavior have had a
|
||||
// chance to be freed.
|
||||
let _result = self
|
||||
.app
|
||||
.proxy
|
||||
.send_event(AppMessage::CloseWindow(self.window.id()));
|
||||
}
|
||||
|
||||
fn process_messages_until_redraw<Behavior>(&mut self, behavior: &mut Behavior) -> bool
|
||||
where
|
||||
Behavior: self::WindowBehavior,
|
||||
{
|
||||
loop {
|
||||
let message = match TimeUntilRedraw::from(self.next_redraw_target) {
|
||||
// The scheduled redraw time has already elapsed, or we need to
|
||||
// redraw. Process messages that are already enqueued, but don't
|
||||
// block.
|
||||
TimeUntilRedraw::None => match self.messages.try_recv() {
|
||||
Ok(message) => message,
|
||||
Err(mpsc::TryRecvError::Disconnected) => return false,
|
||||
Err(mpsc::TryRecvError::Empty) => return true,
|
||||
},
|
||||
// We have a scheduled time for the next frame, and it hasn't
|
||||
// elapsed yet.
|
||||
TimeUntilRedraw::Some(duration_remaining) => {
|
||||
match self.messages.recv_timeout(duration_remaining) {
|
||||
Ok(message) => message,
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => return true,
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => return false,
|
||||
}
|
||||
}
|
||||
// No scheduled redraw time, sleep until the next message.
|
||||
TimeUntilRedraw::Indefinite => match self.messages.recv() {
|
||||
Ok(message) => message,
|
||||
Err(_) => return false,
|
||||
},
|
||||
};
|
||||
|
||||
if !self.handle_message(message, behavior) {
|
||||
break false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)] // can't avoid the match
|
||||
fn handle_message<Behavior>(&mut self, message: WindowMessage, behavior: &mut Behavior) -> bool
|
||||
where
|
||||
Behavior: self::WindowBehavior,
|
||||
{
|
||||
match message {
|
||||
WindowMessage::Redraw => {
|
||||
self.set_needs_redraw();
|
||||
}
|
||||
WindowMessage::Event(evt) => match evt {
|
||||
WindowEvent::CloseRequested => {
|
||||
if behavior.close_requested(self) {
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
WindowEvent::Focused(focused) => {
|
||||
self.focused = focused;
|
||||
behavior.focus_changed(self);
|
||||
}
|
||||
WindowEvent::Occluded(occluded) => {
|
||||
self.occluded = occluded;
|
||||
behavior.occlusion_changed(self);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size,
|
||||
} => {
|
||||
// Ensure both values are updated before any behavior
|
||||
// callbacks are invoked.
|
||||
self.scale = scale_factor;
|
||||
let inner_size_changed = self.inner_size != new_inner_size;
|
||||
self.inner_size = new_inner_size;
|
||||
behavior.scale_factor_changed(self);
|
||||
if inner_size_changed {
|
||||
behavior.resized(self);
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(new_inner_size) => {
|
||||
if self.inner_size != new_inner_size {
|
||||
self.inner_size = new_inner_size;
|
||||
behavior.resized(self);
|
||||
}
|
||||
}
|
||||
WindowEvent::Moved(location) => {
|
||||
self.location = location;
|
||||
}
|
||||
WindowEvent::Destroyed => {
|
||||
return false;
|
||||
}
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
self.theme = theme;
|
||||
behavior.theme_changed(self);
|
||||
}
|
||||
WindowEvent::DroppedFile(path) => {
|
||||
behavior.dropped_file(self, path);
|
||||
}
|
||||
WindowEvent::HoveredFile(path) => {
|
||||
behavior.hovered_file(self, path);
|
||||
}
|
||||
WindowEvent::HoveredFileCancelled => {
|
||||
behavior.hovered_file_cancelled(self);
|
||||
}
|
||||
WindowEvent::ReceivedCharacter(char) => {
|
||||
behavior.received_character(self, char);
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
input,
|
||||
is_synthetic,
|
||||
} => {
|
||||
if let Some(keycode) = input.virtual_keycode {
|
||||
match input.state {
|
||||
ElementState::Pressed => {
|
||||
self.keys.insert(keycode);
|
||||
}
|
||||
ElementState::Released => {
|
||||
self.keys.remove(&keycode);
|
||||
}
|
||||
}
|
||||
}
|
||||
behavior.keyboard_input(self, device_id, input, is_synthetic);
|
||||
}
|
||||
WindowEvent::ModifiersChanged(modifiers) => {
|
||||
self.modifiers = modifiers;
|
||||
behavior.modifiers_changed(self);
|
||||
}
|
||||
WindowEvent::Ime(ime) => {
|
||||
behavior.ime(self, ime);
|
||||
}
|
||||
WindowEvent::CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
} => {
|
||||
self.cursor_location = Some(position);
|
||||
behavior.cursor_moved(self, device_id, position);
|
||||
}
|
||||
WindowEvent::CursorEntered { device_id } => {
|
||||
behavior.cursor_entered(self, device_id);
|
||||
}
|
||||
WindowEvent::CursorLeft { device_id } => {
|
||||
self.cursor_location = None;
|
||||
behavior.cursor_left(self, device_id);
|
||||
}
|
||||
WindowEvent::MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => {
|
||||
behavior.mouse_wheel(self, device_id, delta, phase);
|
||||
}
|
||||
WindowEvent::MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
} => {
|
||||
match state {
|
||||
ElementState::Pressed => {
|
||||
self.mouse_buttons.insert(button);
|
||||
}
|
||||
ElementState::Released => {
|
||||
self.mouse_buttons.remove(&button);
|
||||
}
|
||||
}
|
||||
behavior.mouse_input(self, device_id, state, button);
|
||||
}
|
||||
WindowEvent::TouchpadPressure {
|
||||
device_id,
|
||||
pressure,
|
||||
stage,
|
||||
} => {
|
||||
behavior.touchpad_pressure(self, device_id, pressure, stage);
|
||||
}
|
||||
WindowEvent::AxisMotion {
|
||||
device_id,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
behavior.axis_motion(self, device_id, axis, value);
|
||||
}
|
||||
WindowEvent::Touch(touch) => {
|
||||
behavior.touch(self, touch);
|
||||
}
|
||||
WindowEvent::TouchpadMagnify {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => {
|
||||
behavior.touchpad_magnify(self, device_id, delta, phase);
|
||||
}
|
||||
WindowEvent::SmartMagnify { device_id } => {
|
||||
behavior.smart_magnify(self, device_id);
|
||||
}
|
||||
WindowEvent::TouchpadRotate {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
} => {
|
||||
behavior.touchpad_rotate(self, device_id, delta, phase);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Sets this window to close as soon as possible.
|
||||
pub fn close(&mut self) {
|
||||
self.close = true;
|
||||
self.set_needs_redraw();
|
||||
}
|
||||
|
||||
/// Returns an iterator of the currently pressed keys.
|
||||
///
|
||||
/// This iterator does not guarantee any specific order.
|
||||
pub fn pressed_keys(&self) -> impl Iterator<Item = VirtualKeyCode> + '_ {
|
||||
self.keys.iter().copied()
|
||||
}
|
||||
|
||||
/// Returns true if the given key code is currently pressed.
|
||||
#[must_use]
|
||||
pub fn key_pressed(&self, keycode: &VirtualKeyCode) -> bool {
|
||||
self.keys.contains(keycode)
|
||||
}
|
||||
|
||||
/// Returns an iterator of the currently pressed mouse buttons.
|
||||
///
|
||||
/// This iterator does not guarantee any specific order.
|
||||
pub fn pressed_mouse_buttons(&self) -> impl Iterator<Item = MouseButton> + '_ {
|
||||
self.mouse_buttons.iter().copied()
|
||||
}
|
||||
|
||||
/// Returns true if the button is currently pressed.
|
||||
#[must_use]
|
||||
pub fn mouse_button_pressed(&self, button: &MouseButton) -> bool {
|
||||
self.mouse_buttons.contains(button)
|
||||
}
|
||||
}
|
||||
|
||||
impl Application for RunningWindow {}
|
||||
|
||||
impl private::ApplicationSealed for RunningWindow {
|
||||
fn app(&self) -> App {
|
||||
self.app.clone()
|
||||
}
|
||||
|
||||
fn open(
|
||||
&self,
|
||||
attrs: WindowAttributes,
|
||||
sender: mpsc::SyncSender<WindowMessage>,
|
||||
) -> Result<Option<Arc<winit::window::Window>>, OsError> {
|
||||
let (open_sender, open_receiver) = mpsc::sync_channel(1);
|
||||
if self
|
||||
.app
|
||||
.proxy
|
||||
.send_event(AppMessage::OpenWindow {
|
||||
attrs,
|
||||
sender,
|
||||
open_sender,
|
||||
})
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(window) = open_receiver.recv() {
|
||||
return window.map(Some);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub enum RedrawTarget {
|
||||
Immediate,
|
||||
Scheduled(Instant),
|
||||
}
|
||||
|
||||
impl From<Option<RedrawTarget>> for TimeUntilRedraw {
|
||||
fn from(value: Option<RedrawTarget>) -> Self {
|
||||
match value {
|
||||
Some(RedrawTarget::Immediate) => TimeUntilRedraw::None,
|
||||
Some(RedrawTarget::Scheduled(at)) => match at.checked_duration_since(Instant::now()) {
|
||||
Some(remaining) if !remaining.is_zero() => TimeUntilRedraw::Some(remaining),
|
||||
_ => TimeUntilRedraw::None,
|
||||
},
|
||||
None => Self::Indefinite,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TimeUntilRedraw {
|
||||
None,
|
||||
Some(Duration),
|
||||
Indefinite,
|
||||
}
|
||||
|
||||
/// The behavior that drives the contents of a window.
|
||||
///
|
||||
/// With winit and appit, the act of populating the window is up to the
|
||||
/// consumers of the libraries. This trait provides functions for each of the
|
||||
/// events a window may receive, enabling the type to react and update its
|
||||
/// state.
|
||||
pub trait WindowBehavior: Sized + 'static {
|
||||
/// A type that is passed to [`initialize()`](Self::initialize).
|
||||
///
|
||||
/// This allows providing data to the window from the thread that is opening
|
||||
/// the window without requiring that `WindowBehavior` also be `Send`.
|
||||
type Context: Send;
|
||||
|
||||
/// Returns a new window builder for this behavior. When the window is
|
||||
/// initialized, a default [`Context`](Self::Context) will be passed.
|
||||
fn build<App>(app: &App) -> WindowBuilder<'_, Self, App>
|
||||
where
|
||||
App: Application,
|
||||
Self::Context: Default,
|
||||
{
|
||||
Self::build_with(app, <Self::Context as Default>::default())
|
||||
}
|
||||
|
||||
/// Returns a new window builder for this behavior. When the window is
|
||||
/// initialized, the provided context will be passed.
|
||||
fn build_with<App>(app: &App, context: Self::Context) -> WindowBuilder<'_, Self, App>
|
||||
where
|
||||
App: Application,
|
||||
{
|
||||
WindowBuilder::new(app, context)
|
||||
}
|
||||
|
||||
/// Opens a new window with a default instance of this behavior's
|
||||
/// [`Context`](Self::Context). The events of the window will be processed
|
||||
/// in a thread spawned by this function.
|
||||
///
|
||||
/// If the application has shut down, this function returns None.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The only errors this funciton can return arise from
|
||||
/// [`winit::window::WindowBuilder::build`].
|
||||
fn open<App>(app: &App) -> Result<Option<Window>, OsError>
|
||||
where
|
||||
App: Application,
|
||||
Self::Context: Default,
|
||||
{
|
||||
Self::build(app).open()
|
||||
}
|
||||
|
||||
/// Opens a new window with the provided [`Context`](Self::Context). The
|
||||
/// events of the window will be processed in a thread spawned by this
|
||||
/// function.
|
||||
///
|
||||
/// If the application has shut down, this function returns None.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The only errors this funciton can return arise from
|
||||
/// [`winit::window::WindowBuilder::build`].
|
||||
fn open_with<App>(app: &App, context: Self::Context) -> Result<Option<Window>, OsError>
|
||||
where
|
||||
App: Application,
|
||||
{
|
||||
Self::build_with(app, context).open()
|
||||
}
|
||||
|
||||
/// Runs a window with a default instance of this behavior's
|
||||
/// [`Context`](Self::Context).
|
||||
///
|
||||
/// This function is shorthand for creating a [`PendingApp`], opening this
|
||||
/// window inside of it, and running the pending app.
|
||||
fn run() -> !
|
||||
where
|
||||
Self::Context: Default,
|
||||
{
|
||||
let app = PendingApp::new();
|
||||
Self::open(&app).expect("error opening initial window");
|
||||
app.run()
|
||||
}
|
||||
|
||||
/// Runs a window with the provided [`Context`](Self::Context).
|
||||
///
|
||||
/// This function is shorthand for creating a [`PendingApp`], opening this
|
||||
/// window inside of it, and running the pending app.
|
||||
fn run_with(context: Self::Context) -> ! {
|
||||
let app = PendingApp::new();
|
||||
Self::open_with(&app, context).expect("error opening initial window");
|
||||
app.run()
|
||||
}
|
||||
|
||||
/// Returns a new instance of this behavior after initializing itself with
|
||||
/// the window and context.
|
||||
fn initialize(window: &mut RunningWindow, context: Self::Context) -> Self;
|
||||
|
||||
/// Displays the contents of the window.
|
||||
fn redraw(&mut self, window: &mut RunningWindow);
|
||||
|
||||
/// The window has been requested to be closed. This can happen as a result
|
||||
/// of the user clicking the close button.
|
||||
///
|
||||
/// If the window should be closed, return true. To prevent closing the
|
||||
/// window, return false.
|
||||
#[allow(unused_variables)]
|
||||
fn close_requested(&mut self, window: &mut RunningWindow) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// The window has gained or lost keyboard focus.
|
||||
/// [`RunningWindow::focused()`] returns the current state.
|
||||
#[allow(unused_variables)]
|
||||
fn focus_changed(&mut self, window: &mut RunningWindow) {}
|
||||
|
||||
/// The window has been occluded or revealed. [`RunningWindow::occluded()`]
|
||||
/// returns the current state.
|
||||
#[allow(unused_variables)]
|
||||
fn occlusion_changed(&mut self, window: &mut RunningWindow) {}
|
||||
|
||||
/// The window's scale factor has changed. [`RunningWindow::scale()`]
|
||||
/// returns the current scale.
|
||||
#[allow(unused_variables)]
|
||||
fn scale_factor_changed(&mut self, window: &mut RunningWindow) {}
|
||||
|
||||
/// The window has been resized. [`RunningWindow::inner_size()`]
|
||||
/// returns the current size.
|
||||
#[allow(unused_variables)]
|
||||
fn resized(&mut self, window: &mut RunningWindow) {}
|
||||
|
||||
/// The window's theme has been updated. [`RunningWindow::theme()`]
|
||||
/// returns the current theme.
|
||||
#[allow(unused_variables)]
|
||||
fn theme_changed(&mut self, window: &mut RunningWindow) {}
|
||||
|
||||
/// A file has been dropped on the window.
|
||||
#[allow(unused_variables)]
|
||||
fn dropped_file(&mut self, window: &mut RunningWindow, path: PathBuf) {}
|
||||
|
||||
/// A file is hovering over the window.
|
||||
#[allow(unused_variables)]
|
||||
fn hovered_file(&mut self, window: &mut RunningWindow, path: PathBuf) {}
|
||||
|
||||
/// A file being overed has been cancelled.
|
||||
#[allow(unused_variables)]
|
||||
fn hovered_file_cancelled(&mut self, window: &mut RunningWindow) {}
|
||||
|
||||
/// An input event has generated a character.
|
||||
#[allow(unused_variables)]
|
||||
fn received_character(&mut self, window: &mut RunningWindow, char: char) {}
|
||||
|
||||
/// A keyboard event occurred while the window was focused.
|
||||
#[allow(unused_variables)]
|
||||
fn keyboard_input(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
input: KeyboardInput,
|
||||
is_synthetic: bool,
|
||||
) {
|
||||
}
|
||||
|
||||
/// The keyboard modifier keys have changed. [`RunningWindow::modifiers()`]
|
||||
/// returns the current modifier keys state.
|
||||
#[allow(unused_variables)]
|
||||
fn modifiers_changed(&mut self, window: &mut RunningWindow) {}
|
||||
|
||||
/// An international input even thas occurred for the window.
|
||||
#[allow(unused_variables)]
|
||||
fn ime(&mut self, window: &mut RunningWindow, ime: Ime) {}
|
||||
|
||||
/// A cursor has moved over the window.
|
||||
#[allow(unused_variables)]
|
||||
fn cursor_moved(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
position: PhysicalPosition<f64>,
|
||||
) {
|
||||
}
|
||||
|
||||
/// A cursor has hovered over the window.
|
||||
#[allow(unused_variables)]
|
||||
fn cursor_entered(&mut self, window: &mut RunningWindow, device_id: DeviceId) {}
|
||||
|
||||
/// A cursor is no longer hovering over the window.
|
||||
#[allow(unused_variables)]
|
||||
fn cursor_left(&mut self, window: &mut RunningWindow, device_id: DeviceId) {}
|
||||
|
||||
/// An event from a mouse wheel.
|
||||
#[allow(unused_variables)]
|
||||
fn mouse_wheel(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
) {
|
||||
}
|
||||
|
||||
/// A mouse button was pressed or released.
|
||||
#[allow(unused_variables)]
|
||||
fn mouse_input(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
state: ElementState,
|
||||
button: MouseButton,
|
||||
) {
|
||||
}
|
||||
|
||||
/// A pressure-sensitive touchpad was touched.
|
||||
#[allow(unused_variables)]
|
||||
fn touchpad_pressure(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
pressure: f32,
|
||||
stage: i64,
|
||||
) {
|
||||
}
|
||||
|
||||
/// A multi-axis input device has registered motion.
|
||||
#[allow(unused_variables)]
|
||||
fn axis_motion(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
axis: AxisId,
|
||||
value: f64,
|
||||
) {
|
||||
}
|
||||
|
||||
/// A touch event.
|
||||
#[allow(unused_variables)]
|
||||
fn touch(&mut self, window: &mut RunningWindow, touch: Touch) {}
|
||||
|
||||
/// A touchpad-originated magnification gesture.
|
||||
#[allow(unused_variables)]
|
||||
fn touchpad_magnify(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
delta: f64,
|
||||
phase: TouchPhase,
|
||||
) {
|
||||
}
|
||||
|
||||
/// A request to smart-magnify the window.
|
||||
#[allow(unused_variables)]
|
||||
fn smart_magnify(&mut self, window: &mut RunningWindow, device_id: DeviceId) {}
|
||||
|
||||
/// A touchpad-originated rotation gesture.
|
||||
#[allow(unused_variables)]
|
||||
fn touchpad_rotate(
|
||||
&mut self,
|
||||
window: &mut RunningWindow,
|
||||
device_id: DeviceId,
|
||||
delta: f32,
|
||||
phase: TouchPhase,
|
||||
) {
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue