use std::cell::Cell;
use std::mem::ManuallyDrop;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::Environment;
use sctk::window::{Decorations, Window};
use wayland_protocols::viewporter::client::wp_viewport::WpViewport;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::env::WinitEnv;
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
use crate::platform_impl::wayland::protocols::wp_fractional_scale_v1::WpFractionalScaleV1;
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
use crate::platform_impl::wayland::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, Theme, UserAttentionType};
use super::WinitFrame;
#[derive(Debug, Clone)]
pub enum WindowRequest {
Fullscreen(Option<WlOutput>),
UnsetFullscreen,
ShowCursor(bool),
NewCursorIcon(CursorIcon),
SetCursorGrabMode(CursorGrabMode),
SetLockedCursorPosition(LogicalPosition<u32>),
DragWindow,
Maximize(bool),
Minimize,
Decorate(bool),
Resizeable(bool),
Title(String),
MinSize(Option<LogicalSize<u32>>),
MaxSize(Option<LogicalSize<u32>>),
FrameSize(LogicalSize<u32>),
ImePosition(LogicalPosition<u32>),
AllowIme(bool),
ImePurpose(ImePurpose),
Transparent(bool),
Attention(Option<UserAttentionType>),
PassthroughMouseInput(bool),
Redraw,
Close,
Theme(Option<Theme>),
}
#[derive(Default, Debug, Clone, Copy)]
pub struct WindowCompositorUpdate {
pub size: Option<LogicalSize<u32>>,
pub scale_factor: Option<f64>,
pub close_window: bool,
}
impl WindowCompositorUpdate {
pub fn new() -> Self {
Default::default()
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct WindowUserRequest {
pub redraw_requested: bool,
pub refresh_frame: bool,
}
impl WindowUserRequest {
pub fn new() -> Self {
Default::default()
}
}
pub struct WindowHandle {
pub window: ManuallyDrop<Window<WinitFrame>>,
pub fractional_scaling_state: Option<FractionalScalingState>,
pub scale_factor: Arc<Mutex<f64>>,
pub size: Arc<Mutex<LogicalSize<u32>>>,
pub pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
pub cursor_icon: Cell<CursorIcon>,
pub is_resizable: Cell<bool>,
pub has_focus: Arc<AtomicBool>,
pub ime_allowed: Cell<bool>,
pub ime_purpose: Cell<ImePurpose>,
pub transparent: Cell<bool>,
cursor_visible: Cell<bool>,
cursor_grab_mode: Cell<CursorGrabMode>,
pointers: Vec<WinitPointer>,
text_inputs: Vec<TextInputHandler>,
xdg_activation: Option<Attached<XdgActivationV1>>,
attention_requested: Cell<bool>,
compositor: Attached<WlCompositor>,
}
impl WindowHandle {
pub fn new(
env: &Environment<WinitEnv>,
window: Window<WinitFrame>,
size: Arc<Mutex<LogicalSize<u32>>>,
has_focus: Arc<AtomicBool>,
fractional_scaling_state: Option<FractionalScalingState>,
scale_factor: f64,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self {
let xdg_activation = env.get_global::<XdgActivationV1>();
let compositor = env.get_global::<WlCompositor>().unwrap();
Self {
window: ManuallyDrop::new(window),
fractional_scaling_state,
scale_factor: Arc::new(Mutex::new(scale_factor)),
size,
pending_window_requests,
cursor_icon: Cell::new(CursorIcon::Default),
is_resizable: Cell::new(true),
transparent: Cell::new(false),
cursor_grab_mode: Cell::new(CursorGrabMode::None),
cursor_visible: Cell::new(true),
pointers: Vec::new(),
text_inputs: Vec::new(),
xdg_activation,
attention_requested: Cell::new(false),
compositor,
ime_allowed: Cell::new(false),
ime_purpose: Cell::new(ImePurpose::default()),
has_focus,
}
}
pub fn scale_factor(&self) -> f64 {
*self.scale_factor.lock().unwrap()
}
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
let old_mode = self.cursor_grab_mode.replace(mode);
if old_mode == mode {
return;
}
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
}
let surface = self.window.surface();
match mode {
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
CursorGrabMode::None => {
}
}
}
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
self.pointers
.iter()
.for_each(|p| p.set_cursor_position(position.x, position.y));
}
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
None => return,
Some(xdg_activation) => xdg_activation,
};
if request_type.is_none() || self.attention_requested.get() {
return;
}
let xdg_activation_token = xdg_activation.get_activation_token();
let surface = self.window.surface();
let window_id = wayland::make_wid(surface);
let xdg_activation = xdg_activation.clone();
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
let token = match event {
xdg_activation_token_v1::Event::Done { token } => token,
_ => return,
};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
let surface = window_handle.window.surface();
xdg_activation.activate(token, surface);
window_handle.attention_requested.replace(false);
xdg_token.destroy();
});
xdg_activation_token.set_surface(surface);
xdg_activation_token.commit();
self.attention_requested.replace(true);
}
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
let position = self.pointers.iter().position(|p| *p == pointer);
if position.is_none() {
let surface = self.window.surface();
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.lock(surface),
CursorGrabMode::Confined => pointer.confine(surface),
}
self.pointers.push(pointer);
}
self.set_cursor_visible(self.cursor_visible.get());
}
pub fn pointer_left(&mut self, pointer: WinitPointer) {
let position = self.pointers.iter().position(|p| *p == pointer);
if let Some(position) = position {
let pointer = self.pointers.remove(position);
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.unlock(),
CursorGrabMode::Confined => pointer.unconfine(),
}
}
}
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
if !self.text_inputs.iter().any(|t| *t == text_input) {
self.text_inputs.push(text_input);
}
}
pub fn text_input_left(&mut self, text_input: TextInputHandler) {
if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) {
self.text_inputs.remove(position);
}
}
pub fn set_ime_position(&self, position: LogicalPosition<u32>) {
let (x, y) = (position.x as i32, position.y as i32);
for text_input in self.text_inputs.iter() {
text_input.set_ime_position(x, y);
}
}
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
if passthrough_mouse_input {
let region = self.compositor.create_region();
region.add(0, 0, 0, 0);
self.window
.surface()
.set_input_region(Some(®ion.detach()));
region.destroy();
} else {
self.window.surface().set_input_region(None);
}
}
pub fn set_transparent(&self, transparent: bool) {
self.transparent.set(transparent);
let surface = self.window.surface();
if transparent {
surface.set_opaque_region(None);
} else {
let region = self.compositor.create_region();
region.add(0, 0, i32::MAX, i32::MAX);
surface.set_opaque_region(Some(®ion.detach()));
region.destroy();
}
}
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
if self.ime_allowed.get() == allowed {
return;
}
self.ime_allowed.replace(allowed);
let window_id = wayland::make_wid(self.window.surface());
let purpose = allowed.then(|| self.ime_purpose.get());
for text_input in self.text_inputs.iter() {
text_input.set_input_allowed(purpose);
}
let event = if allowed {
WindowEvent::Ime(Ime::Enabled)
} else {
WindowEvent::Ime(Ime::Disabled)
};
event_sink.push_window_event(event, window_id);
}
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
if self.ime_purpose.get() == purpose {
return;
}
self.ime_purpose.replace(purpose);
if self.ime_allowed.get() {
for text_input in self.text_inputs.iter() {
text_input.set_content_type_by_purpose(purpose);
}
}
}
pub fn set_cursor_visible(&self, visible: bool) {
self.cursor_visible.replace(visible);
let cursor_icon = match visible {
true => Some(self.cursor_icon.get()),
false => None,
};
for pointer in self.pointers.iter() {
pointer.set_cursor(cursor_icon)
}
}
pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) {
self.cursor_icon.replace(cursor_icon);
if !self.cursor_visible.get() {
return;
}
for pointer in self.pointers.iter() {
pointer.set_cursor(Some(cursor_icon));
}
}
pub fn drag_window(&self) {
for pointer in self.pointers.iter() {
pointer.drag_window(&self.window);
}
}
}
#[inline]
pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_map = &mut winit_state.window_map;
let window_user_requests = &mut winit_state.window_user_requests;
let window_compositor_updates = &mut winit_state.window_compositor_updates;
let mut windows_to_close: Vec<WindowId> = Vec::new();
for (window_id, window_handle) in window_map.iter_mut() {
let mut requests = window_handle.pending_window_requests.lock().unwrap();
let requests = requests.drain(..);
for request in requests {
match request {
WindowRequest::Fullscreen(fullscreen) => {
window_handle.window.set_fullscreen(fullscreen.as_ref());
}
WindowRequest::UnsetFullscreen => {
window_handle.window.unset_fullscreen();
}
WindowRequest::ShowCursor(show_cursor) => {
window_handle.set_cursor_visible(show_cursor);
}
WindowRequest::NewCursorIcon(cursor_icon) => {
window_handle.set_cursor_icon(cursor_icon);
}
WindowRequest::ImePosition(position) => {
window_handle.set_ime_position(position);
}
WindowRequest::AllowIme(allow) => {
let event_sink = &mut winit_state.event_sink;
window_handle.set_ime_allowed(allow, event_sink);
}
WindowRequest::ImePurpose(purpose) => {
window_handle.set_ime_purpose(purpose);
}
WindowRequest::SetCursorGrabMode(mode) => {
window_handle.set_cursor_grab(mode);
}
WindowRequest::SetLockedCursorPosition(position) => {
window_handle.set_locked_cursor_position(position);
}
WindowRequest::DragWindow => {
window_handle.drag_window();
}
WindowRequest::Maximize(maximize) => {
if maximize {
window_handle.window.set_maximized();
} else {
window_handle.window.unset_maximized();
}
}
WindowRequest::Minimize => {
window_handle.window.set_minimized();
}
WindowRequest::Transparent(transparent) => {
window_handle.set_transparent(transparent);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.redraw_requested = true;
}
WindowRequest::Decorate(decorate) => {
let decorations = match decorate {
true => Decorations::FollowServer,
false => Decorations::None,
};
window_handle.window.set_decorate(decorations);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Resizeable(resizeable) => {
window_handle.window.set_resizable(resizeable);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Title(title) => {
window_handle.window.set_title(title);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::MinSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_min_size(size);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::MaxSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_max_size(size);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::FrameSize(size) => {
if !window_handle.is_resizable.get() {
let size = Some((size.width, size.height));
window_handle.window.set_max_size(size);
window_handle.window.set_min_size(size);
}
window_handle.window.resize(size.width, size.height);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::PassthroughMouseInput(passthrough) => {
window_handle.passthrough_mouse_input(passthrough);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Attention(request_type) => {
window_handle.set_user_attention(request_type);
}
WindowRequest::Redraw => {
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.redraw_requested = true;
}
WindowRequest::Close => {
windows_to_close.push(*window_id);
let event_sink = &mut winit_state.event_sink;
event_sink.push_window_event(WindowEvent::Destroyed, *window_id);
}
WindowRequest::Theme(_theme) => {
#[cfg(feature = "sctk-adwaita")]
{
window_handle.window.set_frame_config(match _theme {
Some(theme) => theme.into(),
None => sctk_adwaita::FrameConfig::auto(),
});
let window_requst = window_user_requests.get_mut(window_id).unwrap();
window_requst.refresh_frame = true;
}
}
};
}
}
for window in windows_to_close {
let _ = window_map.remove(&window);
let _ = window_user_requests.remove(&window);
let _ = window_compositor_updates.remove(&window);
}
}
impl Drop for WindowHandle {
fn drop(&mut self) {
let _ = self.fractional_scaling_state.take();
unsafe {
let surface = self.window.surface().clone();
ManuallyDrop::drop(&mut self.window);
surface.destroy();
}
}
}
pub struct FractionalScalingState {
pub viewport: WpViewport,
pub fractional_scale: WpFractionalScaleV1,
}
impl FractionalScalingState {
pub fn new(viewport: WpViewport, fractional_scale: WpFractionalScaleV1) -> Self {
Self {
viewport,
fractional_scale,
}
}
}
impl Drop for FractionalScalingState {
fn drop(&mut self) {
self.viewport.destroy();
self.fractional_scale.destroy();
}
}