mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-30 15:17:25 +00:00

On macOS and Windows, use winit's `Window::theme()` instead of dark-light, to detect the theme. Always react to WindowEvent::ThemeChange. It's delivered on macOS, Windows, and the web.
781 lines
32 KiB
Rust
781 lines
32 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
//! This module contains the GraphicsWindow that used to be within corelib.
|
|
|
|
// cspell:ignore borderless corelib nesw webgl winit winsys xlib
|
|
|
|
use core::cell::{Cell, RefCell};
|
|
use core::pin::Pin;
|
|
use std::rc::{Rc, Weak};
|
|
|
|
use crate::event_loop::WinitWindow;
|
|
use crate::renderer::WinitCompatibleRenderer;
|
|
use const_field_offset::FieldOffsets;
|
|
use corelib::component::ComponentRc;
|
|
use corelib::graphics::euclid::num::Zero;
|
|
use corelib::items::MouseCursor;
|
|
use corelib::layout::Orientation;
|
|
use corelib::lengths::{LogicalLength, LogicalPoint, LogicalSize};
|
|
use corelib::platform::PlatformError;
|
|
use corelib::window::{WindowAdapter, WindowAdapterSealed, WindowInner};
|
|
use corelib::Property;
|
|
use corelib::{graphics::*, Coord};
|
|
use i_slint_core as corelib;
|
|
|
|
fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position {
|
|
match pos {
|
|
corelib::api::WindowPosition::Logical(pos) => {
|
|
winit::dpi::Position::new(winit::dpi::LogicalPosition::new(pos.x, pos.y))
|
|
}
|
|
corelib::api::WindowPosition::Physical(pos) => {
|
|
winit::dpi::Position::new(winit::dpi::PhysicalPosition::new(pos.x, pos.y))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn window_size_to_slint(size: &corelib::api::WindowSize) -> winit::dpi::Size {
|
|
match size {
|
|
corelib::api::WindowSize::Logical(size) => {
|
|
winit::dpi::Size::new(winit::dpi::LogicalSize::new(size.width, size.height))
|
|
}
|
|
corelib::api::WindowSize::Physical(size) => {
|
|
winit::dpi::Size::new(winit::dpi::PhysicalSize::new(size.width, size.height))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn physical_size_to_slint(size: &winit::dpi::PhysicalSize<u32>) -> corelib::api::PhysicalSize {
|
|
corelib::api::PhysicalSize::new(size.width, size.height)
|
|
}
|
|
|
|
fn icon_to_winit(icon: corelib::graphics::Image) -> Option<winit::window::Icon> {
|
|
let image_inner: &ImageInner = (&icon).into();
|
|
|
|
let pixel_buffer = match image_inner {
|
|
ImageInner::EmbeddedImage { buffer, .. } => buffer.clone(),
|
|
_ => return None,
|
|
};
|
|
|
|
// This could become a method in SharedPixelBuffer...
|
|
let rgba_pixels: Vec<u8> = match &pixel_buffer {
|
|
SharedImageBuffer::RGB8(pixels) => pixels
|
|
.as_bytes()
|
|
.chunks(3)
|
|
.flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255]))
|
|
.collect(),
|
|
SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().to_vec(),
|
|
SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels
|
|
.as_bytes()
|
|
.chunks(4)
|
|
.flat_map(|rgba| {
|
|
let alpha = rgba[3] as u32;
|
|
IntoIterator::into_iter(rgba)
|
|
.take(3)
|
|
.map(move |component| (*component as u32 * alpha / 255) as u8)
|
|
.chain(std::iter::once(alpha as u8))
|
|
})
|
|
.collect(),
|
|
};
|
|
|
|
winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok()
|
|
}
|
|
|
|
/// GraphicsWindow is an implementation of the [WindowAdapter][`crate::eventloop::WindowAdapter`] trait. This is
|
|
/// typically instantiated by entry factory functions of the different graphics back ends.
|
|
pub(crate) struct GLWindow<Renderer: WinitCompatibleRenderer + 'static> {
|
|
window: corelib::api::Window,
|
|
self_weak: Weak<Self>,
|
|
map_state: RefCell<GraphicsWindowBackendState>,
|
|
currently_pressed_key_code: std::cell::Cell<Option<winit::event::VirtualKeyCode>>,
|
|
pending_redraw: Cell<bool>,
|
|
in_resize_event: Cell<bool>,
|
|
dark_color_scheme: once_cell::unsync::OnceCell<Pin<Box<Property<bool>>>>,
|
|
|
|
renderer: Renderer,
|
|
#[cfg(target_arch = "wasm32")]
|
|
canvas_id: String,
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
virtual_keyboard_helper: RefCell<Option<super::wasm_input_helper::WasmInputHelper>>,
|
|
}
|
|
|
|
impl<Renderer: WinitCompatibleRenderer + 'static> GLWindow<Renderer> {
|
|
/// Creates a new reference-counted instance.
|
|
///
|
|
/// Arguments:
|
|
/// * `graphics_backend_factory`: The factor function stored in the GraphicsWindow that's called when the state
|
|
/// of the window changes to mapped. The event loop and window builder parameters can be used to create a
|
|
/// backing window.
|
|
pub(crate) fn new(#[cfg(target_arch = "wasm32")] canvas_id: String) -> Rc<dyn WindowAdapter> {
|
|
let self_rc = Rc::new_cyclic(|self_weak| Self {
|
|
window: corelib::api::Window::new(self_weak.clone() as _),
|
|
self_weak: self_weak.clone(),
|
|
map_state: RefCell::new(GraphicsWindowBackendState::Unmapped {
|
|
requested_position: None,
|
|
requested_size: None,
|
|
}),
|
|
currently_pressed_key_code: Default::default(),
|
|
pending_redraw: Cell::new(false),
|
|
in_resize_event: Cell::new(false),
|
|
dark_color_scheme: Default::default(),
|
|
renderer: Renderer::new(&(self_weak.clone() as _)),
|
|
#[cfg(target_arch = "wasm32")]
|
|
canvas_id,
|
|
#[cfg(target_arch = "wasm32")]
|
|
virtual_keyboard_helper: Default::default(),
|
|
});
|
|
self_rc as _
|
|
}
|
|
|
|
fn is_mapped(&self) -> bool {
|
|
matches!(&*self.map_state.borrow(), GraphicsWindowBackendState::Mapped { .. })
|
|
}
|
|
|
|
fn borrow_mapped_window(&self) -> Option<std::cell::Ref<MappedWindow>> {
|
|
if self.is_mapped() {
|
|
std::cell::Ref::map(self.map_state.borrow(), |state| match state {
|
|
GraphicsWindowBackendState::Unmapped{..} => {
|
|
panic!("borrow_mapped_window must be called after checking if the window is mapped")
|
|
}
|
|
GraphicsWindowBackendState::Mapped(window) => window,
|
|
}).into()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn unmap(&self) {
|
|
let old_mapped = match self.map_state.replace(GraphicsWindowBackendState::Unmapped {
|
|
requested_position: None,
|
|
requested_size: None,
|
|
}) {
|
|
GraphicsWindowBackendState::Unmapped { .. } => return,
|
|
GraphicsWindowBackendState::Mapped(old_mapped) => old_mapped,
|
|
};
|
|
|
|
crate::event_loop::unregister_window(old_mapped.winit_window.id());
|
|
|
|
self.renderer.hide();
|
|
}
|
|
|
|
fn call_with_event_loop(&self, callback: fn(&Self)) {
|
|
// With wasm, winit's `run()` consumes the event loop and access to it from outside the event handler yields
|
|
// loop and thus ends up trying to create a new event loop instance, which panics in winit. Instead, forward
|
|
// the call to be invoked from within the event loop
|
|
#[cfg(target_arch = "wasm32")]
|
|
corelib::api::invoke_from_event_loop({
|
|
let self_weak = send_wrapper::SendWrapper::new(self.self_weak.clone());
|
|
|
|
move || {
|
|
if let Some(this) = self_weak.take().upgrade() {
|
|
callback(&this)
|
|
}
|
|
}
|
|
})
|
|
.unwrap();
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
callback(self)
|
|
}
|
|
}
|
|
|
|
impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Renderer> {
|
|
fn take_pending_redraw(&self) -> bool {
|
|
self.pending_redraw.take()
|
|
}
|
|
|
|
fn currently_pressed_key_code(&self) -> &Cell<Option<winit::event::VirtualKeyCode>> {
|
|
&self.currently_pressed_key_code
|
|
}
|
|
|
|
/// Draw the items of the specified `component` in the given window.
|
|
fn draw(&self) -> bool {
|
|
let window = match self.borrow_mapped_window() {
|
|
Some(window) => window,
|
|
None => return false, // caller bug, doesn't make sense to call draw() when not mapped
|
|
};
|
|
|
|
self.pending_redraw.set(false);
|
|
|
|
self.renderer.render(physical_size_to_slint(&window.winit_window.inner_size()));
|
|
|
|
self.pending_redraw.get()
|
|
}
|
|
|
|
fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window)) {
|
|
if let Some(mapped_window) = self.borrow_mapped_window() {
|
|
callback(&mapped_window.winit_window);
|
|
}
|
|
}
|
|
|
|
fn constraints(&self) -> (corelib::layout::LayoutInfo, corelib::layout::LayoutInfo) {
|
|
self.borrow_mapped_window().map(|window| window.constraints.get()).unwrap_or_default()
|
|
}
|
|
|
|
fn set_constraints(
|
|
&self,
|
|
constraints: (corelib::layout::LayoutInfo, corelib::layout::LayoutInfo),
|
|
) {
|
|
if let Some(window) = self.borrow_mapped_window() {
|
|
window.constraints.set(constraints);
|
|
}
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
fn input_method_focused(&self) -> bool {
|
|
match self.virtual_keyboard_helper.try_borrow() {
|
|
Ok(vkh) => vkh.as_ref().map_or(false, |h| h.has_focus()),
|
|
// the only location in which the virtual_keyboard_helper is mutably borrowed is from
|
|
// show_virtual_keyboard, which means we have the focus
|
|
Err(_) => true,
|
|
}
|
|
}
|
|
|
|
fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) {
|
|
// slint::Window::set_size will call set_size() on this type, which would call
|
|
// set_inner_size on the winit Window. On Windows that triggers an new resize event
|
|
// in the next event loop iteration for mysterious reasons, with slightly different sizes.
|
|
// I suspect a bug in the way the frame decorations are added and subtracted from the size
|
|
// we provide.
|
|
// Work around it with this guard that prevents us from calling set_inner_size again.
|
|
assert!(!self.in_resize_event.get());
|
|
self.in_resize_event.set(true);
|
|
scopeguard::defer! { self.in_resize_event.set(false); }
|
|
|
|
// When a window is minimized on Windows, we get a move event to an off-screen position
|
|
// and a resize even with a zero size. Don't forward that, especially not to the renderer,
|
|
// which might panic when trying to create a zero-sized surface.
|
|
if size.width > 0 && size.height > 0 {
|
|
let physical_size = physical_size_to_slint(&size);
|
|
self.window.set_size(physical_size);
|
|
self.renderer.resize_event(physical_size);
|
|
}
|
|
}
|
|
|
|
fn set_dark_color_scheme(&self, dark_mode: bool) {
|
|
self.dark_color_scheme
|
|
.get_or_init(|| Box::pin(Property::new(false)))
|
|
.as_ref()
|
|
.set(dark_mode)
|
|
}
|
|
}
|
|
|
|
impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapter for GLWindow<Renderer> {
|
|
fn window(&self) -> &corelib::api::Window {
|
|
&self.window
|
|
}
|
|
}
|
|
|
|
impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWindow<Renderer> {
|
|
fn request_redraw(&self) {
|
|
self.pending_redraw.set(true);
|
|
self.with_window_handle(&mut |window| window.request_redraw())
|
|
}
|
|
|
|
fn request_window_properties_update(&self) {
|
|
self.call_with_event_loop(|self_| {
|
|
self_.with_window_handle(&mut |window| {
|
|
let window_id = window.id();
|
|
crate::event_loop::with_window_target(|event_loop| {
|
|
event_loop.event_loop_proxy().send_event(
|
|
crate::event_loop::CustomEvent::UpdateWindowProperties(window_id),
|
|
)
|
|
})
|
|
.ok();
|
|
})
|
|
});
|
|
}
|
|
|
|
fn apply_window_properties(&self, window_item: Pin<&i_slint_core::items::WindowItem>) {
|
|
// Make the unwrap() calls on self.borrow_mapped_window*() safe
|
|
if !self.is_mapped() {
|
|
return;
|
|
}
|
|
|
|
let mut width = window_item.width().get() as f32;
|
|
let mut height = window_item.height().get() as f32;
|
|
|
|
let mut must_resize = false;
|
|
|
|
self.with_window_handle(&mut |winit_window| {
|
|
winit_window.set_window_icon(icon_to_winit(window_item.icon()));
|
|
winit_window.set_title(&window_item.title());
|
|
winit_window
|
|
.set_decorations(!window_item.no_frame() || winit_window.fullscreen().is_some());
|
|
|
|
if width <= 0. || height <= 0. {
|
|
must_resize = true;
|
|
|
|
let winit_size =
|
|
winit_window.inner_size().to_logical(self.window.scale_factor() as f64);
|
|
|
|
if width <= 0. {
|
|
width = winit_size.width;
|
|
}
|
|
if height <= 0. {
|
|
height = winit_size.height;
|
|
}
|
|
}
|
|
|
|
let existing_size: winit::dpi::LogicalSize<f32> =
|
|
winit_window.inner_size().to_logical(self.window.scale_factor().into());
|
|
|
|
if (existing_size.width - width).abs() > 1.
|
|
|| (existing_size.height - height).abs() > 1.
|
|
{
|
|
// If we're in fullscreen state, don't try to resize the window but maintain the surface
|
|
// size we've been assigned to from the windowing system. Weston/Wayland don't like it
|
|
// when we create a surface that's bigger than the screen due to constraints (#532).
|
|
if winit_window.fullscreen().is_none() {
|
|
winit_window.set_inner_size(winit::dpi::LogicalSize::new(width, height));
|
|
}
|
|
}
|
|
});
|
|
|
|
if must_resize {
|
|
self.window.set_size(i_slint_core::api::LogicalSize::new(width, height));
|
|
}
|
|
}
|
|
|
|
fn apply_geometry_constraint(
|
|
&self,
|
|
constraints_horizontal: corelib::layout::LayoutInfo,
|
|
constraints_vertical: corelib::layout::LayoutInfo,
|
|
) {
|
|
self.with_window_handle(&mut |winit_window| {
|
|
// If we're in fullscreen state, don't try to resize the window but maintain the surface
|
|
// size we've been assigned to from the windowing system. Weston/Wayland don't like it
|
|
// when we create a surface that's bigger than the screen due to constraints (#532).
|
|
if winit_window.fullscreen().is_some() {
|
|
return;
|
|
}
|
|
|
|
if (constraints_horizontal, constraints_vertical) != self.constraints() {
|
|
let min_width = constraints_horizontal.min.min(constraints_horizontal.max) as f32;
|
|
let min_height = constraints_vertical.min.min(constraints_vertical.max) as f32;
|
|
let max_width = constraints_horizontal.max.max(constraints_horizontal.min) as f32;
|
|
let max_height = constraints_vertical.max.max(constraints_vertical.min) as f32;
|
|
|
|
let sf = self.window.scale_factor();
|
|
|
|
winit_window.set_resizable(true);
|
|
winit_window.set_min_inner_size(if min_width > 0. || min_height > 0. {
|
|
Some(winit::dpi::PhysicalSize::new(min_width * sf, min_height * sf))
|
|
} else {
|
|
None
|
|
});
|
|
winit_window.set_max_inner_size(
|
|
if max_width < i32::MAX as f32 || max_height < i32::MAX as f32 {
|
|
Some(winit::dpi::PhysicalSize::new(
|
|
(max_width * sf).min(65535.),
|
|
(max_height * sf).min(65535.),
|
|
))
|
|
} else {
|
|
None
|
|
},
|
|
);
|
|
self.set_constraints((constraints_horizontal, constraints_vertical));
|
|
winit_window.set_resizable(min_width < max_width || min_height < max_height);
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
{
|
|
// set_max_inner_size / set_min_inner_size don't work on wasm, so apply the size manually
|
|
let existing_size: winit::dpi::LogicalSize<f32> =
|
|
winit_window.inner_size().to_logical(sf as f64);
|
|
if !(min_width..=max_width).contains(&(existing_size.width))
|
|
|| !(min_height..=max_height).contains(&(existing_size.height))
|
|
{
|
|
let new_size = winit::dpi::LogicalSize::new(
|
|
existing_size.width.min(max_width).max(min_width),
|
|
existing_size.height.min(max_height).max(min_height),
|
|
);
|
|
winit_window.set_inner_size(new_size);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn show(&self) -> Result<(), PlatformError> {
|
|
self.call_with_event_loop(|self_| {
|
|
let (requested_position, requested_size) = match &*self_.map_state.borrow() {
|
|
GraphicsWindowBackendState::Unmapped { requested_position, requested_size } => {
|
|
(requested_position.clone(), requested_size.clone())
|
|
}
|
|
GraphicsWindowBackendState::Mapped(_) => return,
|
|
};
|
|
|
|
let mut window_builder = winit::window::WindowBuilder::new();
|
|
|
|
let runtime_window = WindowInner::from_pub(&self_.window);
|
|
let component_rc = runtime_window.component();
|
|
let component = ComponentRc::borrow_pin(&component_rc);
|
|
|
|
window_builder = if let Some(window_item) =
|
|
runtime_window.window_item().as_ref().map(|i| i.as_pin_ref())
|
|
{
|
|
window_builder
|
|
.with_title(window_item.title().to_string())
|
|
.with_resizable(
|
|
window_item.height() <= LogicalLength::zero()
|
|
|| window_item.width() <= LogicalLength::zero(),
|
|
)
|
|
.with_decorations(!window_item.no_frame())
|
|
.with_window_icon(icon_to_winit(window_item.icon()))
|
|
} else {
|
|
window_builder.with_title("Slint Window".to_string())
|
|
};
|
|
|
|
let scale_factor_override = runtime_window.scale_factor();
|
|
// If the scale factor was already set programmatically, use that
|
|
// else, use the SLINT_SCALE_FACTOR if set, otherwise use the one from winit
|
|
let scale_factor_override = if scale_factor_override > 1. {
|
|
Some(scale_factor_override as f64)
|
|
} else {
|
|
std::env::var("SLINT_SCALE_FACTOR")
|
|
.ok()
|
|
.and_then(|x| x.parse::<f64>().ok())
|
|
.filter(|f| *f > 0.)
|
|
};
|
|
|
|
let into_size = |s: winit::dpi::LogicalSize<Coord>| -> winit::dpi::Size {
|
|
if let Some(f) = scale_factor_override {
|
|
s.to_physical::<f32>(f).into()
|
|
} else {
|
|
s.into()
|
|
}
|
|
};
|
|
|
|
let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
|
|
if let Some(window_item) = runtime_window.window_item() {
|
|
// Setting the width to its preferred size before querying the vertical layout info
|
|
// is important in case the height depends on the width
|
|
window_item.width.set(LogicalLength::new(layout_info_h.preferred_bounded()));
|
|
}
|
|
let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
|
|
#[allow(unused_mut)]
|
|
let mut s = winit::dpi::LogicalSize::new(
|
|
layout_info_h.preferred_bounded(),
|
|
layout_info_v.preferred_bounded(),
|
|
);
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
{
|
|
use wasm_bindgen::JsCast;
|
|
|
|
let canvas = web_sys::window()
|
|
.unwrap()
|
|
.document()
|
|
.unwrap()
|
|
.get_element_by_id(&self_.canvas_id)
|
|
.unwrap()
|
|
.dyn_into::<web_sys::HtmlCanvasElement>()
|
|
.unwrap();
|
|
|
|
let existing_canvas_size = winit::dpi::LogicalSize::new(
|
|
canvas.client_width() as f32,
|
|
canvas.client_height() as f32,
|
|
);
|
|
|
|
// Try to maintain the existing size of the canvas element. A window created with winit
|
|
// on the web will always have 1024x768 as size otherwise.
|
|
if s.width <= 0. {
|
|
s.width = existing_canvas_size.width;
|
|
}
|
|
if s.height <= 0. {
|
|
s.height = existing_canvas_size.height;
|
|
}
|
|
}
|
|
|
|
let window_builder = if std::env::var("SLINT_FULLSCREEN").is_ok() {
|
|
window_builder.with_fullscreen(Some(winit::window::Fullscreen::Borderless(None)))
|
|
} else {
|
|
if layout_info_h.min >= 1. as Coord || layout_info_v.min >= 1. as Coord {
|
|
window_builder = window_builder.with_min_inner_size(into_size(
|
|
winit::dpi::LogicalSize::new(layout_info_h.min, layout_info_v.min),
|
|
))
|
|
}
|
|
if layout_info_h.max < Coord::MAX || layout_info_v.max < Coord::MAX {
|
|
window_builder = window_builder.with_max_inner_size(into_size(
|
|
winit::dpi::LogicalSize::new(layout_info_h.max, layout_info_v.max),
|
|
))
|
|
}
|
|
|
|
if let Some(requested_size) = &requested_size {
|
|
// It would be nice to bound this with our constraints, but those are in logical coordinates
|
|
// and we don't know the scale factor yet...
|
|
if let Some(sf) = scale_factor_override {
|
|
let physical_size = requested_size.to_physical(sf as f32);
|
|
window_builder.with_inner_size(winit::dpi::Size::new(
|
|
winit::dpi::PhysicalSize::new(
|
|
physical_size.width,
|
|
physical_size.height,
|
|
),
|
|
))
|
|
} else {
|
|
window_builder.with_inner_size(window_size_to_slint(&requested_size))
|
|
}
|
|
} else if s.width > 0 as Coord && s.height > 0 as Coord {
|
|
// Make sure that the window's inner size is in sync with the root window item's
|
|
// width/height.
|
|
runtime_window.set_window_item_geometry(LogicalSize::new(s.width, s.height));
|
|
window_builder.with_inner_size(into_size(s))
|
|
} else {
|
|
window_builder
|
|
}
|
|
};
|
|
|
|
let window_builder = if let Some(pos) = &requested_position {
|
|
window_builder.with_position(position_to_winit(pos))
|
|
} else {
|
|
window_builder
|
|
};
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
let window_builder = {
|
|
use wasm_bindgen::JsCast;
|
|
|
|
let canvas = web_sys::window()
|
|
.unwrap()
|
|
.document()
|
|
.unwrap()
|
|
.get_element_by_id(&self_.canvas_id)
|
|
.unwrap()
|
|
.dyn_into::<web_sys::HtmlCanvasElement>()
|
|
.unwrap();
|
|
|
|
use winit::platform::web::WindowBuilderExtWebSys;
|
|
window_builder.with_canvas(Some(canvas.clone()))
|
|
};
|
|
|
|
let winit_window = Rc::new(crate::event_loop::with_window_target(|event_loop| {
|
|
window_builder.build(event_loop.event_loop_target()).unwrap()
|
|
}));
|
|
|
|
self_.renderer.show(
|
|
&winit_window,
|
|
#[cfg(target_arch = "wasm32")]
|
|
&self_.canvas_id,
|
|
);
|
|
|
|
WindowInner::from_pub(&self_.window).set_scale_factor(
|
|
scale_factor_override.unwrap_or_else(|| winit_window.scale_factor()) as _,
|
|
);
|
|
// On wasm, with_inner_size on the WindowBuilder don't have effect, so apply manually
|
|
#[cfg(target_arch = "wasm32")]
|
|
if s.width > 0 as Coord && s.height > 0 as Coord {
|
|
winit_window.set_inner_size(s);
|
|
}
|
|
let id = winit_window.id();
|
|
|
|
self_.map_state.replace(GraphicsWindowBackendState::Mapped(MappedWindow {
|
|
constraints: Default::default(),
|
|
winit_window,
|
|
}));
|
|
|
|
crate::event_loop::register_window(id, self_.self_weak.upgrade().unwrap());
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
fn hide(&self) {
|
|
self.call_with_event_loop(|self_| {
|
|
self_.unmap();
|
|
|
|
/* FIXME:
|
|
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
|
|
existing_blinker.stop();
|
|
}*/
|
|
crate::event_loop::with_window_target(|event_loop| {
|
|
event_loop
|
|
.event_loop_proxy()
|
|
.send_event(crate::event_loop::CustomEvent::WindowHidden)
|
|
})
|
|
.unwrap();
|
|
});
|
|
}
|
|
|
|
fn set_mouse_cursor(&self, cursor: MouseCursor) {
|
|
let winit_cursor = match cursor {
|
|
MouseCursor::Default => winit::window::CursorIcon::Default,
|
|
MouseCursor::None => winit::window::CursorIcon::Default,
|
|
MouseCursor::Help => winit::window::CursorIcon::Help,
|
|
MouseCursor::Pointer => winit::window::CursorIcon::Hand,
|
|
MouseCursor::Progress => winit::window::CursorIcon::Progress,
|
|
MouseCursor::Wait => winit::window::CursorIcon::Wait,
|
|
MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair,
|
|
MouseCursor::Text => winit::window::CursorIcon::Text,
|
|
MouseCursor::Alias => winit::window::CursorIcon::Alias,
|
|
MouseCursor::Copy => winit::window::CursorIcon::Copy,
|
|
MouseCursor::Move => winit::window::CursorIcon::Move,
|
|
MouseCursor::NoDrop => winit::window::CursorIcon::NoDrop,
|
|
MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed,
|
|
MouseCursor::Grab => winit::window::CursorIcon::Grab,
|
|
MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
|
|
MouseCursor::ColResize => winit::window::CursorIcon::ColResize,
|
|
MouseCursor::RowResize => winit::window::CursorIcon::RowResize,
|
|
MouseCursor::NResize => winit::window::CursorIcon::NResize,
|
|
MouseCursor::EResize => winit::window::CursorIcon::EResize,
|
|
MouseCursor::SResize => winit::window::CursorIcon::SResize,
|
|
MouseCursor::WResize => winit::window::CursorIcon::WResize,
|
|
MouseCursor::NeResize => winit::window::CursorIcon::NeResize,
|
|
MouseCursor::NwResize => winit::window::CursorIcon::NwResize,
|
|
MouseCursor::SeResize => winit::window::CursorIcon::SeResize,
|
|
MouseCursor::SwResize => winit::window::CursorIcon::SwResize,
|
|
MouseCursor::EwResize => winit::window::CursorIcon::EwResize,
|
|
MouseCursor::NsResize => winit::window::CursorIcon::NsResize,
|
|
MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize,
|
|
MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize,
|
|
};
|
|
self.with_window_handle(&mut |winit_window| {
|
|
winit_window.set_cursor_visible(cursor != MouseCursor::None);
|
|
winit_window.set_cursor_icon(winit_cursor);
|
|
});
|
|
}
|
|
|
|
fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
|
|
self.renderer.as_core_renderer()
|
|
}
|
|
|
|
fn enable_input_method(&self, _it: corelib::items::InputType) {
|
|
#[cfg(target_arch = "wasm32")]
|
|
{
|
|
let mut vkh = self.virtual_keyboard_helper.borrow_mut();
|
|
let h = vkh.get_or_insert_with(|| {
|
|
let canvas = self.renderer.html_canvas_element();
|
|
super::wasm_input_helper::WasmInputHelper::new(self.self_weak.clone(), canvas)
|
|
});
|
|
h.show();
|
|
}
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
self.with_window_handle(&mut |winit_window| {
|
|
winit_window.set_ime_allowed(matches!(_it, corelib::items::InputType::Text))
|
|
});
|
|
}
|
|
|
|
fn disable_input_method(&self) {
|
|
#[cfg(target_arch = "wasm32")]
|
|
if let Some(h) = &*self.virtual_keyboard_helper.borrow() {
|
|
h.hide()
|
|
}
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
self.with_window_handle(&mut |winit_window| winit_window.set_ime_allowed(false));
|
|
}
|
|
|
|
fn set_ime_position(&self, ime_pos: LogicalPoint) {
|
|
self.with_window_handle(&mut |winit_window| {
|
|
winit_window.set_ime_position(winit::dpi::LogicalPosition::new(ime_pos.x, ime_pos.y))
|
|
})
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn position(&self) -> corelib::api::PhysicalPosition {
|
|
match &*self.map_state.borrow() {
|
|
GraphicsWindowBackendState::Unmapped { requested_position, .. } => requested_position
|
|
.as_ref()
|
|
.map(|p| p.to_physical(self.window.scale_factor()))
|
|
.unwrap_or_default(),
|
|
GraphicsWindowBackendState::Mapped(mapped_window) => {
|
|
match mapped_window.winit_window.outer_position() {
|
|
Ok(outer_position) => {
|
|
corelib::api::PhysicalPosition::new(outer_position.x, outer_position.y)
|
|
}
|
|
Err(_) => Default::default(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_position(&self, position: corelib::api::WindowPosition) {
|
|
match &mut *self.map_state.borrow_mut() {
|
|
GraphicsWindowBackendState::Unmapped { requested_position, .. } => {
|
|
*requested_position = Some(position)
|
|
}
|
|
GraphicsWindowBackendState::Mapped(mapped_window) => {
|
|
mapped_window.winit_window.set_outer_position(position_to_winit(&position))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_size(&self, size: corelib::api::WindowSize) {
|
|
if self.in_resize_event.get() {
|
|
return;
|
|
}
|
|
if let Ok(mut map_state) = self.map_state.try_borrow_mut() {
|
|
match &mut *map_state {
|
|
GraphicsWindowBackendState::Unmapped { requested_size, .. } => {
|
|
*requested_size = Some(size)
|
|
}
|
|
GraphicsWindowBackendState::Mapped(mapped_window) => {
|
|
mapped_window.winit_window.set_inner_size(window_size_to_slint(&size));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn dark_color_scheme(&self) -> bool {
|
|
self.dark_color_scheme
|
|
.get_or_init(|| {
|
|
Box::pin(Property::new({
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(use_winit_theme)] {
|
|
self.borrow_mapped_window().and_then(|mapped_window| {
|
|
mapped_window
|
|
.winit_window
|
|
.theme()
|
|
.map(|theme| theme == winit::window::Theme::Dark)
|
|
})
|
|
.unwrap_or_default()
|
|
} else {
|
|
dark_light::detect() == dark_light::Mode::Dark
|
|
}
|
|
}
|
|
}))
|
|
})
|
|
.as_ref()
|
|
.get()
|
|
}
|
|
|
|
fn is_visible(&self) -> bool {
|
|
if let Some(mapped_window) = self.borrow_mapped_window() {
|
|
mapped_window.winit_window.is_visible().unwrap_or(true)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Renderer: WinitCompatibleRenderer + 'static> Drop for GLWindow<Renderer> {
|
|
fn drop(&mut self) {
|
|
self.unmap();
|
|
}
|
|
}
|
|
|
|
struct MappedWindow {
|
|
constraints: Cell<(corelib::layout::LayoutInfo, corelib::layout::LayoutInfo)>,
|
|
winit_window: Rc<winit::window::Window>,
|
|
}
|
|
|
|
enum GraphicsWindowBackendState {
|
|
Unmapped {
|
|
requested_position: Option<corelib::api::WindowPosition>,
|
|
requested_size: Option<corelib::api::WindowSize>,
|
|
},
|
|
Mapped(MappedWindow),
|
|
}
|
|
|
|
#[derive(FieldOffsets)]
|
|
#[repr(C)]
|
|
#[pin]
|
|
struct WindowProperties {
|
|
scale_factor: Property<f32>,
|
|
}
|
|
|
|
impl Default for WindowProperties {
|
|
fn default() -> Self {
|
|
Self { scale_factor: Property::new(1.0) }
|
|
}
|
|
}
|