use crate::assets::Texture;
use dotrix_math::{ clamp_min, Vec2, Vec2i, Vec2u };
use winit::{
dpi::{ PhysicalPosition, PhysicalSize, Position },
monitor:: { MonitorHandle as WinitMonitor, VideoMode as WinitVideoMode },
window::{ self, Fullscreen as WinitFullscreen, Window as WinitWindow },
};
pub use window::CursorIcon as CursorIcon;
pub use window::UserAttentionType as UserAttentionType;
const NOT_SUPPORTED_ERROR: &str = "Sorry, the feature is not supported on this device.";
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct VideoMode {
pub color_depth: u16,
pub monitor_number: usize,
pub refresh_rate: u16,
pub resolution: Vec2u,
}
impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} x {}, {} hz, {} bit",
self.resolution.x, self.resolution.y, self.refresh_rate, self.color_depth)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Monitor {
pub name: String,
pub number: usize,
pub scale_factor: f32,
pub size: Vec2u,
pub video_modes: Vec<VideoMode>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Fullscreen {
Borderless(usize),
Exclusive(VideoMode),
}
pub struct Window {
always_on_top: bool,
pub (crate) close_request: bool,
cursor_grab: bool,
cursor_icon: CursorIcon,
cursor_visible: bool,
decorations: bool,
min_inner_size: Vec2u,
monitors: Vec<Monitor>,
resizable: bool,
title: String,
window: Option<winit::window::Window>,
}
impl Default for Window {
fn default() -> Self {
Self {
always_on_top: false,
close_request: false,
cursor_grab: false,
cursor_icon: CursorIcon::Default,
cursor_visible: true,
decorations: true,
resizable: true,
min_inner_size: Vec2u::new(0, 0),
monitors: Vec::with_capacity(2),
title: String::from("Dotrix"),
window: None,
}
}
}
impl Window {
pub(crate) fn set(&mut self, window: WinitWindow) {
self.monitors = init_monitors(&window);
self.window = Some(window);
}
pub fn get(&self) -> &WinitWindow {
self.window.as_ref().expect("Window handle must be set")
}
pub fn always_on_top(&self) -> bool {
self.always_on_top
}
pub fn close(&mut self) {
self.close_request = true;
}
pub fn current_monitor(&self) -> &Monitor {
self.get()
.current_monitor()
.map(|winit_monitor| self.monitors.iter().find(
|monitor| monitor.name == winit_monitor.name().unwrap()
))
.unwrap_or(None)
.unwrap_or(&self.monitors[0])
}
pub fn cursor_grab(&self) -> bool {
self.cursor_grab
}
pub fn cursor_icon(&self) -> CursorIcon {
self.cursor_icon
}
pub fn cursor_visible(&self) -> bool {
self.cursor_visible
}
pub fn decorations(&self) -> bool {
self.decorations
}
pub fn fullscreen(&self) -> bool {
self.get().fullscreen().is_some()
}
fn get_winit_monitor(&self, monitor_number: usize) -> Option<WinitMonitor> {
if let Some(monitor) = self.monitors.get(monitor_number) {
self.get().available_monitors().find(|w_monitor| {
w_monitor.name().unwrap() == monitor.name
})
} else {
None
}
}
fn get_winit_video_mode(&self, vmode: VideoMode) -> Option<WinitVideoMode> {
if let Some(monitor) = self.get_winit_monitor(vmode.monitor_number) {
monitor.video_modes().find(|w_vmode| {
w_vmode.size().width == vmode.resolution.x &&
w_vmode.size().height == vmode.resolution.y &&
w_vmode.refresh_rate() == vmode.refresh_rate &&
w_vmode.bit_depth() == vmode.color_depth
})
} else {
None
}
}
pub fn inner_position(&self) -> Vec2i {
let position = self.get().inner_position().expect(NOT_SUPPORTED_ERROR);
Vec2i { x: position.x, y: position.y }
}
pub fn inner_size(&self) -> Vec2u {
let PhysicalSize { width, height } = self.get().inner_size();
Vec2u {
x: if width == 0 { 1 } else { width },
y: if height == 0 { 1 } else { height },
}
}
pub fn aspect_ratio(&self) -> f32 {
let size = self.inner_size();
size.x as f32 / size.y as f32
}
pub fn maximized(&self) -> bool {
self.get().is_maximized()
}
pub fn min_inner_size(&self) -> Vec2u {
self.min_inner_size
}
pub fn monitors(&self) -> &Vec<Monitor> {
&self.monitors
}
pub fn outer_position(&self) -> Vec2i {
let position = self.get().outer_position().expect(NOT_SUPPORTED_ERROR);
Vec2i { x: position.x, y: position.y }
}
pub fn outer_size(&self) -> Vec2u {
let size = self.get().outer_size();
Vec2u { x: size.width, y: size.height }
}
pub fn resizable(&self) -> bool {
self.resizable
}
pub fn request_attention(&self, request_type: Option<UserAttentionType>) {
self.get().request_user_attention(request_type);
}
pub (crate) fn request_redraw(&self) {
self.get().request_redraw()
}
pub fn scale_factor(&self) -> f32 {
self.get().scale_factor() as f32
}
pub fn screen_size(&self) -> Vec2u {
let monitor = self.get().current_monitor().expect(NOT_SUPPORTED_ERROR);
let size = monitor.size();
Vec2u { x: size.width, y: size.height }
}
pub fn set_always_on_top(&mut self, always_on_top: bool) {
self.get().set_always_on_top(always_on_top);
self.always_on_top = always_on_top;
}
pub fn set_cursor_grab(&mut self, grab: bool) {
self.get().set_cursor_grab(grab).expect(NOT_SUPPORTED_ERROR);
self.cursor_grab = grab;
}
pub fn set_cursor_icon(&mut self, icon: CursorIcon) {
self.get().set_cursor_icon(icon);
self.cursor_icon = icon;
}
pub fn set_cursor_position(&self, pos: Vec2) {
self.get().set_cursor_position(
Position::Physical(
PhysicalPosition { x: pos.x.round() as i32, y: pos.y.round() as i32 }
)
).expect(NOT_SUPPORTED_ERROR);
}
pub fn set_cursor_visible(&mut self, visible: bool) {
self.get().set_cursor_visible(visible);
self.cursor_visible = visible;
}
fn set_borderless_fullscreen(&self, monitor_number: usize) {
let w_monitor = self.get_winit_monitor(monitor_number);
self.get().set_fullscreen(Some(WinitFullscreen::Borderless(w_monitor)));
}
pub fn set_decorations(&mut self, decorations: bool) {
self.get().set_decorations(decorations);
self.decorations = decorations;
}
pub fn set_exclusive_fullscreen(&self, video_mode: VideoMode) {
if let Some(w_video_mode) = self.get_winit_video_mode(video_mode) {
self.get().set_fullscreen(Some(WinitFullscreen::Exclusive(w_video_mode)));
}
}
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
if let Some(fullscreen_mode) = fullscreen {
match fullscreen_mode {
Fullscreen::Borderless(monitor_number) =>
self.set_borderless_fullscreen(monitor_number),
Fullscreen::Exclusive(video_mode) =>
self.set_exclusive_fullscreen(video_mode),
}
} else {
self.get().set_fullscreen(None);
}
}
pub fn set_icon(&self, icon: Option<&Texture>) {
if let Some(texture) = icon {
let icn = window::Icon::from_rgba(texture.data.clone(), texture.width, texture.height)
.expect("Failed to open icon");
self.get().set_window_icon(Some(icn));
} else {
self.get().set_window_icon(None);
}
}
pub fn set_inner_size(&self, size: Vec2u) {
let width = clamp_min(size.x, self.min_inner_size.x);
let height = clamp_min(size.y, self.min_inner_size.y);
self.get().set_inner_size(PhysicalSize::new(width, height));
}
pub fn set_maximized(&self, maximized: bool) {
self.get().set_maximized(maximized);
}
pub fn set_minimized(&mut self, minimized: bool) {
self.get().set_minimized(minimized);
}
pub fn set_min_inner_size(&mut self, min_size: Vec2u) {
self.min_inner_size = min_size;
let min_size_physical = if min_size.x > 0 || min_size.y > 0 {
Some(PhysicalSize::new(min_size.x, min_size.y))
} else {
None
};
self.get().set_min_inner_size(min_size_physical);
let size = self.inner_size();
if size.x < min_size.x || size.y < min_size.y {
self.set_inner_size(Vec2u {
x: clamp_min(size.x, min_size.x),
y: clamp_min(size.y, min_size.y),
});
}
}
pub fn set_outer_position(&self, position: Vec2i) {
self.get().set_outer_position(PhysicalPosition::new(position.x, position.y));
}
pub fn set_resizable(&mut self, resizable: bool) {
self.get().set_resizable(resizable);
self.resizable = resizable;
}
pub fn set_title(&mut self, title: &str) {
self.get().set_title(title);
self.title = String::from(title);
}
pub fn title(&self) -> &str {
self.title.as_str()
}
}
fn init_monitors(window: &WinitWindow) -> Vec<Monitor> {
window.available_monitors().enumerate().map(|(i, w_monitor)| {
Monitor {
name: w_monitor.name().unwrap(),
number: i,
scale_factor: w_monitor.scale_factor() as f32,
size: Vec2u::new(w_monitor.size().width, w_monitor.size().height),
video_modes: w_monitor.video_modes().filter_map(|vmode| {
if vmode.size().width > w_monitor.size().width
|| vmode.size().height > w_monitor.size().height {
None
} else {
Some(
VideoMode {
color_depth: vmode.bit_depth(),
monitor_number: i,
refresh_rate: vmode.refresh_rate(),
resolution: Vec2u::new(vmode.size().width, vmode.size().height),
}
)
}
}).collect(),
}
}).collect()
}