#![warn(missing_docs)]
use alloc::boxed::Box;
use crate::component::ComponentVTable;
use crate::window::{WindowAdapter, WindowInner};
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct LogicalPosition {
pub x: f32,
pub y: f32,
}
impl LogicalPosition {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn from_physical(physical_pos: PhysicalPosition, scale_factor: f32) -> Self {
Self::new(physical_pos.x as f32 / scale_factor, physical_pos.y as f32 / scale_factor)
}
pub fn to_physical(&self, scale_factor: f32) -> PhysicalPosition {
PhysicalPosition::from_logical(*self, scale_factor)
}
pub(crate) fn to_euclid(&self) -> crate::graphics::Point {
[self.x as _, self.y as _].into()
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct PhysicalPosition {
pub x: i32,
pub y: i32,
}
impl PhysicalPosition {
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
pub fn from_logical(logical_pos: LogicalPosition, scale_factor: f32) -> Self {
Self::new((logical_pos.x * scale_factor) as i32, (logical_pos.y * scale_factor) as i32)
}
pub fn to_logical(&self, scale_factor: f32) -> LogicalPosition {
LogicalPosition::from_physical(*self, scale_factor)
}
#[cfg(feature = "ffi")]
pub(crate) fn to_euclid(&self) -> crate::graphics::euclid::default::Point2D<i32> {
[self.x, self.y].into()
}
}
#[derive(Clone, Debug, derive_more::From, PartialEq)]
pub enum WindowPosition {
Physical(PhysicalPosition),
Logical(LogicalPosition),
}
impl WindowPosition {
pub fn to_physical(&self, scale_factor: f32) -> PhysicalPosition {
match self {
WindowPosition::Physical(pos) => pos.clone(),
WindowPosition::Logical(pos) => pos.to_physical(scale_factor),
}
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct LogicalSize {
pub width: f32,
pub height: f32,
}
impl LogicalSize {
pub const fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
pub fn from_physical(physical_size: PhysicalSize, scale_factor: f32) -> Self {
Self::new(
physical_size.width as f32 / scale_factor,
physical_size.height as f32 / scale_factor,
)
}
pub fn to_physical(&self, scale_factor: f32) -> PhysicalSize {
PhysicalSize::from_logical(*self, scale_factor)
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct PhysicalSize {
pub width: u32,
pub height: u32,
}
impl PhysicalSize {
pub const fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
pub fn from_logical(logical_size: LogicalSize, scale_factor: f32) -> Self {
Self::new(
(logical_size.width * scale_factor) as u32,
(logical_size.height * scale_factor) as u32,
)
}
pub fn to_logical(&self, scale_factor: f32) -> LogicalSize {
LogicalSize::from_physical(*self, scale_factor)
}
#[cfg(feature = "ffi")]
pub(crate) fn to_euclid(&self) -> crate::graphics::euclid::default::Size2D<u32> {
[self.width, self.height].into()
}
}
#[derive(Clone, Debug, derive_more::From, PartialEq)]
pub enum WindowSize {
Physical(PhysicalSize),
Logical(LogicalSize),
}
impl WindowSize {
pub fn to_physical(&self, scale_factor: f32) -> PhysicalSize {
match self {
WindowSize::Physical(size) => size.clone(),
WindowSize::Logical(size) => size.to_physical(scale_factor),
}
}
pub fn to_logical(&self, scale_factor: f32) -> LogicalSize {
match self {
WindowSize::Physical(size) => size.to_logical(scale_factor),
WindowSize::Logical(size) => size.clone(),
}
}
}
#[derive(Clone)]
#[non_exhaustive]
pub enum GraphicsAPI<'a> {
NativeOpenGL {
get_proc_address: &'a dyn Fn(&str) -> *const core::ffi::c_void,
},
WebGL {
canvas_element_id: &'a str,
context_type: &'a str,
},
}
impl<'a> core::fmt::Debug for GraphicsAPI<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
GraphicsAPI::NativeOpenGL { .. } => write!(f, "GraphicsAPI::NativeOpenGL"),
GraphicsAPI::WebGL { context_type, .. } => {
write!(f, "GraphicsAPI::WebGL(context_type = {})", context_type)
}
}
}
}
#[derive(Debug, Clone)]
#[repr(C)]
#[non_exhaustive]
pub enum RenderingState {
RenderingSetup,
BeforeRendering,
AfterRendering,
RenderingTeardown,
}
pub trait RenderingNotifier {
fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI);
}
impl<F: FnMut(RenderingState, &GraphicsAPI)> RenderingNotifier for F {
fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI) {
self(state, graphics_api)
}
}
#[derive(Debug, Clone)]
#[repr(C)]
#[non_exhaustive]
pub enum SetRenderingNotifierError {
Unsupported,
AlreadySet,
}
#[repr(transparent)]
pub struct Window(pub(crate) WindowInner);
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub enum CloseRequestResponse {
HideWindow,
KeepWindowShown,
}
impl Default for CloseRequestResponse {
fn default() -> Self {
Self::HideWindow
}
}
impl Window {
#[doc(hidden)]
pub fn new(window_adapter_weak: alloc::rc::Weak<dyn WindowAdapter>) -> Self {
Self(WindowInner::new(window_adapter_weak))
}
pub fn show(&self) {
self.0.show();
}
pub fn hide(&self) {
self.0.hide();
}
pub fn set_rendering_notifier(
&self,
callback: impl FnMut(RenderingState, &GraphicsAPI) + 'static,
) -> Result<(), SetRenderingNotifierError> {
self.0.window_adapter().renderer().set_rendering_notifier(Box::new(callback))
}
pub fn on_close_requested(&self, callback: impl FnMut() -> CloseRequestResponse + 'static) {
self.0.on_close_requested(callback);
}
pub fn request_redraw(&self) {
self.0.window_adapter().request_redraw();
}
pub fn scale_factor(&self) -> f32 {
self.0.scale_factor()
}
pub fn position(&self) -> PhysicalPosition {
self.0.window_adapter().position()
}
pub fn set_position(&self, position: impl Into<WindowPosition>) {
let position = position.into();
self.0.window_adapter().set_position(position)
}
pub fn size(&self) -> PhysicalSize {
self.0.inner_size.get()
}
pub fn set_size(&self, size: impl Into<WindowSize>) {
let size = size.into();
let l = size.to_logical(self.scale_factor());
let p = size.to_physical(self.scale_factor());
self.0.set_window_item_geometry(l.width as _, l.height as _);
if self.0.inner_size.replace(p) != p {
self.0.window_adapter().set_size(size);
}
}
pub fn dispatch_event(&self, event: WindowEvent) {
self.0.process_mouse_input(event.into())
}
pub fn has_active_animations(&self) -> bool {
crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.has_active_animations())
}
}
pub use crate::input::PointerEventButton;
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum WindowEvent {
PointerPressed {
position: LogicalPosition,
button: PointerEventButton,
},
PointerReleased {
position: LogicalPosition,
button: PointerEventButton,
},
PointerMoved { position: LogicalPosition },
PointerScrolled {
position: LogicalPosition,
delta_x: f32,
delta_y: f32,
},
PointerExited,
}
impl WindowEvent {
pub fn position(&self) -> Option<LogicalPosition> {
match self {
WindowEvent::PointerPressed { position, .. } => Some(*position),
WindowEvent::PointerReleased { position, .. } => Some(*position),
WindowEvent::PointerMoved { position } => Some(*position),
WindowEvent::PointerScrolled { position, .. } => Some(*position),
WindowEvent::PointerExited => None,
}
}
}
pub trait Global<'a, Component> {
fn get(component: &'a Component) -> Self;
}
pub trait ComponentHandle {
#[doc(hidden)]
type Inner;
fn as_weak(&self) -> Weak<Self>
where
Self: Sized;
#[must_use]
fn clone_strong(&self) -> Self;
#[doc(hidden)]
fn from_inner(_: vtable::VRc<ComponentVTable, Self::Inner>) -> Self;
fn show(&self);
fn hide(&self);
fn window(&self) -> &Window;
fn run(&self);
fn global<'a, T: Global<'a, Self>>(&'a self) -> T
where
Self: Sized;
}
mod weak_handle {
use super::*;
pub struct Weak<T: ComponentHandle> {
inner: vtable::VWeak<ComponentVTable, T::Inner>,
#[cfg(feature = "std")]
thread: std::thread::ThreadId,
}
impl<T: ComponentHandle> Clone for Weak<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
#[cfg(feature = "std")]
thread: self.thread,
}
}
}
impl<T: ComponentHandle> Weak<T> {
#[doc(hidden)]
pub fn new(rc: &vtable::VRc<ComponentVTable, T::Inner>) -> Self {
Self {
inner: vtable::VRc::downgrade(rc),
#[cfg(feature = "std")]
thread: std::thread::current().id(),
}
}
pub fn upgrade(&self) -> Option<T>
where
T: ComponentHandle,
{
#[cfg(feature = "std")]
if std::thread::current().id() != self.thread {
return None;
}
self.inner.upgrade().map(T::from_inner)
}
pub fn unwrap(&self) -> T {
self.upgrade().unwrap()
}
#[cfg(feature = "std")]
pub fn upgrade_in_event_loop(
&self,
func: impl FnOnce(T) + Send + 'static,
) -> Result<(), EventLoopError>
where
T: 'static,
{
let weak_handle = self.clone();
super::invoke_from_event_loop(move || {
if let Some(h) = weak_handle.upgrade() {
func(h);
}
})
}
}
#[allow(unsafe_code)]
#[cfg(feature = "std")]
unsafe impl<T: ComponentHandle> Send for Weak<T> {}
}
pub use weak_handle::*;
pub fn invoke_from_event_loop(func: impl FnOnce() + Send + 'static) -> Result<(), EventLoopError> {
crate::platform::event_loop_proxy()
.ok_or(EventLoopError::NoEventLoopProvider)?
.invoke_from_event_loop(alloc::boxed::Box::new(func))
}
pub fn quit_event_loop() -> Result<(), EventLoopError> {
crate::platform::event_loop_proxy()
.ok_or(EventLoopError::NoEventLoopProvider)?
.quit_event_loop()
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum EventLoopError {
EventLoopTerminated,
NoEventLoopProvider,
}
#[test]
fn logical_physical_pos() {
use crate::graphics::euclid::approxeq::ApproxEq;
let phys = PhysicalPosition::new(100, 50);
let logical = phys.to_logical(2.);
assert!(logical.x.approx_eq(&50.));
assert!(logical.y.approx_eq(&25.));
assert_eq!(logical.to_physical(2.), phys);
}
#[test]
fn logical_physical_size() {
use crate::graphics::euclid::approxeq::ApproxEq;
let phys = PhysicalSize::new(100, 50);
let logical = phys.to_logical(2.);
assert!(logical.width.approx_eq(&50.));
assert!(logical.height.approx_eq(&25.));
assert_eq!(logical.to_physical(2.), phys);
}