#![warn(missing_docs)]
use crate::api::{
CloseRequestResponse, LogicalPosition, PhysicalPosition, PhysicalSize, PlatformError, Window,
WindowPosition, WindowSize,
};
use crate::input::{
key_codes, ClickState, InternalKeyboardModifierState, KeyEvent, KeyEventType, MouseEvent,
MouseInputState, TextCursorBlinker,
};
use crate::item_tree::{ItemRc, ItemTreeRc, ItemTreeRef, ItemTreeVTable, ItemTreeWeak, ItemWeak};
use crate::items::{ColorScheme, InputType, ItemRef, MouseCursor, PopupClosePolicy};
use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, SizeLengths};
use crate::properties::{Property, PropertyTracker};
use crate::renderer::Renderer;
use crate::{Callback, Coord, SharedString};
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
use alloc::rc::{Rc, Weak};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::cell::{Cell, RefCell};
use core::num::NonZeroU32;
use core::pin::Pin;
use euclid::num::Zero;
use vtable::VRcMapped;
pub mod popup;
fn next_focus_item(item: ItemRc) -> ItemRc {
item.next_focus_item()
}
fn previous_focus_item(item: ItemRc) -> ItemRc {
item.previous_focus_item()
}
pub trait WindowAdapter {
fn window(&self) -> &Window;
fn set_visible(&self, _visible: bool) -> Result<(), PlatformError> {
Ok(())
}
fn position(&self) -> Option<PhysicalPosition> {
None
}
fn set_position(&self, _position: WindowPosition) {}
fn set_size(&self, _size: WindowSize) {}
fn size(&self) -> PhysicalSize;
fn request_redraw(&self) {}
fn renderer(&self) -> &dyn Renderer;
fn update_window_properties(&self, _properties: WindowProperties<'_>) {}
#[doc(hidden)]
fn internal(&self, _: crate::InternalToken) -> Option<&dyn WindowAdapterInternal> {
None
}
#[cfg(feature = "raw-window-handle-06")]
fn window_handle_06(
&self,
) -> Result<raw_window_handle_06::WindowHandle<'_>, raw_window_handle_06::HandleError> {
Err(raw_window_handle_06::HandleError::NotSupported)
}
#[cfg(feature = "raw-window-handle-06")]
fn display_handle_06(
&self,
) -> Result<raw_window_handle_06::DisplayHandle<'_>, raw_window_handle_06::HandleError> {
Err(raw_window_handle_06::HandleError::NotSupported)
}
}
#[doc(hidden)]
pub trait WindowAdapterInternal {
fn register_item_tree(&self) {}
fn unregister_item_tree(
&self,
_component: ItemTreeRef,
_items: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>,
) {
}
fn create_popup(&self, _geometry: LogicalRect) -> Option<Rc<dyn WindowAdapter>> {
None
}
fn set_mouse_cursor(&self, _cursor: MouseCursor) {}
fn input_method_request(&self, _: InputMethodRequest) {}
fn as_any(&self) -> &dyn core::any::Any {
&()
}
fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) {}
fn color_scheme(&self) -> ColorScheme {
ColorScheme::Unknown
}
#[cfg(feature = "raw-window-handle-06")]
fn window_handle_06_rc(
&self,
) -> Result<Rc<dyn raw_window_handle_06::HasWindowHandle>, raw_window_handle_06::HandleError>
{
Err(raw_window_handle_06::HandleError::NotSupported)
}
#[cfg(feature = "raw-window-handle-06")]
fn display_handle_06_rc(
&self,
) -> Result<Rc<dyn raw_window_handle_06::HasDisplayHandle>, raw_window_handle_06::HandleError>
{
Err(raw_window_handle_06::HandleError::NotSupported)
}
fn bring_to_front(&self) -> Result<(), PlatformError> {
Ok(())
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum InputMethodRequest {
Enable(InputMethodProperties),
Update(InputMethodProperties),
Disable,
}
#[non_exhaustive]
#[derive(Clone, Default, Debug)]
pub struct InputMethodProperties {
pub text: SharedString,
pub cursor_position: usize,
pub anchor_position: Option<usize>,
pub preedit_text: SharedString,
pub preedit_offset: usize,
pub cursor_rect_origin: LogicalPosition,
pub cursor_rect_size: crate::api::LogicalSize,
pub anchor_point: LogicalPosition,
pub input_type: InputType,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub struct LayoutConstraints {
pub min: Option<crate::api::LogicalSize>,
pub max: Option<crate::api::LogicalSize>,
pub preferred: crate::api::LogicalSize,
}
pub struct WindowProperties<'a>(&'a WindowInner);
impl<'a> WindowProperties<'a> {
pub fn title(&self) -> SharedString {
self.0.window_item().map(|w| w.as_pin_ref().title()).unwrap_or_default()
}
pub fn background(&self) -> crate::Brush {
self.0
.window_item()
.map(|w: VRcMapped<ItemTreeVTable, crate::items::WindowItem>| {
w.as_pin_ref().background()
})
.unwrap_or_default()
}
pub fn layout_constraints(&self) -> LayoutConstraints {
let component = self.0.component();
let component = ItemTreeRc::borrow_pin(&component);
let h = component.as_ref().layout_info(crate::layout::Orientation::Horizontal);
let v = component.as_ref().layout_info(crate::layout::Orientation::Vertical);
let (min, max) = crate::layout::min_max_size_for_layout_constraints(h, v);
LayoutConstraints {
min,
max,
preferred: crate::api::LogicalSize::new(
h.preferred_bounded() as f32,
v.preferred_bounded() as f32,
),
}
}
#[deprecated(note = "Please use `is_fullscreen` instead")]
pub fn fullscreen(&self) -> bool {
self.is_fullscreen()
}
pub fn is_fullscreen(&self) -> bool {
self.0.is_fullscreen()
}
pub fn is_maximized(&self) -> bool {
self.0.maximized.get()
}
pub fn is_minimized(&self) -> bool {
self.0.minimized.get()
}
}
struct WindowPropertiesTracker {
window_adapter_weak: Weak<dyn WindowAdapter>,
}
impl crate::properties::PropertyDirtyHandler for WindowPropertiesTracker {
fn notify(self: Pin<&Self>) {
let win = self.window_adapter_weak.clone();
crate::timers::Timer::single_shot(Default::default(), move || {
if let Some(window_adapter) = win.upgrade() {
WindowInner::from_pub(window_adapter.window()).update_window_properties();
};
})
}
}
struct WindowRedrawTracker {
window_adapter_weak: Weak<dyn WindowAdapter>,
}
impl crate::properties::PropertyDirtyHandler for WindowRedrawTracker {
fn notify(self: Pin<&Self>) {
if let Some(window_adapter) = self.window_adapter_weak.upgrade() {
window_adapter.request_redraw();
};
}
}
enum PopupWindowLocation {
TopLevel(Rc<dyn WindowAdapter>),
ChildWindow(LogicalPoint),
}
struct PopupWindow {
popup_id: NonZeroU32,
location: PopupWindowLocation,
component: ItemTreeRc,
close_policy: PopupClosePolicy,
focus_item_in_parent: ItemWeak,
}
#[pin_project::pin_project]
struct WindowPinnedFields {
#[pin]
redraw_tracker: PropertyTracker<WindowRedrawTracker>,
#[pin]
window_properties_tracker: PropertyTracker<WindowPropertiesTracker>,
#[pin]
scale_factor: Property<f32>,
#[pin]
active: Property<bool>,
#[pin]
text_input_focused: Property<bool>,
}
pub struct WindowInner {
window_adapter_weak: Weak<dyn WindowAdapter>,
component: RefCell<ItemTreeWeak>,
strong_component_ref: RefCell<Option<ItemTreeRc>>,
mouse_input_state: Cell<MouseInputState>,
pub(crate) modifiers: Cell<InternalKeyboardModifierState>,
pub focus_item: RefCell<crate::item_tree::ItemWeak>,
pub(crate) last_ime_text: RefCell<SharedString>,
pub(crate) prevent_focus_change: Cell<bool>,
cursor_blinker: RefCell<pin_weak::rc::PinWeak<crate::input::TextCursorBlinker>>,
pinned_fields: Pin<Box<WindowPinnedFields>>,
maximized: Cell<bool>,
minimized: Cell<bool>,
active_popups: RefCell<Vec<PopupWindow>>,
next_popup_id: Cell<NonZeroU32>,
had_popup_on_press: Cell<bool>,
close_requested: Callback<(), CloseRequestResponse>,
click_state: ClickState,
pub(crate) ctx: once_cell::unsync::Lazy<crate::SlintContext>,
}
impl Drop for WindowInner {
fn drop(&mut self) {
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
existing_blinker.stop();
}
}
}
impl WindowInner {
pub fn new(window_adapter_weak: Weak<dyn WindowAdapter>) -> Self {
#![allow(unused_mut)]
let mut window_properties_tracker =
PropertyTracker::new_with_dirty_handler(WindowPropertiesTracker {
window_adapter_weak: window_adapter_weak.clone(),
});
let mut redraw_tracker = PropertyTracker::new_with_dirty_handler(WindowRedrawTracker {
window_adapter_weak: window_adapter_weak.clone(),
});
#[cfg(slint_debug_property)]
{
window_properties_tracker
.set_debug_name("i_slint_core::Window::window_properties_tracker".into());
redraw_tracker.set_debug_name("i_slint_core::Window::redraw_tracker".into());
}
Self {
window_adapter_weak,
component: Default::default(),
strong_component_ref: Default::default(),
mouse_input_state: Default::default(),
modifiers: Default::default(),
pinned_fields: Box::pin(WindowPinnedFields {
redraw_tracker,
window_properties_tracker,
scale_factor: Property::new_named(1., "i_slint_core::Window::scale_factor"),
active: Property::new_named(false, "i_slint_core::Window::active"),
text_input_focused: Property::new_named(
false,
"i_slint_core::Window::text_input_focused",
),
}),
maximized: Cell::new(false),
minimized: Cell::new(false),
focus_item: Default::default(),
last_ime_text: Default::default(),
cursor_blinker: Default::default(),
active_popups: Default::default(),
next_popup_id: Cell::new(NonZeroU32::MIN),
had_popup_on_press: Default::default(),
close_requested: Default::default(),
click_state: ClickState::default(),
prevent_focus_change: Default::default(),
ctx: once_cell::unsync::Lazy::new(|| {
crate::context::GLOBAL_CONTEXT.with(|ctx| ctx.get().unwrap().clone())
}),
}
}
pub fn set_component(&self, component: &ItemTreeRc) {
self.close_all_popups();
self.focus_item.replace(Default::default());
self.mouse_input_state.replace(Default::default());
self.modifiers.replace(Default::default());
self.component.replace(ItemTreeRc::downgrade(component));
self.pinned_fields.window_properties_tracker.set_dirty(); let window_adapter = self.window_adapter();
window_adapter.renderer().set_window_adapter(&window_adapter);
{
let component = ItemTreeRc::borrow_pin(component);
let root_item = component.as_ref().get_item_ref(0);
let window_item = ItemRef::downcast_pin::<crate::items::WindowItem>(root_item).unwrap();
let default_font_size_prop =
crate::items::WindowItem::FIELD_OFFSETS.default_font_size.apply_pin(window_item);
if default_font_size_prop.get().get() <= 0 as Coord {
default_font_size_prop.set(window_adapter.renderer().default_font_size());
}
}
self.set_window_item_geometry(
window_adapter.size().to_logical(self.scale_factor()).to_euclid(),
);
window_adapter.request_redraw();
let weak = Rc::downgrade(&window_adapter);
crate::timers::Timer::single_shot(Default::default(), move || {
if let Some(window_adapter) = weak.upgrade() {
WindowInner::from_pub(window_adapter.window()).update_window_properties();
}
})
}
pub fn component(&self) -> ItemTreeRc {
self.component.borrow().upgrade().unwrap()
}
pub fn try_component(&self) -> Option<ItemTreeRc> {
self.component.borrow().upgrade()
}
pub fn process_mouse_input(&self, mut event: MouseEvent) {
crate::animations::update_animations();
event = self.click_state.check_repeat(event, self.ctx.platform().click_interval());
let pressed_event = matches!(event, MouseEvent::Pressed { .. });
let released_event = matches!(event, MouseEvent::Released { .. });
let window_adapter = self.window_adapter();
let mut mouse_input_state = self.mouse_input_state.take();
let last_top_item = mouse_input_state.top_item_including_delayed();
if released_event {
mouse_input_state =
crate::input::process_delayed_event(&window_adapter, mouse_input_state);
}
if pressed_event {
self.had_popup_on_press.set(!self.active_popups.borrow().is_empty());
}
let close_policy = self.top_close_policy();
let mut mouse_inside_popup = false;
mouse_input_state = if let Some(mut event) =
crate::input::handle_mouse_grab(event, &window_adapter, &mut mouse_input_state)
{
let (item_tree, offset) = if let Some(PopupWindow {
location: PopupWindowLocation::ChildWindow(coordinates),
component,
..
}) = self.active_popups.borrow().last()
{
let geom = ItemTreeRc::borrow_pin(component).as_ref().item_geometry(0);
mouse_inside_popup = event
.position()
.map_or(true, |pos| geom.contains(pos - coordinates.to_vector()));
if mouse_inside_popup {
(Some(component.clone()), *coordinates)
} else {
(None, LogicalPoint::default())
}
} else {
(self.component.borrow().upgrade(), LogicalPoint::default())
};
if let Some(item_tree) = item_tree {
event.translate(-offset.to_vector());
let mut new_input_state = crate::input::process_mouse_input(
item_tree,
event,
&window_adapter,
mouse_input_state,
);
new_input_state.offset = offset;
new_input_state
} else {
let mut new_input_state = MouseInputState::default();
crate::input::send_exit_events(
&mouse_input_state,
&mut new_input_state,
event.position(),
&window_adapter,
);
new_input_state
}
} else {
mouse_input_state
};
if last_top_item != mouse_input_state.top_item_including_delayed() {
self.click_state.reset();
self.click_state.check_repeat(event, self.ctx.platform().click_interval());
}
self.mouse_input_state.set(mouse_input_state);
match close_policy {
PopupClosePolicy::CloseOnClick => {
if (mouse_inside_popup && released_event && self.had_popup_on_press.get())
|| (!mouse_inside_popup && pressed_event)
{
self.close_top_popup();
}
}
PopupClosePolicy::CloseOnClickOutside => {
if !mouse_inside_popup && pressed_event {
self.close_top_popup();
}
}
PopupClosePolicy::NoAutoClose => {}
};
crate::properties::ChangeTracker::run_change_handlers();
}
pub(crate) fn process_delayed_event(&self) {
self.mouse_input_state.set(crate::input::process_delayed_event(
&self.window_adapter(),
self.mouse_input_state.take(),
));
}
pub fn process_key_input(&self, mut event: KeyEvent) {
if let Some(updated_modifier) = self
.modifiers
.get()
.state_update(event.event_type == KeyEventType::KeyPressed, &event.text)
{
self.modifiers.set(updated_modifier);
}
event.modifiers = self.modifiers.get().into();
let mut item = self.focus_item.borrow().clone().upgrade();
if item.as_ref().is_some_and(|i| !i.is_visible()) {
self.take_focus_item();
item = None;
}
while let Some(focus_item) = item {
if focus_item.borrow().as_ref().key_event(&event, &self.window_adapter(), &focus_item)
== crate::input::KeyEventResult::EventAccepted
{
crate::properties::ChangeTracker::run_change_handlers();
return;
}
item = focus_item.parent_item();
}
let extra_mod = event.modifiers.control || event.modifiers.meta || event.modifiers.alt;
if event.text.starts_with(key_codes::Tab)
&& !event.modifiers.shift
&& !extra_mod
&& event.event_type == KeyEventType::KeyPressed
{
self.focus_next_item();
} else if (event.text.starts_with(key_codes::Backtab)
|| (event.text.starts_with(key_codes::Tab) && event.modifiers.shift))
&& event.event_type == KeyEventType::KeyPressed
&& !extra_mod
{
self.focus_previous_item();
} else if event.event_type == KeyEventType::KeyPressed
&& event.text.starts_with(key_codes::Escape)
{
let close_on_escape = if let Some(popup) = self.active_popups.borrow().last() {
popup.close_policy == PopupClosePolicy::CloseOnClick
|| popup.close_policy == PopupClosePolicy::CloseOnClickOutside
} else {
false
};
if close_on_escape {
self.close_top_popup();
}
}
crate::properties::ChangeTracker::run_change_handlers();
}
pub fn set_cursor_blink_binding(&self, prop: &crate::Property<bool>) {
let existing_blinker = self.cursor_blinker.borrow().clone();
let blinker = existing_blinker.upgrade().unwrap_or_else(|| {
let new_blinker = TextCursorBlinker::new();
*self.cursor_blinker.borrow_mut() =
pin_weak::rc::PinWeak::downgrade(new_blinker.clone());
new_blinker
});
TextCursorBlinker::set_binding(blinker, prop);
}
pub fn set_focus_item(&self, new_focus_item: &ItemRc, set_focus: bool) {
if self.prevent_focus_change.get() {
return;
}
let current_focus_item = self.focus_item.borrow().clone();
if let Some(current_focus_item_rc) = current_focus_item.upgrade() {
if set_focus {
if current_focus_item_rc == *new_focus_item {
return;
}
} else if current_focus_item_rc != *new_focus_item {
return;
}
}
let old = self.take_focus_item();
let new =
if set_focus { self.move_focus(new_focus_item.clone(), next_focus_item) } else { None };
let window_adapter = self.window_adapter();
if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
window_adapter.handle_focus_change(old, new);
}
}
fn take_focus_item(&self) -> Option<ItemRc> {
let focus_item = self.focus_item.take();
if let Some(focus_item_rc) = focus_item.upgrade() {
focus_item_rc.borrow().as_ref().focus_event(
&crate::input::FocusEvent::FocusOut,
&self.window_adapter(),
&focus_item_rc,
);
Some(focus_item_rc)
} else {
None
}
}
fn publish_focus_item(&self, item: &Option<ItemRc>) -> crate::input::FocusEventResult {
match item {
Some(item) => {
*self.focus_item.borrow_mut() = item.downgrade();
item.borrow().as_ref().focus_event(
&crate::input::FocusEvent::FocusIn,
&self.window_adapter(),
item,
)
}
None => {
*self.focus_item.borrow_mut() = Default::default();
crate::input::FocusEventResult::FocusAccepted }
}
}
fn move_focus(&self, start_item: ItemRc, forward: impl Fn(ItemRc) -> ItemRc) -> Option<ItemRc> {
let mut current_item = start_item;
let mut visited = Vec::new();
loop {
if current_item.is_visible()
&& self.publish_focus_item(&Some(current_item.clone()))
== crate::input::FocusEventResult::FocusAccepted
{
return Some(current_item); }
visited.push(current_item.clone());
current_item = forward(current_item);
if visited.iter().any(|i| *i == current_item) {
return None; }
}
}
pub fn focus_next_item(&self) {
let start_item = self.take_focus_item().map(next_focus_item).unwrap_or_else(|| {
ItemRc::new(
self.active_popups
.borrow()
.last()
.map_or_else(|| self.component(), |p| p.component.clone()),
0,
)
});
let end_item = self.move_focus(start_item.clone(), next_focus_item);
let window_adapter = self.window_adapter();
if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
window_adapter.handle_focus_change(Some(start_item), end_item);
}
}
pub fn focus_previous_item(&self) {
let start_item = previous_focus_item(self.take_focus_item().unwrap_or_else(|| {
ItemRc::new(
self.active_popups
.borrow()
.last()
.map_or_else(|| self.component(), |p| p.component.clone()),
0,
)
}));
let end_item = self.move_focus(start_item.clone(), previous_focus_item);
let window_adapter = self.window_adapter();
if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
window_adapter.handle_focus_change(Some(start_item), end_item);
}
}
pub fn set_active(&self, have_focus: bool) {
self.pinned_fields.as_ref().project_ref().active.set(have_focus);
let event = if have_focus {
crate::input::FocusEvent::WindowReceivedFocus
} else {
crate::input::FocusEvent::WindowLostFocus
};
if let Some(focus_item) = self.focus_item.borrow().upgrade() {
focus_item.borrow().as_ref().focus_event(&event, &self.window_adapter(), &focus_item);
}
if !have_focus {
self.modifiers.take();
}
}
pub fn active(&self) -> bool {
self.pinned_fields.as_ref().project_ref().active.get()
}
pub fn update_window_properties(&self) {
let window_adapter = self.window_adapter();
self.pinned_fields
.as_ref()
.project_ref()
.window_properties_tracker
.evaluate_as_dependency_root(|| {
window_adapter.update_window_properties(WindowProperties(self));
});
}
pub fn draw_contents<T>(
&self,
render_components: impl FnOnce(&[(&ItemTreeRc, LogicalPoint)]) -> T,
) -> Option<T> {
let component_rc = self.try_component()?;
Some(self.pinned_fields.as_ref().project_ref().redraw_tracker.evaluate_as_dependency_root(
|| {
if !self
.active_popups
.borrow()
.iter()
.any(|p| matches!(p.location, PopupWindowLocation::ChildWindow(..)))
{
render_components(&[(&component_rc, LogicalPoint::default())])
} else {
let borrow = self.active_popups.borrow();
let mut cmps = Vec::with_capacity(borrow.len() + 1);
cmps.push((&component_rc, LogicalPoint::default()));
for popup in borrow.iter() {
if let PopupWindowLocation::ChildWindow(location) = &popup.location {
cmps.push((&popup.component, *location));
}
}
render_components(&cmps)
}
},
))
}
pub fn show(&self) -> Result<(), PlatformError> {
if let Some(component) = self.try_component() {
let was_visible = self.strong_component_ref.replace(Some(component)).is_some();
if !was_visible {
*(self.ctx.0.window_count.borrow_mut()) += 1;
}
}
self.update_window_properties();
self.window_adapter().set_visible(true)?;
let size = self.window_adapter().size();
self.set_window_item_geometry(size.to_logical(self.scale_factor()).to_euclid());
self.window_adapter().renderer().resize(size).unwrap();
if let Some(hook) = self.ctx.0.window_shown_hook.borrow_mut().as_mut() {
hook(&self.window_adapter());
}
Ok(())
}
pub fn hide(&self) -> Result<(), PlatformError> {
let result = self.window_adapter().set_visible(false);
let was_visible = self.strong_component_ref.borrow_mut().take().is_some();
if was_visible {
let mut count = self.ctx.0.window_count.borrow_mut();
*count -= 1;
if *count <= 0 {
drop(count);
let _ = self.ctx.event_loop_proxy().and_then(|p| p.quit_event_loop().ok());
}
}
result
}
pub fn color_scheme(&self) -> ColorScheme {
self.window_adapter()
.internal(crate::InternalToken)
.map_or(ColorScheme::Unknown, |x| x.color_scheme())
}
pub fn show_popup(
&self,
popup_componentrc: &ItemTreeRc,
position: LogicalPosition,
close_policy: PopupClosePolicy,
parent_item: &ItemRc,
) -> NonZeroU32 {
let position = parent_item
.map_to_window(parent_item.geometry().origin + position.to_euclid().to_vector());
let root_of = |mut item_tree: ItemTreeRc| loop {
if ItemRc::new(item_tree.clone(), 0).downcast::<crate::items::WindowItem>().is_some() {
return item_tree;
}
let mut r = crate::item_tree::ItemWeak::default();
ItemTreeRc::borrow_pin(&item_tree).as_ref().parent_node(&mut r);
match r.upgrade() {
None => return item_tree,
Some(x) => item_tree = x.item_tree().clone(),
}
};
let parent_root_item_tree = root_of(parent_item.item_tree().clone());
let (parent_window_adapter, position) = if let Some(parent_popup) = self
.active_popups
.borrow()
.iter()
.find(|p| ItemTreeRc::ptr_eq(&p.component, &parent_root_item_tree))
{
match &parent_popup.location {
PopupWindowLocation::TopLevel(wa) => (wa.clone(), position),
PopupWindowLocation::ChildWindow(offset) => {
(self.window_adapter(), position + offset.to_vector())
}
}
} else {
(self.window_adapter(), position)
};
let popup_component = ItemTreeRc::borrow_pin(popup_componentrc);
let popup_root = popup_component.as_ref().get_item_ref(0);
let (mut w, mut h) = if let Some(window_item) =
ItemRef::downcast_pin::<crate::items::WindowItem>(popup_root)
{
(window_item.width(), window_item.height())
} else {
(LogicalLength::zero(), LogicalLength::zero())
};
let layout_info_h =
popup_component.as_ref().layout_info(crate::layout::Orientation::Horizontal);
let layout_info_v =
popup_component.as_ref().layout_info(crate::layout::Orientation::Vertical);
if w <= LogicalLength::zero() {
w = LogicalLength::new(layout_info_h.preferred);
}
if h <= LogicalLength::zero() {
h = LogicalLength::new(layout_info_v.preferred);
}
w = w.max(LogicalLength::new(layout_info_h.min)).min(LogicalLength::new(layout_info_h.max));
h = h.max(LogicalLength::new(layout_info_v.min)).min(LogicalLength::new(layout_info_v.max));
let size = crate::lengths::LogicalSize::from_lengths(w, h);
if let Some(window_item) = ItemRef::downcast_pin(popup_root) {
let width_property =
crate::items::WindowItem::FIELD_OFFSETS.width.apply_pin(window_item);
let height_property =
crate::items::WindowItem::FIELD_OFFSETS.height.apply_pin(window_item);
width_property.set(size.width_length());
height_property.set(size.height_length());
};
let popup_id = self.next_popup_id.get();
self.next_popup_id.set(self.next_popup_id.get().checked_add(1).unwrap());
let location = match parent_window_adapter
.internal(crate::InternalToken)
.and_then(|x| x.create_popup(LogicalRect::new(position, size)))
{
None => {
let clip = LogicalRect::new(
LogicalPoint::new(0.0 as crate::Coord, 0.0 as crate::Coord),
self.window_adapter().size().to_logical(self.scale_factor()).to_euclid(),
);
let rect = popup::place_popup(
popup::Placement::Fixed(LogicalRect::new(position, size)),
&Some(clip),
);
self.window_adapter().request_redraw();
PopupWindowLocation::ChildWindow(rect.origin)
}
Some(window_adapter) => {
WindowInner::from_pub(window_adapter.window()).set_component(popup_componentrc);
PopupWindowLocation::TopLevel(window_adapter)
}
};
let focus_item = self.take_focus_item().map(|item| item.downgrade()).unwrap_or_default();
self.active_popups.borrow_mut().push(PopupWindow {
popup_id,
location,
component: popup_componentrc.clone(),
close_policy,
focus_item_in_parent: focus_item,
});
popup_id
}
pub fn show_native_popup_menu(
&self,
_context_menu_item: &ItemRc,
_position: LogicalPosition,
) -> bool {
false
}
fn close_popup_impl(&self, current_popup: &PopupWindow) {
match ¤t_popup.location {
PopupWindowLocation::ChildWindow(offset) => {
let popup_region = crate::properties::evaluate_no_tracking(|| {
let popup_component = ItemTreeRc::borrow_pin(¤t_popup.component);
popup_component.as_ref().item_geometry(0)
})
.translate(offset.to_vector());
if !popup_region.is_empty() {
let window_adapter = self.window_adapter();
window_adapter.renderer().mark_dirty_region(popup_region.into());
window_adapter.request_redraw();
}
}
PopupWindowLocation::TopLevel(adapter) => {
let _ = adapter.set_visible(false);
}
}
if let Some(focus) = current_popup.focus_item_in_parent.upgrade() {
self.set_focus_item(&focus, true);
}
}
pub fn close_popup(&self, popup_id: NonZeroU32) {
let mut active_popups = self.active_popups.borrow_mut();
let maybe_index = active_popups.iter().position(|popup| popup.popup_id == popup_id);
if let Some(popup_index) = maybe_index {
self.close_popup_impl(&active_popups.remove(popup_index));
}
}
pub fn close_all_popups(&self) {
for popup in self.active_popups.take() {
self.close_popup_impl(&popup);
}
}
pub fn close_top_popup(&self) {
if let Some(popup) = self.active_popups.borrow_mut().pop() {
self.close_popup_impl(&popup);
}
}
pub fn top_close_policy(&self) -> PopupClosePolicy {
self.active_popups
.borrow()
.last()
.map_or(PopupClosePolicy::NoAutoClose, |popup| popup.close_policy)
}
pub fn scale_factor(&self) -> f32 {
self.pinned_fields.as_ref().project_ref().scale_factor.get()
}
pub(crate) fn set_scale_factor(&self, factor: f32) {
self.pinned_fields.scale_factor.set(factor)
}
pub fn text_input_focused(&self) -> bool {
self.pinned_fields.as_ref().project_ref().text_input_focused.get()
}
pub fn set_text_input_focused(&self, value: bool) {
self.pinned_fields.text_input_focused.set(value)
}
pub fn is_visible(&self) -> bool {
self.strong_component_ref.borrow().is_some()
}
pub fn window_item(&self) -> Option<VRcMapped<ItemTreeVTable, crate::items::WindowItem>> {
self.try_component().and_then(|component_rc| {
ItemRc::new(component_rc, 0).downcast::<crate::items::WindowItem>()
})
}
pub(crate) fn set_window_item_geometry(&self, size: crate::lengths::LogicalSize) {
if let Some(component_rc) = self.try_component() {
let component = ItemTreeRc::borrow_pin(&component_rc);
let root_item = component.as_ref().get_item_ref(0);
if let Some(window_item) = ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
{
window_item.width.set(size.width_length());
window_item.height.set(size.height_length());
}
}
}
pub fn on_close_requested(&self, mut callback: impl FnMut() -> CloseRequestResponse + 'static) {
self.close_requested.set_handler(move |()| callback());
}
pub fn request_close(&self) -> bool {
match self.close_requested.call(&()) {
CloseRequestResponse::HideWindow => true,
CloseRequestResponse::KeepWindowShown => false,
}
}
pub fn is_fullscreen(&self) -> bool {
if let Some(window_item) = self.window_item() {
window_item.as_pin_ref().full_screen()
} else {
false
}
}
pub fn set_fullscreen(&self, enabled: bool) {
if let Some(window_item) = self.window_item() {
window_item.as_pin_ref().full_screen.set(enabled);
self.update_window_properties()
}
}
pub fn is_maximized(&self) -> bool {
self.maximized.get()
}
pub fn set_maximized(&self, maximized: bool) {
self.maximized.set(maximized);
self.update_window_properties()
}
pub fn is_minimized(&self) -> bool {
self.minimized.get()
}
pub fn set_minimized(&self, minimized: bool) {
self.minimized.set(minimized);
self.update_window_properties()
}
pub fn xdg_app_id(&self) -> Option<SharedString> {
self.ctx.xdg_app_id()
}
pub fn window_adapter(&self) -> Rc<dyn WindowAdapter> {
self.window_adapter_weak.upgrade().unwrap()
}
pub fn from_pub(window: &crate::api::Window) -> &Self {
&window.0
}
pub fn context(&self) -> &crate::SlintContext {
&*self.ctx
}
}
pub type WindowAdapterRc = Rc<dyn WindowAdapter>;
#[cfg(feature = "ffi")]
pub mod ffi {
#![allow(unsafe_code)]
#![allow(clippy::missing_safety_doc)]
#![allow(missing_docs)]
use super::*;
use crate::api::{RenderingNotifier, RenderingState, SetRenderingNotifierError};
use crate::graphics::Size;
use crate::graphics::{IntSize, Rgba8Pixel};
use crate::SharedVector;
#[repr(u8)]
pub enum GraphicsAPI {
NativeOpenGL,
}
#[allow(non_camel_case_types)]
type c_void = ();
#[repr(C)]
pub struct WindowAdapterRcOpaque(*const c_void, *const c_void);
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_drop(handle: *mut WindowAdapterRcOpaque) {
assert_eq!(
core::mem::size_of::<Rc<dyn WindowAdapter>>(),
core::mem::size_of::<WindowAdapterRcOpaque>()
);
core::ptr::read(handle as *mut Rc<dyn WindowAdapter>);
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_clone(
source: *const WindowAdapterRcOpaque,
target: *mut WindowAdapterRcOpaque,
) {
assert_eq!(
core::mem::size_of::<Rc<dyn WindowAdapter>>(),
core::mem::size_of::<WindowAdapterRcOpaque>()
);
let window = &*(source as *const Rc<dyn WindowAdapter>);
core::ptr::write(target as *mut Rc<dyn WindowAdapter>, window.clone());
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_show(handle: *const WindowAdapterRcOpaque) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().show().unwrap();
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_hide(handle: *const WindowAdapterRcOpaque) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().hide().unwrap();
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_is_visible(
handle: *const WindowAdapterRcOpaque,
) -> bool {
let window = &*(handle as *const Rc<dyn WindowAdapter>);
window.window().is_visible()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_get_scale_factor(
handle: *const WindowAdapterRcOpaque,
) -> f32 {
assert_eq!(
core::mem::size_of::<Rc<dyn WindowAdapter>>(),
core::mem::size_of::<WindowAdapterRcOpaque>()
);
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).scale_factor()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_scale_factor(
handle: *const WindowAdapterRcOpaque,
value: f32,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).set_scale_factor(value)
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_get_text_input_focused(
handle: *const WindowAdapterRcOpaque,
) -> bool {
assert_eq!(
core::mem::size_of::<Rc<dyn WindowAdapter>>(),
core::mem::size_of::<WindowAdapterRcOpaque>()
);
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).text_input_focused()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_text_input_focused(
handle: *const WindowAdapterRcOpaque,
value: bool,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).set_text_input_focused(value)
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_focus_item(
handle: *const WindowAdapterRcOpaque,
focus_item: &ItemRc,
set_focus: bool,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).set_focus_item(focus_item, set_focus)
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_component(
handle: *const WindowAdapterRcOpaque,
component: &ItemTreeRc,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).set_component(component)
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_show_popup(
handle: *const WindowAdapterRcOpaque,
popup: &ItemTreeRc,
position: LogicalPosition,
close_policy: PopupClosePolicy,
parent_item: &ItemRc,
) -> NonZeroU32 {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
return WindowInner::from_pub(window_adapter.window()).show_popup(
popup,
position,
close_policy,
parent_item,
);
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_close_popup(
handle: *const WindowAdapterRcOpaque,
popup_id: NonZeroU32,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).close_popup(popup_id);
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_rendering_notifier(
handle: *const WindowAdapterRcOpaque,
callback: extern "C" fn(
rendering_state: RenderingState,
graphics_api: GraphicsAPI,
user_data: *mut c_void,
),
drop_user_data: extern "C" fn(user_data: *mut c_void),
user_data: *mut c_void,
error: *mut SetRenderingNotifierError,
) -> bool {
struct CNotifier {
callback: extern "C" fn(
rendering_state: RenderingState,
graphics_api: GraphicsAPI,
user_data: *mut c_void,
),
drop_user_data: extern "C" fn(*mut c_void),
user_data: *mut c_void,
}
impl Drop for CNotifier {
fn drop(&mut self) {
(self.drop_user_data)(self.user_data)
}
}
impl RenderingNotifier for CNotifier {
fn notify(&mut self, state: RenderingState, graphics_api: &crate::api::GraphicsAPI) {
let cpp_graphics_api = match graphics_api {
crate::api::GraphicsAPI::NativeOpenGL { .. } => GraphicsAPI::NativeOpenGL,
crate::api::GraphicsAPI::WebGL { .. } => unreachable!(), };
(self.callback)(state, cpp_graphics_api, self.user_data)
}
}
let window = &*(handle as *const Rc<dyn WindowAdapter>);
match window.renderer().set_rendering_notifier(Box::new(CNotifier {
callback,
drop_user_data,
user_data,
})) {
Ok(()) => true,
Err(err) => {
*error = err;
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_on_close_requested(
handle: *const WindowAdapterRcOpaque,
callback: extern "C" fn(user_data: *mut c_void) -> CloseRequestResponse,
drop_user_data: extern "C" fn(user_data: *mut c_void),
user_data: *mut c_void,
) {
struct WithUserData {
callback: extern "C" fn(user_data: *mut c_void) -> CloseRequestResponse,
drop_user_data: extern "C" fn(*mut c_void),
user_data: *mut c_void,
}
impl Drop for WithUserData {
fn drop(&mut self) {
(self.drop_user_data)(self.user_data)
}
}
impl WithUserData {
fn call(&self) -> CloseRequestResponse {
(self.callback)(self.user_data)
}
}
let with_user_data = WithUserData { callback, drop_user_data, user_data };
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().on_close_requested(move || with_user_data.call());
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_request_redraw(handle: *const WindowAdapterRcOpaque) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.request_redraw();
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_position(
handle: *const WindowAdapterRcOpaque,
pos: &mut euclid::default::Point2D<i32>,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
*pos = window_adapter.position().unwrap_or_default().to_euclid()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_physical_position(
handle: *const WindowAdapterRcOpaque,
pos: &euclid::default::Point2D<i32>,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.set_position(crate::api::PhysicalPosition::new(pos.x, pos.y).into());
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_logical_position(
handle: *const WindowAdapterRcOpaque,
pos: &euclid::default::Point2D<f32>,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.set_position(LogicalPosition::new(pos.x, pos.y).into());
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_size(handle: *const WindowAdapterRcOpaque) -> IntSize {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.size().to_euclid().cast()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_physical_size(
handle: *const WindowAdapterRcOpaque,
size: &IntSize,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().set_size(crate::api::PhysicalSize::new(size.width, size.height));
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_logical_size(
handle: *const WindowAdapterRcOpaque,
size: &Size,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().set_size(crate::api::LogicalSize::new(size.width, size.height));
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_color_scheme(
handle: *const WindowAdapterRcOpaque,
) -> ColorScheme {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter
.internal(crate::InternalToken)
.map_or(ColorScheme::Unknown, |x| x.color_scheme())
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_default_font_size(
handle: *const WindowAdapterRcOpaque,
) -> f32 {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().0.window_item().unwrap().as_pin_ref().default_font_size().get()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_dispatch_key_event(
handle: *const WindowAdapterRcOpaque,
event_type: crate::input::KeyEventType,
text: &SharedString,
repeat: bool,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().0.process_key_input(crate::items::KeyEvent {
text: text.clone(),
repeat,
event_type,
..Default::default()
});
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_dispatch_pointer_event(
handle: *const WindowAdapterRcOpaque,
event: crate::input::MouseEvent,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().0.process_mouse_input(event);
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_dispatch_event(
handle: *const WindowAdapterRcOpaque,
event: &crate::platform::WindowEvent,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().dispatch_event(event.clone());
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_is_fullscreen(
handle: *const WindowAdapterRcOpaque,
) -> bool {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().is_fullscreen()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_is_minimized(
handle: *const WindowAdapterRcOpaque,
) -> bool {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().is_minimized()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_is_maximized(
handle: *const WindowAdapterRcOpaque,
) -> bool {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().is_maximized()
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_fullscreen(
handle: *const WindowAdapterRcOpaque,
value: bool,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().set_fullscreen(value)
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_minimized(
handle: *const WindowAdapterRcOpaque,
value: bool,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().set_minimized(value)
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_maximized(
handle: *const WindowAdapterRcOpaque,
value: bool,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
window_adapter.window().set_maximized(value)
}
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_take_snapshot(
handle: *const WindowAdapterRcOpaque,
data: &mut SharedVector<Rgba8Pixel>,
width: &mut u32,
height: &mut u32,
) -> bool {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
if let Ok(snapshot) = window_adapter.window().take_snapshot() {
*data = snapshot.data.clone();
*width = snapshot.width();
*height = snapshot.height();
true
} else {
false
}
}
}
#[cfg(feature = "software-renderer")]
#[test]
fn test_empty_window() {
let msw = crate::software_renderer::MinimalSoftwareWindow::new(
crate::software_renderer::RepaintBufferType::NewBuffer,
);
msw.window().request_redraw();
let mut region = None;
let render_called = msw.draw_if_needed(|renderer| {
let mut buffer =
crate::graphics::SharedPixelBuffer::<crate::graphics::Rgb8Pixel>::new(100, 100);
let stride = buffer.width() as usize;
region = Some(renderer.render(buffer.make_mut_slice(), stride));
});
assert!(render_called);
let region = region.unwrap();
assert_eq!(region.bounding_box_size(), PhysicalSize::default());
assert_eq!(region.bounding_box_origin(), PhysicalPosition::default());
}