use std::fmt;
use serde::{Deserialize, Serialize};
use zng_txt::Txt;
use crate::{
api_extension::{ApiExtensionId, ApiExtensionPayload},
display_list::{DisplayList, FrameValueUpdate},
image::{ImageDecoded, ImageId, ImageMaskMode},
};
use zng_unit::{Dip, DipPoint, DipRect, DipSideOffsets, 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)]
#[non_exhaustive]
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)]
#[non_exhaustive]
pub struct HeadlessRequest {
pub id: WindowId,
pub scale_factor: Factor,
pub size: DipSize,
pub render_mode: RenderMode,
pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
}
impl HeadlessRequest {
pub fn new(
id: WindowId,
scale_factor: Factor,
size: DipSize,
render_mode: RenderMode,
extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
) -> Self {
Self {
id,
scale_factor,
size,
render_mode,
extensions,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
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 new(name: Txt, position: PxPoint, size: PxSize, scale_factor: Factor, video_modes: Vec<VideoMode>, is_primary: bool) -> Self {
Self {
name,
position,
size,
scale_factor,
video_modes,
is_primary,
}
}
pub fn dip_size(&self) -> DipSize {
self.size.to_dip(self.scale_factor)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
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 fn new(size: PxSize, bit_depth: u16, refresh_rate: u32) -> Self {
Self {
size,
bit_depth,
refresh_rate,
}
}
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)]
#[non_exhaustive]
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 safe_padding: DipSideOffsets,
}
impl WindowOpenData {
pub fn new(
state: WindowStateAll,
monitor: Option<MonitorId>,
position: (PxPoint, DipPoint),
size: DipSize,
scale_factor: Factor,
render_mode: RenderMode,
safe_padding: DipSideOffsets,
) -> Self {
Self {
state,
monitor,
position,
size,
scale_factor,
render_mode,
safe_padding,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HeadlessOpenData {
pub render_mode: RenderMode,
}
impl HeadlessOpenData {
pub fn new(render_mode: RenderMode) -> Self {
Self { render_mode }
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum FocusIndicator {
Critical,
Info,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum FrameCapture {
#[default]
None,
Full,
Mask(ImageMaskMode),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FrameRequest {
pub id: FrameId,
pub clear_color: Rgba,
pub display_list: DisplayList,
pub capture: FrameCapture,
pub wait_id: Option<FrameWaitId>,
}
impl FrameRequest {
pub fn new(id: FrameId, clear_color: Rgba, display_list: DisplayList, capture: FrameCapture, wait_id: Option<FrameWaitId>) -> Self {
Self {
id,
clear_color,
display_list,
capture,
wait_id,
}
}
}
#[derive(Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FrameUpdateRequest {
pub id: FrameId,
pub transforms: Vec<FrameValueUpdate<PxTransform>>,
pub floats: Vec<FrameValueUpdate<f32>>,
pub colors: Vec<FrameValueUpdate<Rgba>>,
pub clear_color: Option<Rgba>,
pub capture: FrameCapture,
pub wait_id: Option<FrameWaitId>,
pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
}
impl FrameUpdateRequest {
#[allow(clippy::too_many_arguments)] pub fn new(
id: FrameId,
transforms: Vec<FrameValueUpdate<PxTransform>>,
floats: Vec<FrameValueUpdate<f32>>,
colors: Vec<FrameValueUpdate<Rgba>>,
clear_color: Option<Rgba>,
capture: FrameCapture,
wait_id: Option<FrameWaitId>,
extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
) -> Self {
Self {
id,
transforms,
floats,
colors,
extensions,
clear_color,
capture,
wait_id,
}
}
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)]
#[non_exhaustive]
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, PxPoint)>,
pub transparent: bool,
pub capture_mode: bool,
pub render_mode: RenderMode,
pub focus_indicator: Option<FocusIndicator>,
pub focus: bool,
pub ime_area: Option<DipRect>,
pub enabled_buttons: WindowButton,
pub system_shutdown_warn: Txt,
pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
}
impl WindowRequest {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: WindowId,
title: Txt,
state: WindowStateAll,
kiosk: bool,
default_position: bool,
video_mode: VideoMode,
visible: bool,
taskbar_visible: bool,
always_on_top: bool,
movable: bool,
resizable: bool,
icon: Option<ImageId>,
cursor: Option<CursorIcon>,
cursor_image: Option<(ImageId, PxPoint)>,
transparent: bool,
capture_mode: bool,
render_mode: RenderMode,
focus_indicator: Option<FocusIndicator>,
focus: bool,
ime_area: Option<DipRect>,
enabled_buttons: WindowButton,
system_shutdown_warn: Txt,
extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
) -> Self {
Self {
id,
title,
state,
kiosk,
default_position,
video_mode,
visible,
taskbar_visible,
always_on_top,
movable,
resizable,
icon,
cursor,
cursor_image,
transparent,
capture_mode,
render_mode,
focus_indicator,
focus,
extensions,
ime_area,
enabled_buttons,
system_shutdown_warn,
}
}
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)]
#[non_exhaustive]
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 new(
state: WindowState,
global_position: PxPoint,
restore_rect: DipRect,
restore_state: WindowState,
min_size: DipSize,
max_size: DipSize,
chrome_visible: bool,
) -> Self {
Self {
state,
global_position,
restore_rect,
restore_state,
min_size,
max_size,
chrome_visible,
}
}
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)]
#[non_exhaustive]
pub struct CursorImage {
pub img: ImageId,
pub hotspot: PxPoint,
}
impl CursorImage {
pub fn new(img: ImageId, hotspot: PxPoint) -> Self {
Self { img, hotspot }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum ResizeDirection {
East,
North,
NorthEast,
NorthWest,
South,
SouthEast,
SouthWest,
West,
}
impl From<ResizeDirection> for CursorIcon {
fn from(direction: ResizeDirection) -> Self {
use ResizeDirection::*;
match direction {
East => CursorIcon::EResize,
North => CursorIcon::NResize,
NorthEast => CursorIcon::NeResize,
NorthWest => CursorIcon::NwResize,
South => CursorIcon::SResize,
SouthEast => CursorIcon::SeResize,
SouthWest => CursorIcon::SwResize,
West => CursorIcon::WResize,
}
}
}
#[cfg(feature = "var")]
zng_var::impl_from_and_into_var! {
fn from(some: ResizeDirection) -> Option<ResizeDirection>;
fn from(some: ResizeDirection) -> Option<CursorIcon> {
Some(some.into())
}
}
impl ResizeDirection {
pub const ALL: &'static [ResizeDirection] = {
use ResizeDirection::*;
&[East, North, NorthEast, NorthWest, South, SouthEast, SouthWest, West]
};
pub const fn is_corner(self) -> bool {
matches!(self, Self::NorthEast | Self::NorthWest | Self::SouthEast | Self::SouthWest)
}
pub const fn is_border(self) -> bool {
!self.is_corner()
}
}
#[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)]
#[non_exhaustive]
pub struct EventFrameRendered {
pub window: WindowId,
pub frame: FrameId,
pub frame_image: Option<ImageDecoded>,
}
impl EventFrameRendered {
pub fn new(window: WindowId, frame: FrameId, frame_image: Option<ImageDecoded>) -> Self {
Self {
window,
frame,
frame_image,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct WindowChanged {
pub window: WindowId,
pub state: Option<WindowStateAll>,
pub position: Option<(PxPoint, DipPoint)>,
pub monitor: Option<MonitorId>,
pub size: Option<DipSize>,
pub safe_padding: Option<DipSideOffsets>,
pub frame_wait_id: Option<FrameWaitId>,
pub cause: EventCause,
}
impl WindowChanged {
#[allow(clippy::too_many_arguments)] pub fn new(
window: WindowId,
state: Option<WindowStateAll>,
position: Option<(PxPoint, DipPoint)>,
monitor: Option<MonitorId>,
size: Option<DipSize>,
safe_padding: Option<DipSideOffsets>,
frame_wait_id: Option<FrameWaitId>,
cause: EventCause,
) -> Self {
Self {
window,
state,
position,
monitor,
size,
safe_padding,
frame_wait_id,
cause,
}
}
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,
safe_padding: 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,
safe_padding: 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),
safe_padding: None,
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,
safe_padding: 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)]
#[non_exhaustive]
pub enum EventCause {
System,
App,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WindowButton: u32 {
const CLOSE = 1 << 0;
const MINIMIZE = 1 << 1;
const MAXIMIZE = 1 << 2;
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WindowCapability: u64 {
const SET_TITLE = 1 << 0;
const SET_VISIBLE = 1 << 1;
const SET_ALWAYS_ON_TOP = 1 << 2;
const SET_MOVABLE = 1 << 3;
const SET_RESIZABLE = 1 << 4;
const SET_TASKBAR_VISIBLE = 1 << 5;
const BRING_TO_TOP = 1 << 6;
const SET_ICON = 1 << 7;
const SET_CURSOR = 1 << 8;
const SET_CURSOR_IMAGE = 1 << 9;
const SET_FOCUS_INDICATOR = 1 << 10;
const FOCUS = 1 << 11;
const DRAG_MOVE = 1 << 12;
const DRAG_RESIZE = 1 << 13;
const OPEN_TITLE_BAR_CONTEXT_MENU = 1 << 14;
const SYSTEM_CHROME = 1 << 15;
const MINIMIZE = (1 << 16);
const RESTORE = (1 << 17);
const MAXIMIZE = (1 << 18);
const FULLSCREEN = (1 << 19);
const EXCLUSIVE = (1 << 20);
const SET_CHROME = (1 << 21) | Self::SYSTEM_CHROME.bits();
const SET_POSITION = (1 << 22);
/// Can programmatically resize window after it is open.
const SET_SIZE = (1 << 23);
/// Can disable close button.
const DISABLE_CLOSE_BUTTON = (1 << 24);
/// Can disable minimize button.
const DISABLE_MINIMIZE_BUTTON = (1 << 25);
/// Can disable maximize button.
const DISABLE_MAXIMIZE_BUTTON = (1 << 26);
/// Can set a system shutdown warning/blocker associated with the window.
const SET_SYSTEM_SHUTDOWN_WARN = (1 << 27);
/// Can set the IME area, show virtual keyboard.
const SET_IME_AREA = (1 << 28);
}
}