use std::fmt;
use serde::{Deserialize, Serialize};
use zng_txt::Txt;
use crate::{
api_extension::{ApiExtensionId, ApiExtensionPayload},
config::ColorScheme,
display_list::{DisplayList, FrameValueUpdate},
image::{ImageId, ImageLoadedData, ImageMaskMode},
};
use zng_unit::{Dip, DipPoint, DipRect, DipSize, DipToPx as _, Factor, Px, PxPoint, PxSize, PxToDip, PxTransform, Rgba};
crate::declare_id! {
pub struct WindowId(_);
pub struct MonitorId(_);
pub struct FrameWaitId(_);
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum RenderMode {
Dedicated,
Integrated,
Software,
}
impl Default for RenderMode {
fn default() -> Self {
RenderMode::Integrated
}
}
impl RenderMode {
pub fn fallbacks(self) -> [RenderMode; 2] {
use RenderMode::*;
match self {
Dedicated => [Integrated, Software],
Integrated => [Dedicated, Software],
Software => [Integrated, Dedicated],
}
}
pub fn with_fallbacks(self) -> [RenderMode; 3] {
let [f0, f1] = self.fallbacks();
[self, f0, f1]
}
}
#[cfg(feature = "var")]
zng_var::impl_from_and_into_var! {
fn from(some: RenderMode) -> Option<RenderMode>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeadlessRequest {
pub id: WindowId,
pub scale_factor: Factor,
pub size: DipSize,
pub render_mode: RenderMode,
pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitorInfo {
pub name: Txt,
pub position: PxPoint,
pub size: PxSize,
pub scale_factor: Factor,
pub video_modes: Vec<VideoMode>,
pub is_primary: bool,
}
impl MonitorInfo {
pub fn dip_size(&self) -> DipSize {
self.size.to_dip(self.scale_factor)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct VideoMode {
pub size: PxSize,
pub bit_depth: u16,
pub refresh_rate: u32,
}
impl Default for VideoMode {
fn default() -> Self {
Self::MAX
}
}
impl VideoMode {
pub const MAX: VideoMode = VideoMode {
size: PxSize::new(Px::MAX, Px::MAX),
bit_depth: u16::MAX,
refresh_rate: u32::MAX,
};
}
impl fmt::Display for VideoMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Self::MAX {
write!(f, "MAX")
} else {
write!(
f,
"{}x{}, {}, {}hz",
self.size.width.0,
self.size.height.0,
self.bit_depth,
(self.refresh_rate as f32 * 0.001).round()
)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowOpenData {
pub state: WindowStateAll,
pub monitor: Option<MonitorId>,
pub position: (PxPoint, DipPoint),
pub size: DipSize,
pub scale_factor: Factor,
pub render_mode: RenderMode,
pub color_scheme: ColorScheme,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeadlessOpenData {
pub render_mode: RenderMode,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum FocusIndicator {
Critical,
Info,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FrameCapture {
#[default]
None,
Full,
Mask(ImageMaskMode),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameRequest {
pub id: FrameId,
pub clear_color: Rgba,
pub display_list: DisplayList,
pub capture: FrameCapture,
pub wait_id: Option<FrameWaitId>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct FrameUpdateRequest {
pub id: FrameId,
pub transforms: Vec<FrameValueUpdate<PxTransform>>,
pub floats: Vec<FrameValueUpdate<f32>>,
pub colors: Vec<FrameValueUpdate<Rgba>>,
pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
pub clear_color: Option<Rgba>,
pub capture: FrameCapture,
pub wait_id: Option<FrameWaitId>,
}
impl FrameUpdateRequest {
pub fn empty(id: FrameId) -> FrameUpdateRequest {
FrameUpdateRequest {
id,
transforms: vec![],
floats: vec![],
colors: vec![],
extensions: vec![],
clear_color: None,
capture: FrameCapture::None,
wait_id: None,
}
}
pub fn has_bounds(&self) -> bool {
!(self.transforms.is_empty() && self.floats.is_empty() && self.colors.is_empty())
}
pub fn is_empty(&self) -> bool {
!self.has_bounds() && self.extensions.is_empty() && self.clear_color.is_none() && self.capture != FrameCapture::None
}
}
impl fmt::Debug for FrameUpdateRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FrameUpdateRequest")
.field("id", &self.id)
.field("transforms", &self.transforms)
.field("floats", &self.floats)
.field("colors", &self.colors)
.field("clear_color", &self.clear_color)
.field("capture", &self.capture)
.finish()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowRequest {
pub id: WindowId,
pub title: Txt,
pub state: WindowStateAll,
pub kiosk: bool,
pub default_position: bool,
pub video_mode: VideoMode,
pub visible: bool,
pub taskbar_visible: bool,
pub always_on_top: bool,
pub movable: bool,
pub resizable: bool,
pub icon: Option<ImageId>,
pub cursor: Option<CursorIcon>,
pub cursor_image: Option<ImageId>,
pub transparent: bool,
pub capture_mode: bool,
pub render_mode: RenderMode,
pub focus_indicator: Option<FocusIndicator>,
pub focus: bool,
pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
pub ime_area: Option<DipRect>,
}
impl WindowRequest {
pub fn enforce_kiosk(&mut self) {
if self.kiosk {
if !self.state.state.is_fullscreen() {
tracing::error!("window in `kiosk` mode did not request fullscreen");
self.state.state = WindowState::Exclusive;
}
if self.state.chrome_visible {
tracing::error!("window in `kiosk` mode request chrome");
self.state.chrome_visible = false;
}
if !self.visible {
tracing::error!("window in `kiosk` mode can only be visible");
self.visible = true;
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WindowStateAll {
pub state: WindowState,
pub global_position: PxPoint,
pub restore_rect: DipRect,
pub restore_state: WindowState,
pub min_size: DipSize,
pub max_size: DipSize,
pub chrome_visible: bool,
}
impl WindowStateAll {
pub fn clamp_size(&mut self) {
self.restore_rect.size = self.restore_rect.size.min(self.max_size).max(self.min_size)
}
pub fn set_state(&mut self, new_state: WindowState) {
self.restore_state = Self::compute_restore_state(self.restore_state, self.state, new_state);
self.state = new_state;
}
pub fn set_restore_state_from(&mut self, prev_state: WindowState) {
self.restore_state = Self::compute_restore_state(self.restore_state, prev_state, self.state);
}
fn compute_restore_state(restore_state: WindowState, prev_state: WindowState, new_state: WindowState) -> WindowState {
if new_state == WindowState::Minimized {
if prev_state != WindowState::Minimized {
prev_state
} else {
WindowState::Normal
}
} else if new_state.is_fullscreen() && !prev_state.is_fullscreen() {
if prev_state == WindowState::Maximized {
WindowState::Maximized
} else {
WindowState::Normal
}
} else if new_state == WindowState::Maximized {
WindowState::Normal
} else {
restore_state
}
}
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum CursorIcon {
#[default]
Default,
ContextMenu,
Help,
Pointer,
Progress,
Wait,
Cell,
Crosshair,
Text,
VerticalText,
Alias,
Copy,
Move,
NoDrop,
NotAllowed,
Grab,
Grabbing,
EResize,
NResize,
NeResize,
NwResize,
SResize,
SeResize,
SwResize,
WResize,
EwResize,
NsResize,
NeswResize,
NwseResize,
ColResize,
RowResize,
AllScroll,
ZoomIn,
ZoomOut,
}
#[cfg(feature = "var")]
zng_var::impl_from_and_into_var! {
fn from(some: CursorIcon) -> Option<CursorIcon>;
}
impl CursorIcon {
pub const ALL: &'static [CursorIcon] = {
use CursorIcon::*;
&[
Default,
ContextMenu,
Help,
Pointer,
Progress,
Wait,
Cell,
Crosshair,
Text,
VerticalText,
Alias,
Copy,
Move,
NoDrop,
NotAllowed,
Grab,
Grabbing,
EResize,
NResize,
NeResize,
NwResize,
SResize,
SeResize,
SwResize,
WResize,
EwResize,
NsResize,
NeswResize,
NwseResize,
ColResize,
RowResize,
AllScroll,
ZoomIn,
ZoomOut,
]
};
pub fn size_and_spot(&self, scale_factor: Factor) -> (PxSize, PxPoint) {
fn splat(s: f32, rel_pt: f32) -> (DipSize, DipPoint) {
size(s, s, rel_pt, rel_pt)
}
fn size(w: f32, h: f32, rel_x: f32, rel_y: f32) -> (DipSize, DipPoint) {
(
DipSize::new(Dip::new_f32(w), Dip::new_f32(h)),
DipPoint::new(Dip::new_f32(w * rel_x), Dip::new_f32(h * rel_y)),
)
}
let (size, spot) = match self {
CursorIcon::Crosshair
| CursorIcon::Move
| CursorIcon::Wait
| CursorIcon::NotAllowed
| CursorIcon::NoDrop
| CursorIcon::Cell
| CursorIcon::Grab
| CursorIcon::Grabbing
| CursorIcon::AllScroll => splat(20.0, 0.5),
CursorIcon::Text | CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize => size(8.0, 20.0, 0.5, 0.5),
CursorIcon::VerticalText | CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize => size(20.0, 8.0, 0.5, 0.5),
_ => splat(20.0, 0.0),
};
(size.to_px(scale_factor), spot.to_px(scale_factor))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CursorImage {
pub img: ImageId,
pub hotspot: PxPoint,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
pub enum WindowState {
#[default]
Normal,
Minimized,
Maximized,
Fullscreen,
Exclusive,
}
impl WindowState {
pub fn is_fullscreen(self) -> bool {
matches!(self, Self::Fullscreen | Self::Exclusive)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EventFrameRendered {
pub window: WindowId,
pub frame: FrameId,
pub frame_image: Option<ImageLoadedData>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowChanged {
pub window: WindowId,
pub state: Option<WindowStateAll>,
pub position: Option<(PxPoint, DipPoint)>,
pub monitor: Option<MonitorId>,
pub size: Option<DipSize>,
pub frame_wait_id: Option<FrameWaitId>,
pub cause: EventCause,
}
impl WindowChanged {
pub fn moved(window: WindowId, global_position: PxPoint, position: DipPoint, cause: EventCause) -> Self {
WindowChanged {
window,
state: None,
position: Some((global_position, position)),
monitor: None,
size: None,
frame_wait_id: None,
cause,
}
}
pub fn monitor_changed(window: WindowId, monitor: MonitorId, cause: EventCause) -> Self {
WindowChanged {
window,
state: None,
position: None,
monitor: Some(monitor),
size: None,
frame_wait_id: None,
cause,
}
}
pub fn resized(window: WindowId, size: DipSize, cause: EventCause, frame_wait_id: Option<FrameWaitId>) -> Self {
WindowChanged {
window,
state: None,
position: None,
monitor: None,
size: Some(size),
frame_wait_id,
cause,
}
}
pub fn state_changed(window: WindowId, state: WindowStateAll, cause: EventCause) -> Self {
WindowChanged {
window,
state: Some(state),
position: None,
monitor: None,
size: None,
frame_wait_id: None,
cause,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, bytemuck::NoUninit)]
#[repr(C)]
pub struct FrameId(u32, u32);
impl FrameId {
pub const INVALID: FrameId = FrameId(u32::MAX, u32::MAX);
pub fn first() -> FrameId {
FrameId(0, 0)
}
pub fn next(self) -> FrameId {
let mut id = self.0.wrapping_add(1);
if id == u32::MAX {
id = 0;
}
FrameId(id, 0)
}
pub fn next_update(self) -> FrameId {
let mut id = self.1.wrapping_add(1);
if id == u32::MAX {
id = 0;
}
FrameId(self.0, id)
}
pub fn get(self) -> u64 {
(self.0 as u64) << 32 | (self.1 as u64)
}
pub fn epoch(self) -> u32 {
self.0
}
pub fn update(self) -> u32 {
self.1
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum EventCause {
System,
App,
}