use crate::{
access::{AccessCmd, AccessNodeId},
api_extension::{ApiExtensionId, ApiExtensionPayload, ApiExtensions},
audio::{AudioDecoded, AudioDeviceId, AudioDeviceInfo, AudioId, AudioMetadata, AudioOutputId, AudioOutputOpenData, AudioPlayId},
config::{AnimationsConfig, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
dialog::{DialogId, FileDialogResponse, MsgDialogResponse, NotificationResponse},
drag_drop::{DragDropData, DragDropEffect},
image::{ImageDecoded, ImageEncodeId, ImageId, ImageMetadata},
keyboard::{Key, KeyCode, KeyLocation, KeyState},
mouse::{ButtonState, MouseButton, MouseScrollDelta},
raw_input::{InputDeviceCapability, InputDeviceEvent, InputDeviceId, InputDeviceInfo},
touch::{TouchPhase, TouchUpdate},
window::{EventFrameRendered, HeadlessOpenData, MonitorId, MonitorInfo, WindowChanged, WindowId, WindowOpenData},
};
use serde::{Deserialize, Serialize};
use std::fmt;
use zng_task::channel::{ChannelError, IpcBytes};
use zng_txt::Txt;
use zng_unit::{DipPoint, Rgba};
macro_rules! declare_id {
($(
$(#[$docs:meta])+
pub struct $Id:ident(_);
)+) => {$(
$(#[$docs])+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct $Id(u32);
impl $Id {
pub const INVALID: Self = Self(0);
pub const fn first() -> Self {
Self(1)
}
#[must_use]
pub const fn next(self) -> Self {
let r = Self(self.0.wrapping_add(1));
if r.0 == Self::INVALID.0 {
Self::first()
} else {
r
}
}
#[must_use]
pub fn incr(&mut self) -> Self {
std::mem::replace(self, self.next())
}
pub const fn get(self) -> u32 {
self.0
}
pub const fn from_raw(id: u32) -> Self {
Self(id)
}
}
)+};
}
pub(crate) use declare_id;
declare_id! {
pub struct ViewProcessGen(_);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct AxisId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct DragDropId(pub u32);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ViewProcessInfo {
pub generation: ViewProcessGen,
pub is_respawn: bool,
pub input_device: InputDeviceCapability,
pub window: crate::window::WindowCapability,
pub dialog: crate::dialog::DialogCapability,
pub menu: crate::menu::MenuCapability,
pub clipboard: crate::clipboard::ClipboardTypes,
pub image: Vec<crate::image::ImageFormat>,
pub audio: Vec<crate::audio::AudioFormat>,
pub extensions: ApiExtensions,
}
impl ViewProcessInfo {
pub const fn new(generation: ViewProcessGen, is_respawn: bool) -> Self {
Self {
generation,
is_respawn,
input_device: InputDeviceCapability::empty(),
window: crate::window::WindowCapability::empty(),
dialog: crate::dialog::DialogCapability::empty(),
menu: crate::menu::MenuCapability::empty(),
clipboard: crate::clipboard::ClipboardTypes::new(vec![], vec![], false),
image: vec![],
audio: vec![],
extensions: ApiExtensions::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Ime {
Preview(Txt, (usize, usize)),
Commit(Txt),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Event {
Inited(ViewProcessInfo),
Suspended,
Disconnected(ViewProcessGen),
WindowOpened(WindowId, WindowOpenData),
HeadlessOpened(WindowId, HeadlessOpenData),
WindowOrHeadlessOpenError {
id: WindowId,
error: Txt,
},
FrameRendered(EventFrameRendered),
WindowChanged(WindowChanged),
DragHovered {
window: WindowId,
data: Vec<DragDropData>,
allowed: DragDropEffect,
},
DragMoved {
window: WindowId,
coalesced_pos: Vec<DipPoint>,
position: DipPoint,
},
DragDropped {
window: WindowId,
data: Vec<DragDropData>,
allowed: DragDropEffect,
drop_id: DragDropId,
},
DragCancelled {
window: WindowId,
},
AppDragEnded {
window: WindowId,
drag: DragDropId,
applied: DragDropEffect,
},
FocusChanged {
prev: Option<WindowId>,
new: Option<WindowId>,
},
KeyboardInput {
window: WindowId,
device: InputDeviceId,
key_code: KeyCode,
state: KeyState,
key_location: KeyLocation,
key: Key,
key_modified: Key,
text: Txt,
},
Ime {
window: WindowId,
ime: Ime,
},
MouseMoved {
window: WindowId,
device: InputDeviceId,
coalesced_pos: Vec<DipPoint>,
position: DipPoint,
},
MouseEntered {
window: WindowId,
device: InputDeviceId,
},
MouseLeft {
window: WindowId,
device: InputDeviceId,
},
MouseWheel {
window: WindowId,
device: InputDeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
},
MouseInput {
window: WindowId,
device: InputDeviceId,
state: ButtonState,
button: MouseButton,
},
TouchpadPressure {
window: WindowId,
device: InputDeviceId,
pressure: f32,
stage: i64,
},
AxisMotion {
window: WindowId,
device: InputDeviceId,
axis: AxisId,
value: f64,
},
Touch {
window: WindowId,
device: InputDeviceId,
touches: Vec<TouchUpdate>,
},
#[deprecated = "use `MonitorsChanged`"]
ScaleFactorChanged {
monitor: MonitorId,
windows: Vec<WindowId>,
scale_factor: f32,
},
MonitorsChanged(Vec<(MonitorId, MonitorInfo)>),
AudioDevicesChanged(Vec<(AudioDeviceId, AudioDeviceInfo)>),
InputDevicesChanged(Vec<(InputDeviceId, InputDeviceInfo)>),
WindowCloseRequested(WindowId),
WindowClosed(WindowId),
ImageMetadataDecoded(ImageMetadata),
ImageDecoded(ImageDecoded),
ImageDecodeError {
image: ImageId,
error: Txt,
},
ImageEncoded {
task: ImageEncodeId,
data: IpcBytes,
},
ImageEncodeError {
task: ImageEncodeId,
error: Txt,
},
AudioMetadataDecoded(AudioMetadata),
AudioDecoded(AudioDecoded),
AudioDecodeError {
audio: AudioId,
error: Txt,
},
AudioOutputOpened(AudioOutputId, AudioOutputOpenData),
AudioOutputOpenError {
id: AudioOutputId,
error: Txt,
},
AudioPlayError {
play: AudioPlayId,
error: Txt,
},
FontsChanged,
FontAaChanged(FontAntiAliasing),
MultiClickConfigChanged(MultiClickConfig),
AnimationsConfigChanged(AnimationsConfig),
KeyRepeatConfigChanged(KeyRepeatConfig),
TouchConfigChanged(TouchConfig),
LocaleChanged(LocaleConfig),
ColorsConfigChanged(ColorsConfig),
InputDeviceEvent {
device: InputDeviceId,
event: InputDeviceEvent,
},
MsgDialogResponse(DialogId, MsgDialogResponse),
FileDialogResponse(DialogId, FileDialogResponse),
NotificationResponse(DialogId, NotificationResponse),
MenuCommand {
id: Txt,
},
AccessInit {
window: WindowId,
},
AccessCommand {
window: WindowId,
target: AccessNodeId,
command: AccessCmd,
},
AccessDeinit {
window: WindowId,
},
LowMemory,
RecoveredFromComponentPanic {
component: Txt,
recover: Txt,
panic: Txt,
},
ExtensionEvent(ApiExtensionId, ApiExtensionPayload),
Pong(u16),
}
impl Event {
#[expect(clippy::result_large_err)]
pub fn coalesce(&mut self, other: Event) -> Result<(), Event> {
use Event::*;
match (self, other) {
(
MouseMoved {
window,
device,
coalesced_pos,
position,
},
MouseMoved {
window: n_window,
device: n_device,
coalesced_pos: n_coal_pos,
position: n_pos,
},
) if *window == n_window && *device == n_device => {
coalesced_pos.push(*position);
coalesced_pos.extend(n_coal_pos);
*position = n_pos;
}
(
DragMoved {
window,
coalesced_pos,
position,
},
DragMoved {
window: n_window,
coalesced_pos: n_coal_pos,
position: n_pos,
},
) if *window == n_window => {
coalesced_pos.push(*position);
coalesced_pos.extend(n_coal_pos);
*position = n_pos;
}
(
InputDeviceEvent { device, event },
InputDeviceEvent {
device: n_device,
event: n_event,
},
) if *device == n_device => {
if let Err(e) = event.coalesce(n_event) {
return Err(InputDeviceEvent {
device: n_device,
event: e,
});
}
}
(
MouseWheel {
window,
device,
delta: MouseScrollDelta::LineDelta(delta_x, delta_y),
phase,
},
MouseWheel {
window: n_window,
device: n_device,
delta: MouseScrollDelta::LineDelta(n_delta_x, n_delta_y),
phase: n_phase,
},
) if *window == n_window && *device == n_device && *phase == n_phase => {
*delta_x += n_delta_x;
*delta_y += n_delta_y;
}
(
MouseWheel {
window,
device,
delta: MouseScrollDelta::PixelDelta(delta_x, delta_y),
phase,
},
MouseWheel {
window: n_window,
device: n_device,
delta: MouseScrollDelta::PixelDelta(n_delta_x, n_delta_y),
phase: n_phase,
},
) if *window == n_window && *device == n_device && *phase == n_phase => {
*delta_x += n_delta_x;
*delta_y += n_delta_y;
}
(
Touch { window, device, touches },
Touch {
window: n_window,
device: n_device,
touches: mut n_touches,
},
) if *window == n_window && *device == n_device => {
touches.append(&mut n_touches);
}
(WindowChanged(change), WindowChanged(n_change))
if change.window == n_change.window && change.cause == n_change.cause && change.frame_wait_id.is_none() =>
{
if n_change.state.is_some() {
change.state = n_change.state;
}
if n_change.position.is_some() {
change.position = n_change.position;
}
if n_change.monitor.is_some() {
change.monitor = n_change.monitor;
}
if n_change.size.is_some() {
change.size = n_change.size;
}
if n_change.safe_padding.is_some() {
change.safe_padding = n_change.safe_padding;
}
if n_change.scale_factor.is_some() {
change.scale_factor = n_change.scale_factor;
}
if n_change.refresh_rate.is_some() {
change.refresh_rate = n_change.refresh_rate;
}
change.frame_wait_id = n_change.frame_wait_id;
}
(FocusChanged { prev, new }, FocusChanged { prev: n_prev, new: n_new })
if prev.is_some() && new.is_none() && n_prev.is_none() && n_new.is_some() =>
{
*new = n_new;
}
(
Ime {
window,
ime: ime @ self::Ime::Preview(_, _),
},
Ime {
window: n_window,
ime: n_ime @ self::Ime::Commit(_),
},
) if *window == n_window => {
*ime = n_ime;
}
#[allow(deprecated)]
(
ScaleFactorChanged {
monitor,
windows,
scale_factor,
},
ScaleFactorChanged {
monitor: n_monitor,
windows: n_windows,
scale_factor: n_scale_factor,
},
) if *monitor == n_monitor => {
for w in n_windows {
if !windows.contains(&w) {
windows.push(w);
}
}
*scale_factor = n_scale_factor;
}
(FontsChanged, FontsChanged) => {}
(FontAaChanged(config), FontAaChanged(n_config)) => {
*config = n_config;
}
(MultiClickConfigChanged(config), MultiClickConfigChanged(n_config)) => {
*config = n_config;
}
(TouchConfigChanged(config), TouchConfigChanged(n_config)) => {
*config = n_config;
}
(AnimationsConfigChanged(config), AnimationsConfigChanged(n_config)) => {
*config = n_config;
}
(KeyRepeatConfigChanged(config), KeyRepeatConfigChanged(n_config)) => {
*config = n_config;
}
(LocaleChanged(config), LocaleChanged(n_config)) => {
*config = n_config;
}
(
DragHovered {
window,
data,
allowed: effects,
},
DragHovered {
window: n_window,
data: mut n_data,
allowed: n_effects,
},
) if *window == n_window && effects.contains(n_effects) => {
data.append(&mut n_data);
}
(
DragDropped {
window,
data,
allowed,
drop_id,
},
DragDropped {
window: n_window,
data: mut n_data,
allowed: n_allowed,
drop_id: n_drop_id,
},
) if *window == n_window && allowed.contains(n_allowed) && *drop_id == n_drop_id => {
data.append(&mut n_data);
}
(DragCancelled { window }, DragCancelled { window: n_window }) if *window == n_window => {}
(InputDevicesChanged(devices), InputDevicesChanged(n_devices)) => {
*devices = n_devices;
}
(AudioDevicesChanged(devices), AudioDevicesChanged(n_devices)) => {
*devices = n_devices;
}
(_, e) => return Err(e),
}
Ok(())
}
}
pub(crate) type VpResult<T> = std::result::Result<T, ChannelError>;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct GradientStop {
pub offset: f32,
pub color: Rgba,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct BorderSide {
pub color: Rgba,
pub style: BorderStyle,
}
#[derive(Default, Clone, Copy, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
#[repr(u8)]
pub enum TransformStyle {
#[default]
Flat = 0,
Preserve3D = 1,
}
impl fmt::Debug for TransformStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "TransformStyle::")?;
}
match self {
Self::Flat => write!(f, "Flat"),
Self::Preserve3D => write!(f, "Preserve3D"),
}
}
}
#[derive(Default, Debug, Clone, Copy, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
pub struct ReferenceFrameId(pub u64, pub u64);
impl ReferenceFrameId {
pub fn is_app_generated(&self) -> bool {
self.1 & (1 << 63) == 0
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize, serde::Deserialize, Default)]
pub enum RepeatMode {
#[default]
Stretch,
Repeat,
Round,
Space,
}
#[cfg(feature = "var")]
zng_var::impl_from_and_into_var! {
fn from(value: bool) -> RepeatMode {
match value {
true => RepeatMode::Repeat,
false => RepeatMode::Stretch,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Default)]
#[non_exhaustive]
pub enum MixBlendMode {
#[default]
Normal = 0,
Multiply = 1,
Screen = 2,
Overlay = 3,
Darken = 4,
Lighten = 5,
ColorDodge = 6,
ColorBurn = 7,
HardLight = 8,
SoftLight = 9,
Difference = 10,
Exclusion = 11,
Hue = 12,
Saturation = 13,
Color = 14,
Luminosity = 15,
PlusLighter = 16,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum ImageRendering {
Auto = 0,
CrispEdges = 1,
Pixelated = 2,
}
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
pub enum ExtendMode {
Clamp,
Repeat,
}
#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum LineOrientation {
Vertical,
Horizontal,
}
impl fmt::Debug for LineOrientation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "LineOrientation::")?;
}
match self {
LineOrientation::Vertical => {
write!(f, "Vertical")
}
LineOrientation::Horizontal => {
write!(f, "Horizontal")
}
}
}
}
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
pub enum LineStyle {
Solid,
Dotted,
Dashed,
Wavy(f32),
}
#[repr(u8)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum BorderStyle {
#[default]
None = 0,
Solid = 1,
Double = 2,
Dotted = 3,
Dashed = 4,
Hidden = 5,
Groove = 6,
Ridge = 7,
Inset = 8,
Outset = 9,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum FocusResult {
Requested,
AlreadyFocused,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct DeviceEventsFilter {
pub input: InputDeviceCapability,
}
impl DeviceEventsFilter {
pub fn empty() -> Self {
Self {
input: InputDeviceCapability::empty(),
}
}
pub fn is_empty(&self) -> bool {
self.input.is_empty()
}
pub fn new(input: InputDeviceCapability) -> Self {
Self { input }
}
}
impl Default for DeviceEventsFilter {
fn default() -> Self {
Self::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_code_iter() {
let mut iter = KeyCode::all_identified();
let first = iter.next().unwrap();
assert_eq!(first, KeyCode::Backquote);
for k in iter {
assert_eq!(k.name(), &format!("{k:?}"));
}
}
}