use std::fmt;
use zng_app::{
Deadline,
event::{event, event_args},
timer::DeadlineVar,
widget::{WidgetId, node::UiNode},
window::WindowId,
};
use zng_layout::unit::{DipPoint, DipSize, PxPoint};
use zng_task::channel::ChannelError;
#[cfg(feature = "image")]
use zng_task::channel::IpcBytes;
use zng_unique_id::IdSet;
#[cfg(feature = "image")]
use zng_var::WeakVarEq;
use zng_var::impl_from_and_into_var;
use zng_view_api::window::{CursorIcon, EventCause};
pub use zng_view_api::window::{FocusIndicator, RenderMode, VideoMode, WindowButton, WindowCapability, WindowState};
use zng_wgt::prelude::IntoUiNode;
use crate::{HeadlessMonitor, WINDOWS};
#[cfg(feature = "image")]
use crate::WINDOW_Ext as _;
#[cfg(feature = "image")]
use std::path::{Path, PathBuf};
#[cfg(feature = "image")]
use zng_app::window::WINDOW;
#[cfg(feature = "image")]
use zng_ext_image::{ImageEntry, ImageSource, ImageVar};
#[cfg(feature = "image")]
use zng_layout::unit::Point;
#[cfg(feature = "image")]
use zng_txt::Txt;
#[cfg(feature = "image")]
use zng_view_api::{
image::{ImageDataFormat, ImageMaskMode},
window::FrameId,
};
pub struct WindowRoot {
pub(super) id: WidgetId,
pub(super) start_position: StartPosition,
pub(super) kiosk: bool,
pub(super) transparent: bool,
pub(super) render_mode: Option<RenderMode>,
pub(super) headless_monitor: HeadlessMonitor,
pub(super) start_focused: bool,
pub(super) child: UiNode,
}
impl WindowRoot {
#[expect(clippy::too_many_arguments)]
pub fn new(
root_id: WidgetId,
start_position: StartPosition,
kiosk: bool,
transparent: bool,
render_mode: Option<RenderMode>,
headless_monitor: HeadlessMonitor,
start_focused: bool,
root: impl IntoUiNode,
) -> Self {
WindowRoot {
id: root_id,
start_position,
kiosk,
transparent,
render_mode,
headless_monitor,
start_focused,
child: root.into_node(),
}
}
#[expect(clippy::too_many_arguments)]
pub fn new_container(
root_id: WidgetId,
start_position: StartPosition,
kiosk: bool,
transparent: bool,
render_mode: Option<RenderMode>,
headless_monitor: HeadlessMonitor,
start_focused: bool,
child: impl IntoUiNode,
) -> Self {
WindowRoot::new(
root_id,
start_position,
kiosk,
transparent,
render_mode,
headless_monitor,
start_focused,
zng_app::widget::base::node::widget_inner(child),
)
}
#[cfg(any(test, doc, feature = "test_util"))]
pub fn new_test(child: impl IntoUiNode) -> Self {
WindowRoot::new_container(
WidgetId::named("test-window-root"),
StartPosition::Default,
false,
false,
None,
HeadlessMonitor::default(),
false,
child,
)
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct AutoSize: u8 {
const DISABLED = 0;
const CONTENT_WIDTH = 0b01;
const CONTENT_HEIGHT = 0b10;
const CONTENT = Self::CONTENT_WIDTH.bits() | Self::CONTENT_HEIGHT.bits();
}
}
impl_from_and_into_var! {
fn from(content: bool) -> AutoSize {
if content { AutoSize::CONTENT } else { AutoSize::DISABLED }
}
}
#[derive(Clone, Default, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum StartPosition {
#[default]
Default,
CenterMonitor,
CenterParent,
}
impl fmt::Debug for StartPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "StartPosition::")?;
}
match self {
StartPosition::Default => write!(f, "Default"),
StartPosition::CenterMonitor => write!(f, "CenterMonitor"),
StartPosition::CenterParent => write!(f, "CenterParent"),
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct WindowStateAllowed: u8 {
const MINIMIZE = 0b0001;
const MAXIMIZE = 0b0010;
const FULLSCREEN_WN_ONLY = 0b0100;
const FULLSCREEN = 0b1100;
}
}
#[derive(Clone, PartialEq)]
#[non_exhaustive]
pub enum WindowIcon {
Default,
#[cfg(feature = "image")]
Image(ImageSource),
}
impl fmt::Debug for WindowIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "WindowIcon::")?;
}
match self {
WindowIcon::Default => write!(f, "Default"),
#[cfg(feature = "image")]
WindowIcon::Image(r) => write!(f, "Image({r:?})"),
}
}
}
impl Default for WindowIcon {
fn default() -> Self {
Self::Default
}
}
#[cfg(feature = "image")]
impl WindowIcon {
pub fn render(new_icon: impl Fn() -> UiNode + Send + Sync + 'static) -> Self {
Self::Image(ImageSource::render_node(RenderMode::Software, move |args| {
let node = new_icon();
WINDOW.vars().parent().set(args.parent);
node
}))
}
}
#[cfg(all(feature = "http", feature = "image"))]
impl_from_and_into_var! {
fn from(uri: zng_task::http::Uri) -> WindowIcon {
ImageSource::from(uri).into()
}
}
#[cfg(feature = "image")]
impl_from_and_into_var! {
fn from(source: ImageSource) -> WindowIcon {
WindowIcon::Image(source)
}
fn from(image: ImageVar) -> WindowIcon {
ImageSource::Image(image).into()
}
fn from(path: PathBuf) -> WindowIcon {
ImageSource::from(path).into()
}
fn from(path: &Path) -> WindowIcon {
ImageSource::from(path).into()
}
fn from(s: &str) -> WindowIcon {
ImageSource::from(s).into()
}
fn from(s: String) -> WindowIcon {
ImageSource::from(s).into()
}
fn from(s: Txt) -> WindowIcon {
ImageSource::from(s).into()
}
fn from(data: &'static [u8]) -> WindowIcon {
ImageSource::from(data).into()
}
fn from<const N: usize>(data: &'static [u8; N]) -> WindowIcon {
ImageSource::from(data).into()
}
fn from(data: IpcBytes) -> WindowIcon {
ImageSource::from(data).into()
}
fn from(data: Vec<u8>) -> WindowIcon {
ImageSource::from(data).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> WindowIcon {
ImageSource::from((data, format)).into()
}
fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> WindowIcon {
ImageSource::from((data, format)).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> WindowIcon {
ImageSource::from((data, format)).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, F)) -> WindowIcon {
ImageSource::from((data, format)).into()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg(feature = "image")]
pub struct CursorImg {
pub source: ImageSource,
pub hotspot: Point,
pub fallback: CursorIcon,
}
#[cfg(feature = "image")]
impl_from_and_into_var! {
fn from(img: CursorImg) -> Option<CursorImg>;
}
#[derive(Debug, Clone, PartialEq)]
pub enum CursorSource {
Icon(CursorIcon),
#[cfg(feature = "image")]
Img(CursorImg),
Hidden,
}
impl CursorSource {
pub fn icon(&self) -> Option<CursorIcon> {
match self {
CursorSource::Icon(ico) => Some(*ico),
#[cfg(feature = "image")]
CursorSource::Img(img) => Some(img.fallback),
CursorSource::Hidden => None,
}
}
#[cfg(feature = "image")]
pub fn img(&self) -> Option<&ImageSource> {
match self {
CursorSource::Img(img) => Some(&img.source),
_ => None,
}
}
#[cfg(feature = "image")]
pub fn hotspot(&self) -> Option<&Point> {
match self {
CursorSource::Img(img) => Some(&img.hotspot),
_ => None,
}
}
}
#[cfg(feature = "image")]
impl_from_and_into_var! {
fn from(img: CursorImg) -> CursorSource {
CursorSource::Img(img)
}
}
impl_from_and_into_var! {
fn from(icon: CursorIcon) -> CursorSource {
CursorSource::Icon(icon)
}
fn from(default_icon_or_hidden: bool) -> CursorSource {
if default_icon_or_hidden {
CursorIcon::Default.into()
} else {
CursorSource::Hidden
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[cfg(feature = "image")]
pub enum FrameCaptureMode {
Sporadic,
Next,
NextMask(ImageMaskMode),
All,
AllMask(ImageMaskMode),
}
#[cfg(feature = "image")]
impl Default for FrameCaptureMode {
fn default() -> Self {
Self::Sporadic
}
}
event_args! {
pub struct WindowOpenArgs {
pub window_id: WindowId,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
true
}
}
pub struct WindowCloseArgs {
pub windows: IdSet<WindowId>,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
true
}
}
pub struct WindowChangedArgs {
pub window_id: WindowId,
pub state: Option<(WindowState, WindowState)>,
pub position: Option<(PxPoint, DipPoint)>,
pub size: Option<DipSize>,
pub cause: EventCause,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
true
}
}
pub struct WindowFocusChangedArgs {
pub prev_focus: Option<WindowId>,
pub new_focus: Option<WindowId>,
pub closed: bool,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
true
}
}
pub struct WindowCloseRequestedArgs {
pub windows: IdSet<WindowId>,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
true
}
}
}
#[cfg(feature = "image")]
event_args! {
pub struct FrameImageReadyArgs {
pub window_id: WindowId,
pub frame_id: FrameId,
pub frame_image: WeakVarEq<ImageEntry>,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
true
}
}
}
impl WindowChangedArgs {
pub fn is_state_changed(&self) -> bool {
self.state.is_some()
}
pub fn prev_state(&self) -> Option<WindowState> {
self.state.map(|(p, _)| p)
}
pub fn new_state(&self) -> Option<WindowState> {
self.state.map(|(_, n)| n)
}
pub fn entered_state(&self, state: WindowState) -> bool {
if let Some((prev, new)) = self.state {
prev != state && new == state
} else {
false
}
}
pub fn exited_state(&self, state: WindowState) -> bool {
if let Some((prev, new)) = self.state {
prev == state && new != state
} else {
false
}
}
pub fn entered_fullscreen(&self) -> bool {
if let Some((prev, new)) = self.state {
!prev.is_fullscreen() && new.is_fullscreen()
} else {
false
}
}
pub fn exited_fullscreen(&self) -> bool {
if let Some((prev, new)) = self.state {
prev.is_fullscreen() && !new.is_fullscreen()
} else {
false
}
}
pub fn is_moved(&self) -> bool {
self.position.is_some()
}
pub fn is_resized(&self) -> bool {
self.size.is_some()
}
}
impl WindowFocusChangedArgs {
pub fn is_focus(&self, window_id: WindowId) -> bool {
self.new_focus == Some(window_id)
}
pub fn is_blur(&self, window_id: WindowId) -> bool {
self.prev_focus == Some(window_id)
}
pub fn is_close(&self, window_id: WindowId) -> bool {
self.closed && self.is_blur(window_id)
}
pub fn closed(&self) -> Option<WindowId> {
if self.closed { self.prev_focus } else { None }
}
}
impl WindowCloseRequestedArgs {
pub fn headed(&self) -> impl Iterator<Item = WindowId> + '_ {
self.windows
.iter()
.copied()
.filter(|&id| WINDOWS.mode(id).map(|m| m.is_headed()).unwrap_or(false))
}
pub fn headless(&self) -> impl Iterator<Item = WindowId> + '_ {
self.windows
.iter()
.copied()
.filter(|&id| WINDOWS.mode(id).map(|m| m.is_headless()).unwrap_or(false))
}
}
event! {
pub static WINDOW_CHANGED_EVENT: WindowChangedArgs;
pub static WINDOW_OPEN_EVENT: WindowOpenArgs;
pub static WINDOW_LOAD_EVENT: WindowOpenArgs;
pub static WINDOW_FOCUS_CHANGED_EVENT: WindowFocusChangedArgs;
pub static WINDOW_CLOSE_REQUESTED_EVENT: WindowCloseRequestedArgs;
pub static WINDOW_CLOSE_EVENT: WindowCloseArgs;
}
#[cfg(feature = "image")]
event! {
pub static FRAME_IMAGE_READY_EVENT: FrameImageReadyArgs;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CloseWindowResult {
Closed,
Cancel,
}
#[derive(Clone, Debug)]
#[must_use = "the window does not await loading if the handle is dropped"]
pub struct WindowLoadingHandle(pub(crate) DeadlineVar);
impl WindowLoadingHandle {
pub fn deadline(&self) -> Deadline {
self.0.get()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ViewExtensionError {
WindowNotFound(WindowId),
WindowNotHeaded(WindowId),
NotOpenInViewProcess(WindowId),
AlreadyOpenInViewProcess(WindowId),
Disconnected,
Api(zng_view_api::api_extension::ApiExtensionRecvError),
}
impl fmt::Display for ViewExtensionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::WindowNotFound(id) => write!(f, "window `{id}` not found"),
Self::WindowNotHeaded(id) => write!(f, "window `{id}` is not headed"),
Self::NotOpenInViewProcess(id) => write!(f, "window/renderer `{id}` not open in the view-process"),
Self::AlreadyOpenInViewProcess(id) => write!(f, "window/renderer `{id}` already open in the view-process"),
Self::Disconnected => fmt::Display::fmt(&DISCONNECTED_SOURCE, f),
Self::Api(e) => fmt::Display::fmt(e, f),
}
}
}
impl std::error::Error for ViewExtensionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::WindowNotFound(_) => None,
Self::WindowNotHeaded(_) => None,
Self::NotOpenInViewProcess(_) => None,
Self::AlreadyOpenInViewProcess(_) => None,
Self::Disconnected => Some(&DISCONNECTED_SOURCE),
Self::Api(e) => Some(e),
}
}
}
static DISCONNECTED_SOURCE: ChannelError = ChannelError::Disconnected { cause: None };
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct ParallelWin: u8 {
const UPDATE = 0b0001;
const LAYOUT = 0b0100;
const RENDER = 0b1000;
}
}
impl Default for ParallelWin {
fn default() -> Self {
Self::all()
}
}
impl_from_and_into_var! {
fn from(all: bool) -> ParallelWin {
if all { ParallelWin::all() } else { ParallelWin::empty() }
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum WindowInstanceState {
Building,
Loading,
Loaded {
has_view: bool,
},
Closed,
}
impl WindowInstanceState {
pub fn is_loaded(&self) -> bool {
matches!(self, Self::Loaded { .. })
}
}