use std::fmt;
use bitflags::bitflags;
use cursor_icon::CursorIcon;
use dpi::{
LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::as_any::AsAny;
use crate::cursor::Cursor;
use crate::error::RequestError;
use crate::icon::Icon;
use crate::monitor::{Fullscreen, MonitorHandle};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize);
impl WindowId {
pub const fn into_raw(self) -> usize {
self.0
}
pub const fn from_raw(id: usize) -> Self {
Self(id)
}
}
impl fmt::Debug for WindowId {
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmtr)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct WindowAttributes {
pub surface_size: Option<Size>,
pub min_surface_size: Option<Size>,
pub max_surface_size: Option<Size>,
pub surface_resize_increments: Option<Size>,
pub position: Option<Position>,
pub resizable: bool,
pub enabled_buttons: WindowButtons,
pub title: String,
pub maximized: bool,
pub visible: bool,
pub transparent: bool,
pub blur: bool,
pub decorations: bool,
pub window_icon: Option<Icon>,
pub preferred_theme: Option<Theme>,
pub content_protected: bool,
pub window_level: WindowLevel,
pub active: bool,
pub cursor: Cursor,
pub(crate) parent_window: Option<SendSyncRawWindowHandle>,
pub fullscreen: Option<Fullscreen>,
pub platform: Option<Box<dyn PlatformWindowAttributes>>,
}
impl WindowAttributes {
pub fn parent_window(&self) -> Option<&rwh_06::RawWindowHandle> {
self.parent_window.as_ref().map(|handle| &handle.0)
}
#[inline]
pub fn with_surface_size<S: Into<Size>>(mut self, size: S) -> Self {
self.surface_size = Some(size.into());
self
}
#[inline]
pub fn with_min_surface_size<S: Into<Size>>(mut self, min_size: S) -> Self {
self.min_surface_size = Some(min_size.into());
self
}
#[inline]
pub fn with_max_surface_size<S: Into<Size>>(mut self, max_size: S) -> Self {
self.max_surface_size = Some(max_size.into());
self
}
#[inline]
pub fn with_surface_resize_increments<S: Into<Size>>(
mut self,
surface_resize_increments: S,
) -> Self {
self.surface_resize_increments = Some(surface_resize_increments.into());
self
}
#[inline]
pub fn with_position<P: Into<Position>>(mut self, position: P) -> Self {
self.position = Some(position.into());
self
}
#[inline]
pub fn with_resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
#[inline]
pub fn with_enabled_buttons(mut self, buttons: WindowButtons) -> Self {
self.enabled_buttons = buttons;
self
}
#[inline]
pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
self.title = title.into();
self
}
#[inline]
pub fn with_fullscreen(mut self, fullscreen: Option<Fullscreen>) -> Self {
self.fullscreen = fullscreen;
self
}
#[inline]
pub fn with_maximized(mut self, maximized: bool) -> Self {
self.maximized = maximized;
self
}
#[inline]
pub fn with_visible(mut self, visible: bool) -> Self {
self.visible = visible;
self
}
#[inline]
pub fn with_transparent(mut self, transparent: bool) -> Self {
self.transparent = transparent;
self
}
#[inline]
pub fn with_blur(mut self, blur: bool) -> Self {
self.blur = blur;
self
}
#[inline]
pub fn transparent(&self) -> bool {
self.transparent
}
#[inline]
pub fn with_decorations(mut self, decorations: bool) -> Self {
self.decorations = decorations;
self
}
#[inline]
pub fn with_window_level(mut self, level: WindowLevel) -> Self {
self.window_level = level;
self
}
#[inline]
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> Self {
self.window_icon = window_icon;
self
}
#[inline]
pub fn with_theme(mut self, theme: Option<Theme>) -> Self {
self.preferred_theme = theme;
self
}
#[inline]
pub fn with_content_protected(mut self, protected: bool) -> Self {
self.content_protected = protected;
self
}
#[inline]
pub fn with_active(mut self, active: bool) -> Self {
self.active = active;
self
}
#[inline]
pub fn with_cursor(mut self, cursor: impl Into<Cursor>) -> Self {
self.cursor = cursor.into();
self
}
#[inline]
pub unsafe fn with_parent_window(
mut self,
parent_window: Option<rwh_06::RawWindowHandle>,
) -> Self {
self.parent_window = parent_window.map(SendSyncRawWindowHandle);
self
}
#[inline]
pub fn with_platform_attributes(mut self, platform: Box<dyn PlatformWindowAttributes>) -> Self {
self.platform = Some(platform);
self
}
}
impl Clone for WindowAttributes {
fn clone(&self) -> Self {
Self {
surface_size: self.surface_size,
min_surface_size: self.min_surface_size,
max_surface_size: self.max_surface_size,
surface_resize_increments: self.surface_resize_increments,
position: self.position,
resizable: self.resizable,
enabled_buttons: self.enabled_buttons,
title: self.title.clone(),
maximized: self.maximized,
visible: self.visible,
transparent: self.transparent,
blur: self.blur,
decorations: self.decorations,
window_icon: self.window_icon.clone(),
preferred_theme: self.preferred_theme,
content_protected: self.content_protected,
window_level: self.window_level,
active: self.active,
cursor: self.cursor.clone(),
parent_window: self.parent_window.clone(),
fullscreen: self.fullscreen.clone(),
platform: self.platform.as_ref().map(|platform| platform.box_clone()),
}
}
}
impl Default for WindowAttributes {
#[inline]
fn default() -> WindowAttributes {
WindowAttributes {
enabled_buttons: WindowButtons::all(),
title: String::from("winit window"),
decorations: true,
resizable: true,
visible: true,
active: true,
surface_resize_increments: Default::default(),
content_protected: Default::default(),
min_surface_size: Default::default(),
max_surface_size: Default::default(),
preferred_theme: Default::default(),
parent_window: Default::default(),
surface_size: Default::default(),
window_level: Default::default(),
window_icon: Default::default(),
transparent: Default::default(),
fullscreen: Default::default(),
maximized: Default::default(),
position: Default::default(),
platform: Default::default(),
cursor: Cursor::default(),
blur: Default::default(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SendSyncRawWindowHandle(pub(crate) rwh_06::RawWindowHandle);
unsafe impl Send for SendSyncRawWindowHandle {}
unsafe impl Sync for SendSyncRawWindowHandle {}
pub trait PlatformWindowAttributes: AsAny + std::fmt::Debug + Send + Sync {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes>;
}
impl_dyn_casting!(PlatformWindowAttributes);
pub trait Window: AsAny + Send + Sync + fmt::Debug {
fn id(&self) -> WindowId;
fn scale_factor(&self) -> f64;
fn request_redraw(&self);
fn pre_present_notify(&self);
fn reset_dead_keys(&self);
fn surface_position(&self) -> PhysicalPosition<i32>;
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError>;
fn set_outer_position(&self, position: Position);
fn surface_size(&self) -> PhysicalSize<u32>;
#[must_use]
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>>;
fn outer_size(&self) -> PhysicalSize<u32>;
fn safe_area(&self) -> PhysicalInsets<u32>;
fn set_min_surface_size(&self, min_size: Option<Size>);
fn set_max_surface_size(&self, max_size: Option<Size>);
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>>;
fn set_surface_resize_increments(&self, increments: Option<Size>);
fn set_title(&self, title: &str);
fn set_transparent(&self, transparent: bool);
fn set_blur(&self, blur: bool);
fn set_visible(&self, visible: bool);
fn is_visible(&self) -> Option<bool>;
fn set_resizable(&self, resizable: bool);
fn is_resizable(&self) -> bool;
fn set_enabled_buttons(&self, buttons: WindowButtons);
fn enabled_buttons(&self) -> WindowButtons;
fn set_minimized(&self, minimized: bool);
fn is_minimized(&self) -> Option<bool>;
fn set_maximized(&self, maximized: bool);
fn is_maximized(&self) -> bool;
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>);
fn fullscreen(&self) -> Option<Fullscreen>;
fn set_decorations(&self, decorations: bool);
fn is_decorated(&self) -> bool;
fn set_window_level(&self, level: WindowLevel);
fn set_window_icon(&self, window_icon: Option<Icon>);
#[deprecated = "use Window::request_ime_update instead"]
fn set_ime_cursor_area(&self, position: Position, size: Size) {
if self.ime_capabilities().map(|caps| caps.cursor_area()).unwrap_or(false) {
let _ = self.request_ime_update(ImeRequest::Update(
ImeRequestData::default().with_cursor_area(position, size),
));
}
}
#[deprecated = "use Window::request_ime_update instead"]
fn set_ime_allowed(&self, allowed: bool) {
let action = if allowed {
let position = LogicalPosition::new(0, 0);
let size = LogicalSize::new(0, 0);
let ime_caps = ImeCapabilities::new().with_hint_and_purpose().with_cursor_area();
let request_data = ImeRequestData {
hint_and_purpose: Some((ImeHint::NONE, ImePurpose::Normal)),
cursor_area: Some((position.into(), size.into())),
..ImeRequestData::default()
};
ImeRequest::Enable(ImeEnableRequest::new(ime_caps, request_data).unwrap())
} else {
ImeRequest::Disable
};
let _ = self.request_ime_update(action);
}
#[deprecated = "use Window::request_ime_update instead"]
fn set_ime_purpose(&self, purpose: ImePurpose) {
if self.ime_capabilities().map(|caps| caps.hint_and_purpose()).unwrap_or(false) {
let _ = self.request_ime_update(ImeRequest::Update(ImeRequestData {
hint_and_purpose: Some((ImeHint::NONE, purpose)),
..ImeRequestData::default()
}));
}
}
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError>;
fn ime_capabilities(&self) -> Option<ImeCapabilities>;
fn focus_window(&self);
fn has_focus(&self) -> bool;
fn request_user_attention(&self, request_type: Option<UserAttentionType>);
fn set_theme(&self, theme: Option<Theme>);
fn theme(&self) -> Option<Theme>;
fn set_content_protected(&self, protected: bool);
fn title(&self) -> String;
fn set_cursor(&self, cursor: Cursor);
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError>;
fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError>;
fn set_cursor_visible(&self, visible: bool);
fn drag_window(&self) -> Result<(), RequestError>;
fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError>;
fn show_window_menu(&self, position: Position);
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError>;
fn current_monitor(&self) -> Option<MonitorHandle>;
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
fn primary_monitor(&self) -> Option<MonitorHandle>;
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle;
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle;
}
impl_dyn_casting!(Window);
impl PartialEq for dyn Window + '_ {
fn eq(&self, other: &dyn Window) -> bool {
self.id().eq(&other.id())
}
}
impl Eq for dyn Window + '_ {}
impl std::hash::Hash for dyn Window + '_ {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl rwh_06::HasDisplayHandle for dyn Window + '_ {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
self.rwh_06_display_handle().display_handle()
}
}
impl rwh_06::HasWindowHandle for dyn Window + '_ {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
self.rwh_06_window_handle().window_handle()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CursorGrabMode {
None,
Confined,
Locked,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, 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,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Theme {
Light,
Dark,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum UserAttentionType {
Critical,
#[default]
Informational,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowButtons: u32 {
const CLOSE = 1 << 0;
const MINIMIZE = 1 << 1;
const MAXIMIZE = 1 << 2;
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowLevel {
AlwaysOnBottom,
#[default]
Normal,
AlwaysOnTop,
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ImePurpose {
#[default]
Normal,
Password,
Terminal,
Number,
Phone,
Url,
Email,
Pin,
Date,
Time,
DateTime,
}
bitflags! {
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ImeHint: u32 {
const NONE = 0;
const COMPLETION = 0x1;
const SPELLCHECK = 0x2;
const AUTO_CAPITALIZATION = 0x4;
const LOWERCASE = 0x8;
const UPPERCASE = 0x10;
const TITLECASE = 0x20;
const HIDDEN_TEXT = 0x40;
const SENSITIVE_DATA = 0x80;
const LATIN = 0x100;
const MULTILINE = 0x200;
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ImeSurroundingTextError {
TextTooLong,
CursorBadPosition,
AnchorBadPosition,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ImeSurroundingText {
text: String,
cursor: usize,
anchor: usize,
}
impl ImeSurroundingText {
pub const MAX_TEXT_BYTES: usize = 4000;
pub fn new(
text: String,
cursor: usize,
anchor: usize,
) -> Result<Self, ImeSurroundingTextError> {
let text = if text.len() < 4000 {
text
} else {
return Err(ImeSurroundingTextError::TextTooLong);
};
let cursor = if text.is_char_boundary(cursor) && cursor <= text.len() {
cursor
} else {
return Err(ImeSurroundingTextError::CursorBadPosition);
};
let anchor = if text.is_char_boundary(anchor) && anchor <= text.len() {
anchor
} else {
return Err(ImeSurroundingTextError::AnchorBadPosition);
};
Ok(Self { text, cursor, anchor })
}
pub fn into_text(self) -> String {
self.text
}
pub fn text(&self) -> &str {
&self.text
}
pub fn cursor(&self) -> usize {
self.cursor
}
pub fn anchor(&self) -> usize {
self.anchor
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum ImeRequest {
Enable(ImeEnableRequest),
Update(ImeRequestData),
Disable,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImeEnableRequest {
capabilities: ImeCapabilities,
request_data: ImeRequestData,
}
impl ImeEnableRequest {
pub fn new(capabilities: ImeCapabilities, request_data: ImeRequestData) -> Option<Self> {
if capabilities.cursor_area() ^ request_data.cursor_area.is_some() {
return None;
}
if capabilities.hint_and_purpose() ^ request_data.hint_and_purpose.is_some() {
return None;
}
if capabilities.surrounding_text() ^ request_data.surrounding_text.is_some() {
return None;
}
Some(Self { capabilities, request_data })
}
pub const fn capabilities(&self) -> &ImeCapabilities {
&self.capabilities
}
pub const fn request_data(&self) -> &ImeRequestData {
&self.request_data
}
pub fn into_raw(self) -> (ImeCapabilities, ImeRequestData) {
(self.capabilities, self.request_data)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ImeCapabilities(ImeCapabilitiesFlags);
impl ImeCapabilities {
pub fn new() -> Self {
Self::default()
}
pub const fn with_hint_and_purpose(self) -> Self {
Self(self.0.union(ImeCapabilitiesFlags::HINT_AND_PURPOSE))
}
pub const fn without_hint_and_purpose(self) -> Self {
Self(self.0.difference(ImeCapabilitiesFlags::HINT_AND_PURPOSE))
}
pub const fn hint_and_purpose(&self) -> bool {
self.0.contains(ImeCapabilitiesFlags::HINT_AND_PURPOSE)
}
pub const fn with_cursor_area(self) -> Self {
Self(self.0.union(ImeCapabilitiesFlags::CURSOR_AREA))
}
pub const fn without_cursor_area(self) -> Self {
Self(self.0.difference(ImeCapabilitiesFlags::CURSOR_AREA))
}
pub const fn cursor_area(&self) -> bool {
self.0.contains(ImeCapabilitiesFlags::CURSOR_AREA)
}
pub const fn with_surrounding_text(self) -> Self {
Self(self.0.union(ImeCapabilitiesFlags::SURROUNDING_TEXT))
}
pub const fn without_surrounding_text(self) -> Self {
Self(self.0.difference(ImeCapabilitiesFlags::SURROUNDING_TEXT))
}
pub const fn surrounding_text(&self) -> bool {
self.0.contains(ImeCapabilitiesFlags::SURROUNDING_TEXT)
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub(crate) struct ImeCapabilitiesFlags : u8 {
const HINT_AND_PURPOSE = 1 << 0;
const CURSOR_AREA = 1 << 1;
const SURROUNDING_TEXT = 1 << 2;
}
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ImeRequestData {
pub hint_and_purpose: Option<(ImeHint, ImePurpose)>,
pub cursor_area: Option<(Position, Size)>,
pub surrounding_text: Option<ImeSurroundingText>,
}
impl ImeRequestData {
pub fn with_hint_and_purpose(self, hint: ImeHint, purpose: ImePurpose) -> Self {
Self { hint_and_purpose: Some((hint, purpose)), ..self }
}
pub fn with_cursor_area(self, position: Position, size: Size) -> Self {
Self { cursor_area: Some((position, size)), ..self }
}
pub fn with_surrounding_text(self, surrounding_text: ImeSurroundingText) -> Self {
Self { surrounding_text: Some(surrounding_text), ..self }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ImeRequestError {
NotEnabled,
AlreadyEnabled,
NotSupported,
}
impl fmt::Display for ImeRequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImeRequestError::NotEnabled => write!(f, "ime is not enabled."),
ImeRequestError::AlreadyEnabled => write!(f, "ime is already enabled."),
ImeRequestError::NotSupported => write!(f, "ime is not supported."),
}
}
}
impl std::error::Error for ImeRequestError {}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct ActivationToken {
pub(crate) token: String,
}
impl ActivationToken {
pub fn from_raw(token: String) -> Self {
Self { token }
}
pub fn into_raw(self) -> String {
self.token
}
pub fn as_raw(&self) -> &str {
&self.token
}
}
#[cfg(test)]
mod tests {
use dpi::{LogicalPosition, LogicalSize, Position, Size};
use super::{
ImeCapabilities, ImeEnableRequest, ImeRequestData, ImeSurroundingText,
ImeSurroundingTextError,
};
use crate::window::{ImeHint, ImePurpose};
#[test]
fn ime_initial_request_caps_match() {
let position: Position = LogicalPosition::new(0, 0).into();
let size: Size = LogicalSize::new(0, 0).into();
assert!(
ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default()
)
.is_none()
);
assert!(
ImeEnableRequest::new(
ImeCapabilities::new().with_hint_and_purpose(),
ImeRequestData::default()
)
.is_none()
);
assert!(
ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default().with_hint_and_purpose(ImeHint::NONE, ImePurpose::Normal)
)
.is_none()
);
assert!(
ImeEnableRequest::new(
ImeCapabilities::new(),
ImeRequestData::default()
.with_hint_and_purpose(ImeHint::NONE, ImePurpose::Normal)
.with_cursor_area(position, size)
)
.is_none()
);
assert!(
ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default()
.with_hint_and_purpose(ImeHint::NONE, ImePurpose::Normal)
.with_cursor_area(position, size)
)
.is_none()
);
assert!(
ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default().with_cursor_area(position, size)
)
.is_some()
);
assert!(
ImeEnableRequest::new(
ImeCapabilities::new().with_hint_and_purpose().with_cursor_area(),
ImeRequestData::default()
.with_hint_and_purpose(ImeHint::NONE, ImePurpose::Normal)
.with_cursor_area(position, size)
)
.is_some()
);
let text: &[u8] = ['a' as u8; 8000].as_slice();
let text = std::str::from_utf8(text).unwrap();
assert_eq!(
ImeSurroundingText::new(text.into(), 0, 0),
Err(ImeSurroundingTextError::TextTooLong),
);
assert_eq!(
ImeSurroundingText::new("short".into(), 110, 0),
Err(ImeSurroundingTextError::CursorBadPosition),
);
assert_eq!(
ImeSurroundingText::new("граница".into(), 1, 0),
Err(ImeSurroundingTextError::CursorBadPosition),
);
}
}