gallery/vendor/i-slint-core/items.rs
Greg Pomerantz 2f4768f354 Fix 120Hz scroll jank end-to-end: render-ahead present + full-cache eviction off the UI thread
Three root causes, all fixed and verified on the Pixel 9 Pro:

1. Present phase (backend + renderer): VSYNC_LOCKED_RENDER defers the
   render to the vsync wake; RENDER_AHEAD presents the previous frame at
   the start of render() (phase = wake + ~1.1 ms) and holds the new
   frame one slot. Crash fix: a held wgpu frame must be PRESENTED, never
   merely dropped, before the next acquire (AlreadyAcquired panic at
   launch otherwise); discard_pending_frame presents too.

2. Full-res prefetch churn (src/lib.rs): the 8-slot full cache cycled
   through a ~20-cell viewport forever (137 decodes/20 s) and each
   eviction free()'d a 50 MB Vec on the UI thread (20-52 ms CPU spikes,
   ~7/s = the residual SLOWEV NonMain jank). Fix: 60 s tombstone
   cooldown on evicted paths (idle prefetch skips, urgent tap bypasses)
   + off-thread reaper for the freed blobs (insert: 21 ms -> 4 us).

3. (earlier) Skia final flush + Mailbox present mode (vendor, M3).

Instrumentation (all behind --features perf): OTHR/INVK/INVK_TAG/FLCK
lines for cross-thread closure timing and push-site tagging.

Warm measured swipe, 8580-file grid: cadence median 8.33 ms (exact
120 Hz), p99 15.5 ms, 5/552 gaps >16.7 ms (0.9%, max 28.3 ms, zero
>33 ms); PRES phase median ~994 us, zero late frames. Docs updated
(docs/BUGS/scroll-jank-120hz.md, full scorecard + line reference).

Also: vendored i-slint-core + wgpu-hal (vkshim interposition patch),
vkshim profiling tooling, backend made a non-optional dep, debuggable
flag for simpleperf (REMOVE before shipping).
2026-10-30 02:49:23 -04:00

2275 lines
69 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
// cSpell: ignore nesw windowitem
/*!
This module contains the builtin items, either in this file or in sub-modules.
When adding an item or a property, it needs to be kept in sync with different place.
(This is less than ideal and maybe we can have some automation later)
- It needs to be changed in this module
- In the compiler: internal/compiler/builtin_elements.rs
- In the interpreter (new item only): item_registry.rs
- For the C++ code (new item only): the cbindgen.rs to export the new item
- Don't forget to update the documentation
*/
#![allow(unsafe_code)]
#![allow(non_upper_case_globals)]
#![allow(missing_docs)] // because documenting each property of items is redundant
use crate::api::LogicalPosition;
use crate::cursor::MouseCursorInner;
use crate::data_transfer::DataTransfer;
use crate::graphics::{Brush, Color, FontRequest, Image};
use crate::input::{
FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, InternalKeyEvent,
KeyEventResult, KeyEventType, Keys, MouseEvent,
};
use crate::item_rendering::{CachedRenderingData, RenderBorderRectangle, RenderRectangle};
use crate::item_tree::ItemTreeRc;
pub use crate::item_tree::{ItemRc, ItemTreeVTable};
use crate::layout::LayoutInfo;
use crate::lengths::{
LogicalBorderRadius, LogicalLength, LogicalRect, LogicalSize, LogicalVector, PointLengths,
RectLengths,
};
pub use crate::menus::MenuItem;
#[cfg(feature = "rtti")]
use crate::rtti::*;
use crate::window::{WindowAdapter, WindowAdapterRc, WindowInner};
use crate::{Callback, Coord, Property, SharedString};
use alloc::rc::Rc;
use const_field_offset::FieldOffsets;
use core::cell::Cell;
use core::num::NonZeroU32;
use core::pin::Pin;
use core::time::Duration;
use i_slint_core_macros::*;
pub use system_tray::SystemTrayIcon;
use vtable::*;
mod component_container;
pub use self::component_container::*;
pub mod flickable; // vendored: `pub` for the jank-hunt FLICK_* probes
pub use flickable::Flickable;
mod text;
pub use text::*;
mod input_items;
pub use input_items::*;
mod image;
pub use self::image::*;
mod drag_n_drop;
pub use drag_n_drop::*;
#[cfg(feature = "path")]
mod path;
#[cfg(feature = "path")]
pub use path::*;
pub mod system_tray;
/// Alias for `&mut dyn ItemRenderer`. Required so cbindgen generates the ItemVTable
/// despite the presence of trait object
type ItemRendererRef<'a> = &'a mut dyn crate::item_rendering::ItemRenderer;
/// Workarounds for cbindgen
pub type VoidArg = ();
pub type KeyEventArg = (KeyEvent,);
pub type DragActionArg = (DragAction,);
type FocusReasonArg = (FocusReason,);
type PointerEventArg = (PointerEvent,);
type PointerScrollEventArg = (PointerScrollEvent,);
type PointArg = (LogicalPosition,);
type MenuEntryArg = (MenuEntry,);
type StringArg = (SharedString,);
type MenuEntryModel = crate::model::ModelRc<MenuEntry>;
#[cfg(all(feature = "ffi", windows))]
#[macro_export]
macro_rules! declare_item_vtable {
(fn $getter:ident() -> $item_vtable_ty:ident for $item_ty:ty) => {
ItemVTable_static! {
#[unsafe(no_mangle)]
pub static $item_vtable_ty for $item_ty
}
#[unsafe(no_mangle)]
pub extern "C" fn $getter() -> *const ItemVTable {
use vtable::HasStaticVTable;
<$item_ty>::STATIC_VTABLE
}
};
}
#[cfg(not(all(feature = "ffi", windows)))]
#[macro_export]
macro_rules! declare_item_vtable {
(fn $getter:ident() -> $item_vtable_ty:ident for $item_ty:ty) => {
ItemVTable_static! {
#[unsafe(no_mangle)]
pub static $item_vtable_ty for $item_ty
}
};
}
/// Returned by the `render()` function on items to indicate whether the rendering of
/// children should be handled by the caller, of if the item took care of that (for example
/// through layer indirection)
#[repr(C)]
#[derive(Default)]
pub enum RenderingResult {
#[default]
ContinueRenderingChildren,
ContinueRenderingWithoutChildren,
}
/// Items are the nodes in the render tree.
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
#[vtable]
#[repr(C)]
pub struct ItemVTable {
/// This function is called by the run-time after the memory for the item
/// has been allocated and initialized. It will be called before any user specified
/// bindings are set.
pub init: extern "C" fn(core::pin::Pin<VRef<ItemVTable>>, my_item: &ItemRc),
pub deinit: extern "C" fn(core::pin::Pin<VRef<ItemVTable>>, window_adapter: &WindowAdapterRc),
/// offset in bytes from the *const ItemImpl.
/// usize::MAX means None
#[allow(non_upper_case_globals)]
#[field_offset(CachedRenderingData)]
pub cached_rendering_data_offset: usize,
/// We would need max/min/preferred size, and all layout info.
/// `cross_axis_constraint` is the available width when querying vertical
/// layout info, enabling height-for-width (Text word-wrap, Image aspect
/// ratio). Negative values mean unconstrained.
pub layout_info: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
orientation: Orientation,
cross_axis_constraint: Coord,
window_adapter: &WindowAdapterRc,
self_rc: &ItemRc,
) -> LayoutInfo,
/// Event handler for mouse and touch event. This function is called before being called on children.
/// Then, depending on the return value, it is called for the children, and their children, then
/// [`Self::input_event`] is called on the children, and finally [`Self::input_event`] is called
/// on this item again.
///
/// The `cursor` argument needs to be changed by either this function ot the `input_event` function
/// if this item wants to change the cursor.
/// The value of `cursor` is always reset to `MouseCursor::Default` before dispatching the event,
/// so any call to this function need to set the cursor
pub input_event_filter_before_children: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
&MouseEvent,
window_adapter: &WindowAdapterRc,
self_rc: &ItemRc,
cursor: &mut MouseCursorInner,
) -> InputEventFilterResult,
/// Handle input event for mouse and touch event
pub input_event: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
&MouseEvent,
window_adapter: &WindowAdapterRc,
self_rc: &ItemRc,
cursor: &mut MouseCursorInner,
) -> InputEventResult,
pub focus_event: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
&FocusEvent,
window_adapter: &WindowAdapterRc,
self_rc: &ItemRc,
) -> FocusEventResult,
/// Called on the parents of the focused item, allowing for global shortcuts and similar
/// overrides of the default actions.
pub capture_key_event: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
&InternalKeyEvent,
window_adapter: &WindowAdapterRc,
self_rc: &ItemRc,
) -> KeyEventResult,
pub key_event: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
&InternalKeyEvent,
window_adapter: &WindowAdapterRc,
self_rc: &ItemRc,
) -> KeyEventResult,
pub render: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult,
/// Returns the rendering bounding rect for that particular item in the parent's item coordinate
/// (same coordinate system as the geometry)
pub bounding_rect: extern "C" fn(
core::pin::Pin<VRef<ItemVTable>>,
window_adapter: &WindowAdapterRc,
self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect,
pub clips_children: extern "C" fn(core::pin::Pin<VRef<ItemVTable>>) -> bool,
}
/// Alias for `vtable::VRef<ItemVTable>` which represent a pointer to a `dyn Item` with
/// the associated vtable
pub type ItemRef<'a> = vtable::VRef<'a, ItemVTable>;
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of an empty items that does nothing
pub struct Empty {
pub cached_rendering_data: CachedRenderingData,
}
impl Item for Empty {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
_backend: &mut ItemRendererRef,
_self_rc: &ItemRc,
_size: LogicalSize,
) -> RenderingResult {
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
mut geometry: LogicalRect,
) -> LogicalRect {
geometry.size = LogicalSize::zero();
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl ItemConsts for Empty {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
Empty,
CachedRenderingData,
> = Empty::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_EmptyVTable() -> EmptyVTable for Empty
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of the `Rectangle` element
pub struct Rectangle {
pub background: Property<Brush>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for Rectangle {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
(*backend).draw_rectangle(self, self_rc, size, &self.cached_rendering_data);
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl RenderRectangle for Rectangle {
fn background(self: Pin<&Self>) -> Brush {
self.background()
}
}
impl ItemConsts for Rectangle {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
Rectangle,
CachedRenderingData,
> = Rectangle::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_RectangleVTable() -> RectangleVTable for Rectangle
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of the `BasicBorderRectangle` element
pub struct BasicBorderRectangle {
pub background: Property<Brush>,
pub border_width: Property<LogicalLength>,
pub border_radius: Property<LogicalLength>,
pub border_color: Property<Brush>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for BasicBorderRectangle {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
(*backend).draw_border_rectangle(self, self_rc, size, &self.cached_rendering_data);
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl RenderBorderRectangle for BasicBorderRectangle {
fn background(self: Pin<&Self>) -> Brush {
self.background()
}
fn border_width(self: Pin<&Self>) -> LogicalLength {
self.border_width()
}
fn border_radius(self: Pin<&Self>) -> LogicalBorderRadius {
LogicalBorderRadius::from_length(self.border_radius())
}
fn border_color(self: Pin<&Self>) -> Brush {
self.border_color()
}
}
impl ItemConsts for BasicBorderRectangle {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
BasicBorderRectangle,
CachedRenderingData,
> = BasicBorderRectangle::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_BasicBorderRectangleVTable() -> BasicBorderRectangleVTable for BasicBorderRectangle
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of the `BorderRectangle` element
pub struct BorderRectangle {
pub background: Property<Brush>,
pub border_width: Property<LogicalLength>,
pub border_radius: Property<LogicalLength>,
pub border_top_left_radius: Property<LogicalLength>,
pub border_top_right_radius: Property<LogicalLength>,
pub border_bottom_left_radius: Property<LogicalLength>,
pub border_bottom_right_radius: Property<LogicalLength>,
pub border_color: Property<Brush>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for BorderRectangle {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
(*backend).draw_border_rectangle(self, self_rc, size, &self.cached_rendering_data);
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl RenderBorderRectangle for BorderRectangle {
fn background(self: Pin<&Self>) -> Brush {
self.background()
}
fn border_width(self: Pin<&Self>) -> LogicalLength {
self.border_width()
}
fn border_radius(self: Pin<&Self>) -> LogicalBorderRadius {
LogicalBorderRadius::from_lengths(
self.border_top_left_radius(),
self.border_top_right_radius(),
self.border_bottom_right_radius(),
self.border_bottom_left_radius(),
)
}
fn border_color(self: Pin<&Self>) -> Brush {
self.border_color()
}
}
impl ItemConsts for BorderRectangle {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
BorderRectangle,
CachedRenderingData,
> = BorderRectangle::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_BorderRectangleVTable() -> BorderRectangleVTable for BorderRectangle
}
declare_item_vtable! {
fn slint_get_TouchAreaVTable() -> TouchAreaVTable for TouchArea
}
declare_item_vtable! {
fn slint_get_FocusScopeVTable() -> FocusScopeVTable for FocusScope
}
crate::declare_item_vtable! {
fn slint_get_KeyBindingVTable() -> KeyBindingVTable for KeyBinding
}
declare_item_vtable! {
fn slint_get_SwipeGestureHandlerVTable() -> SwipeGestureHandlerVTable for SwipeGestureHandler
}
declare_item_vtable! {
fn slint_get_ScaleRotateGestureHandlerVTable() -> ScaleRotateGestureHandlerVTable for ScaleRotateGestureHandler
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of the `Clip` element
pub struct Clip {
pub border_top_left_radius: Property<LogicalLength>,
pub border_top_right_radius: Property<LogicalLength>,
pub border_bottom_left_radius: Property<LogicalLength>,
pub border_bottom_right_radius: Property<LogicalLength>,
pub border_width: Property<LogicalLength>,
pub cached_rendering_data: CachedRenderingData,
pub clip: Property<bool>,
pub is_visibility_clip: Property<bool>,
}
impl Item for Clip {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
event: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
if let Some(pos) = event.position() {
let geometry = self_rc.geometry();
if self.clip()
&& (pos.x < 0 as Coord
|| pos.y < 0 as Coord
|| pos.x_length() > geometry.width_length()
|| pos.y_length() > geometry.height_length())
{
return InputEventFilterResult::Intercept;
}
}
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
(*backend).visit_clip(self, self_rc, size)
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
self.clip()
}
}
impl Clip {
pub fn logical_border_radius(self: Pin<&Self>) -> LogicalBorderRadius {
LogicalBorderRadius::from_lengths(
self.border_top_left_radius(),
self.border_top_right_radius(),
self.border_bottom_right_radius(),
self.border_bottom_left_radius(),
)
}
}
impl ItemConsts for Clip {
const cached_rendering_data_offset: const_field_offset::FieldOffset<Clip, CachedRenderingData> =
Clip::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_ClipVTable() -> ClipVTable for Clip
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The Opacity Item is not meant to be used directly by the .slint code, instead, the `opacity: xxx` or `visible: false` should be used
pub struct Opacity {
// FIXME: this element shouldn't need these geometry property
pub opacity: Property<f32>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for Opacity {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
backend.visit_opacity(self, self_rc, size)
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl Opacity {
// This function determines the optimization opportunities for not having to render the
// children of the Opacity element into a layer:
// * The opacity item typically only one child (this is not guaranteed). If that item has
// no children, then we can skip the layer and apply the opacity directly. This is not perfect though,
// for example if the compiler inserts another synthetic element between the `Opacity` and the actual child,
// then this check will apply a layer even though it might not actually be necessary.
// * If the vale of the opacity is 1.0 then we don't need to do anything.
pub fn need_layer(self_rc: &ItemRc, opacity: f32) -> bool {
if opacity == 1.0 {
return false;
}
let opacity_child = match self_rc.first_child() {
Some(first_child) => first_child,
None => return false, // No children? Don't need a layer then.
};
if opacity_child.next_sibling().is_some() {
return true; // If the opacity item has more than one child, then we need a layer
}
// If the target of the opacity has any children then we need a layer
opacity_child.first_child().is_some()
}
}
impl ItemConsts for Opacity {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
Opacity,
CachedRenderingData,
> = Opacity::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_OpacityVTable() -> OpacityVTable for Opacity
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The Layer Item is not meant to be used directly by the .slint code, instead, the `layer: xxx` property should be used
pub struct Layer {
pub cache_rendering_hint: Property<bool>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for Layer {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
backend.visit_layer(self, self_rc, size)
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl ItemConsts for Layer {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
Layer,
CachedRenderingData,
> = Layer::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_LayerVTable() -> LayerVTable for Layer
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of the `Transform` item.
/// This item is generated by the compiler as soon as any transform property is used on any element.
pub struct Transform {
pub transform_rotation: Property<f32>,
pub transform_scale_x: Property<f32>,
pub transform_scale_y: Property<f32>,
pub transform_origin: Property<LogicalPosition>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for Transform {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
_self_rc: &ItemRc,
_size: LogicalSize,
) -> RenderingResult {
let origin = self.transform_origin().to_euclid().to_vector();
(*backend).translate(origin);
(*backend).scale(self.transform_scale_x(), self.transform_scale_y());
(*backend).rotate(self.transform_rotation());
(*backend).translate(-origin);
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl ItemConsts for Transform {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
Transform,
CachedRenderingData,
> = Transform::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_TransformVTable() -> TransformVTable for Transform
}
declare_item_vtable! {
fn slint_get_FlickableVTable() -> FlickableVTable for Flickable
}
declare_item_vtable! {
fn slint_get_DragAreaVTable() -> DragAreaVTable for DragArea
}
declare_item_vtable! {
fn slint_get_DropAreaVTable() -> DropAreaVTable for DropArea
}
declare_item_vtable! {
fn slint_get_WindowMoveAreaVTable() -> WindowMoveAreaVTable for WindowMoveArea
}
/// The implementation of the `PropertyAnimation` element
/// This animation has the time as animation limit
#[repr(C)]
#[derive(FieldOffsets, SlintElement, Clone, Debug)]
#[pin]
pub struct PropertyAnimation {
#[rtti_field]
pub delay: i32,
/// duration in millisecond
#[rtti_field]
pub duration: i32,
#[rtti_field]
pub iteration_count: f32,
#[rtti_field]
pub direction: AnimationDirection,
#[rtti_field]
pub easing: crate::animations::EasingCurve,
#[rtti_field]
pub enabled: bool,
}
impl Default for PropertyAnimation {
fn default() -> Self {
// Defaults for PropertyAnimation are defined here (for internal Rust code doing programmatic animations)
// as well as in `internal/compiler/builtin_elements.rs` (for generated C++ and Rust code)
Self {
delay: 0,
duration: 0,
iteration_count: 1.,
direction: Default::default(),
easing: Default::default(),
enabled: true,
}
}
}
/// The implementation of the `Window` element
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
pub struct WindowItem {
pub width: Property<LogicalLength>,
pub height: Property<LogicalLength>,
pub safe_area_insets: Property<crate::lengths::LogicalEdges>,
pub virtual_keyboard_position: Property<crate::lengths::LogicalPoint>,
pub virtual_keyboard_size: Property<crate::lengths::LogicalSize>,
pub background: Property<Brush>,
pub title: Property<SharedString>,
pub no_frame: Property<bool>,
pub resize_border_width: Property<LogicalLength>,
pub always_on_top: Property<bool>,
pub full_screen: Property<bool>,
pub minimized: Property<bool>,
pub maximized: Property<bool>,
pub icon: Property<crate::graphics::Image>,
pub default_font_family: Property<SharedString>,
pub default_font_size: Property<LogicalLength>,
pub default_font_weight: Property<i32>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for WindowItem {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
#[cfg(feature = "std")]
self.full_screen.set(std::env::var("SLINT_FULLSCREEN").is_ok());
}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo::default()
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
if self_rc.parent_item(crate::item_tree::ParentItemTraversalMode::StopAtPopups).is_none() {
backend.draw_window_background(self, self_rc, size, &self.cached_rendering_data);
} else {
// Dialogs and other nested Window items
backend.draw_rectangle(self, self_rc, size, &self.cached_rendering_data);
}
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl RenderRectangle for WindowItem {
fn background(self: Pin<&Self>) -> Brush {
self.background()
}
}
fn next_window_item(item: &ItemRc) -> Option<ItemRc> {
let root_item_in_local_item_tree = ItemRc::new_root(item.item_tree().clone());
if root_item_in_local_item_tree.downcast::<crate::items::WindowItem>().is_some() {
Some(root_item_in_local_item_tree)
} else {
root_item_in_local_item_tree
.parent_item(crate::item_tree::ParentItemTraversalMode::FindAllParents)
.and_then(|parent| next_window_item(&parent))
}
}
impl WindowItem {
pub fn font_family(self: Pin<&Self>) -> Option<SharedString> {
let maybe_family = self.default_font_family();
if !maybe_family.is_empty() { Some(maybe_family) } else { None }
}
pub fn font_size(self: Pin<&Self>) -> Option<LogicalLength> {
let font_size = self.default_font_size();
if font_size.get() <= 0 as Coord { None } else { Some(font_size) }
}
pub fn font_weight(self: Pin<&Self>) -> Option<i32> {
let font_weight = self.default_font_weight();
if font_weight == 0 { None } else { Some(font_weight) }
}
pub fn resolved_default_font_size(item_tree: ItemTreeRc) -> LogicalLength {
let first_item = ItemRc::new_root(item_tree);
let window_item = next_window_item(&first_item).unwrap();
Self::resolve_font_property(&window_item, Self::font_size)
.or_else(|| Self::platform_default_font_size(&first_item))
.unwrap_or(crate::textlayout::DEFAULT_FONT_SIZE)
}
/// Returns the default font size reported by the platform (e.g. iOS Dynamic Type),
/// or `None` when the backend doesn't report one. Used as fallback when no
/// `default-font-size` is set in the .slint code, before the renderer's built-in
/// default applies.
fn platform_default_font_size(item: &ItemRc) -> Option<LogicalLength> {
item.window_adapter().and_then(|adapter| {
WindowInner::from_pub(adapter.window()).context().platform_default_font_size()
})
}
fn resolve_font_property<T>(
self_rc: &ItemRc,
property_fn: impl Fn(Pin<&Self>) -> Option<T>,
) -> Option<T> {
let mut window_item_rc = self_rc.clone();
loop {
let window_item = window_item_rc.downcast::<Self>()?;
if let Some(result) = property_fn(window_item.as_pin_ref()) {
return Some(result);
}
window_item_rc = window_item_rc
.parent_item(crate::item_tree::ParentItemTraversalMode::FindAllParents)
.and_then(|p| next_window_item(&p))?;
}
}
/// Creates a new FontRequest that uses the provide local font properties. If they're not set, i.e.
/// the family is an empty string, or the weight is zero, the corresponding properties are fetched
/// from the next parent WindowItem.
pub fn resolved_font_request(
self_rc: &crate::items::ItemRc,
local_font_family: SharedString,
local_font_weight: i32,
local_font_size: LogicalLength,
local_letter_spacing: LogicalLength,
local_line_height_factor: f32,
local_italic: bool,
) -> FontRequest {
let Some(window_item_rc) = next_window_item(self_rc) else {
return FontRequest::default();
};
FontRequest {
family: {
if !local_font_family.is_empty() {
Some(local_font_family)
} else {
Self::resolve_font_property(
&window_item_rc,
crate::items::WindowItem::font_family,
)
}
},
weight: {
if local_font_weight == 0 {
Self::resolve_font_property(
&window_item_rc,
crate::items::WindowItem::font_weight,
)
} else {
Some(local_font_weight)
}
},
pixel_size: {
if local_font_size.get() == 0 as Coord {
Self::resolve_font_property(
&window_item_rc,
crate::items::WindowItem::font_size,
)
.or_else(|| Self::platform_default_font_size(self_rc))
} else {
Some(local_font_size)
}
},
letter_spacing: Some(local_letter_spacing),
// 1 is neutral and negative or non-finite values behave like 1, all mapping to
// None (the font's natural line height); 0 is a valid factor and collapses lines.
line_height_factor: (local_line_height_factor.is_finite()
&& local_line_height_factor >= 0.0
&& local_line_height_factor != 1.0)
.then_some(local_line_height_factor),
italic: local_italic,
}
}
pub fn close(
self: Pin<&Self>,
window_adapter: &Rc<dyn WindowAdapter>,
self_rc: &ItemRc,
) -> bool {
if !is_root_window_item(window_adapter, self_rc) {
return false;
}
let inner = WindowInner::from_pub(window_adapter.window());
let accepted = inner.request_close();
if accepted && let Err(err) = inner.hide() {
crate::debug_log!("Slint: Failed to hide window after close request: {err}");
}
accepted
}
pub fn hide(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
if !is_root_window_item(window_adapter, self_rc) {
return;
}
let _ = WindowInner::from_pub(window_adapter.window()).hide();
}
}
/// A `WindowItem` is considered the adapter's root window only when it is at index 0 of
/// the component item tree currently bound to the adapter.
fn is_root_window_item(window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) -> bool {
if !self_rc.is_root() {
return false;
}
WindowInner::from_pub(window_adapter.window())
.try_component()
.is_some_and(|component| VRc::ptr_eq(&component, self_rc.item_tree()))
}
impl ItemConsts for WindowItem {
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
#[cfg(feature = "ffi")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_windowitem_close(
window_item: Pin<&WindowItem>,
window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
self_index: u32,
) -> bool {
unsafe {
let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
let item_rc = ItemRc::new(self_component.clone(), self_index);
window_item.close(window_adapter, &item_rc)
}
}
#[cfg(feature = "ffi")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_windowitem_hide(
window_item: Pin<&WindowItem>,
window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
self_index: u32,
) {
unsafe {
let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
let item_rc = ItemRc::new(self_component.clone(), self_index);
window_item.hide(window_adapter, &item_rc);
}
}
declare_item_vtable! {
fn slint_get_WindowItemVTable() -> WindowItemVTable for WindowItem
}
/// The implementation used for `ContextMenuArea` and `ContextMenuInternal` elements
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
pub struct ContextMenu {
//pub entries: Property<crate::model::ModelRc<MenuEntry>>,
pub sub_menu: Callback<MenuEntryArg, MenuEntryModel>,
pub activated: Callback<MenuEntryArg>,
pub show: Callback<PointArg>,
pub cached_rendering_data: CachedRenderingData,
pub popup_id: Cell<Option<NonZeroU32>>,
pub enabled: Property<bool>,
#[cfg(target_os = "android")]
long_press_timer: crate::timers::Timer,
}
impl Item for ContextMenu {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo::default()
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardEvent
}
fn input_event(
self: Pin<&Self>,
event: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
if !self.enabled() {
return InputEventResult::EventIgnored;
}
match event {
MouseEvent::Pressed { position, button: PointerEventButton::Right, .. } => {
self.show.call(&(LogicalPosition::from_euclid(*position),));
InputEventResult::EventAccepted
}
#[cfg(target_os = "android")]
MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
let self_weak = _self_rc.downgrade();
let position = *position;
let ctx = WindowInner::from_pub(_window_adapter.window()).context();
self.long_press_timer.start_on(
ctx,
crate::timers::TimerMode::SingleShot,
ctx.platform().long_press_interval(crate::InternalToken),
move || {
let Some(self_rc) = self_weak.upgrade() else { return };
let Some(self_) = self_rc.downcast::<ContextMenu>() else { return };
self_.show.call(&(LogicalPosition::from_euclid(position),));
},
);
InputEventResult::GrabMouse
}
#[cfg(target_os = "android")]
MouseEvent::Released { .. } | MouseEvent::Exit => {
self.long_press_timer.stop();
InputEventResult::EventIgnored
}
#[cfg(target_os = "android")]
MouseEvent::Moved { .. } => InputEventResult::EventAccepted,
_ => InputEventResult::EventIgnored,
}
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
event: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
if !self.enabled() {
return KeyEventResult::EventIgnored;
}
fn is_menu_key(event: &InternalKeyEvent) -> bool {
#[allow(unused_mut)]
let mut is_menu_key = event.key_event.text.contains(crate::input::key_codes::Menu);
#[cfg(target_os = "windows")]
{
// Windows maps Shift + F10 to open the context menu
is_menu_key |= event.key_event.text.contains(crate::input::key_codes::F10)
&& event.key_event.modifiers.shift;
}
is_menu_key
}
if is_menu_key(event) {
self.show.call(&(Default::default(),));
KeyEventResult::EventAccepted
} else {
KeyEventResult::EventIgnored
}
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
_backend: &mut ItemRendererRef,
_self_rc: &ItemRc,
_size: LogicalSize,
) -> RenderingResult {
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl ContextMenu {
pub fn close(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, _: &ItemRc) {
if let Some(id) = self.popup_id.take() {
WindowInner::from_pub(window_adapter.window()).close_popup(id);
}
}
pub fn is_open(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, _: &ItemRc) -> bool {
self.popup_id.get().is_some_and(|id| {
WindowInner::from_pub(window_adapter.window())
.active_popups()
.iter()
.any(|p| p.popup_id == id)
})
}
}
impl ItemConsts for ContextMenu {
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_ContextMenuVTable() -> ContextMenuVTable for ContextMenu
}
#[cfg(feature = "ffi")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_contextmenu_close(
s: Pin<&ContextMenu>,
window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
self_index: u32,
) {
unsafe {
let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
let self_rc = ItemRc::new(self_component.clone(), self_index);
s.close(window_adapter, &self_rc);
}
}
#[cfg(feature = "ffi")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_contextmenu_is_open(
s: Pin<&ContextMenu>,
window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
self_index: u32,
) -> bool {
unsafe {
let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
let self_rc = ItemRc::new(self_component.clone(), self_index);
s.is_open(window_adapter, &self_rc)
}
}
/// The implementation of the `BoxShadow` element
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
pub struct BoxShadow {
pub border_top_left_radius: Property<LogicalLength>,
pub border_top_right_radius: Property<LogicalLength>,
pub border_bottom_left_radius: Property<LogicalLength>,
pub border_bottom_right_radius: Property<LogicalLength>,
// Shadow specific properties
pub offset_x: Property<LogicalLength>,
pub offset_y: Property<LogicalLength>,
pub color: Property<Color>,
pub blur: Property<LogicalLength>,
pub spread: Property<LogicalLength>,
pub inset: Property<bool>,
pub cached_rendering_data: CachedRenderingData,
}
impl BoxShadow {
pub fn logical_border_radius(self: Pin<&Self>) -> LogicalBorderRadius {
LogicalBorderRadius::from_lengths(
self.border_top_left_radius(),
self.border_top_right_radius(),
self.border_bottom_right_radius(),
self.border_bottom_left_radius(),
)
}
}
impl Item for BoxShadow {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}
fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
backend: &mut ItemRendererRef,
self_rc: &ItemRc,
size: LogicalSize,
) -> RenderingResult {
(*backend).draw_box_shadow(self, self_rc, size);
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
if self.inset() {
// Inset shadow paints inside the geometry; never extends outside.
geometry
} else {
let pad = self.blur() + LogicalLength::new(self.spread().get().max(0 as crate::Coord));
geometry
.outer_rect(euclid::SideOffsets2D::from_length_all_same(pad))
.translate(LogicalVector::from_lengths(self.offset_x(), self.offset_y()))
}
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl ItemConsts for BoxShadow {
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_BoxShadowVTable() -> BoxShadowVTable for BoxShadow
}
declare_item_vtable! {
fn slint_get_ComponentContainerVTable() -> ComponentContainerVTable for ComponentContainer
}
declare_item_vtable! {
fn slint_get_ComplexTextVTable() -> ComplexTextVTable for ComplexText
}
declare_item_vtable! {
fn slint_get_StyledTextItemVTable() -> StyledTextItemVTable for StyledTextItem
}
declare_item_vtable! {
fn slint_get_SimpleTextVTable() -> SimpleTextVTable for SimpleText
}
declare_item_vtable! {
fn slint_get_TextInputVTable() -> TextInputVTable for TextInput
}
declare_item_vtable! {
fn slint_get_ImageItemVTable() -> ImageItemVTable for ImageItem
}
declare_item_vtable! {
fn slint_get_ClippedImageVTable() -> ClippedImageVTable for ClippedImage
}
#[cfg(feature = "path")]
declare_item_vtable! {
fn slint_get_PathVTable() -> PathVTable for Path
}
declare_item_vtable! {
fn slint_get_MenuItemVTable() -> MenuItemVTable for MenuItem
}
declare_item_vtable! {
fn slint_get_SystemTrayIconVTable() -> SystemTrayIconVTable for SystemTrayIcon
}
macro_rules! declare_enums {
($( $(#[$enum_doc:meta])* $vis:vis enum $Name:ident { $( $(#[$value_doc:meta])* $Value:ident,)* })*) => {
$(
#[derive(Copy, Clone, Debug, PartialEq, Eq, strum::EnumString, strum::Display, Hash)]
#[repr(u32)]
#[strum(serialize_all = "kebab-case")]
$(#[$enum_doc])*
pub enum $Name {
$( $(#[$value_doc])* $Value),*
}
impl Default for $Name {
fn default() -> Self {
// Always return the first value
($(Self::$Value,)*).0
}
}
)*
};
}
i_slint_common::for_each_enums!(declare_enums);
/// Internal transparent hover tracker synthesized by tooltip lowering.
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
pub struct TooltipArea {
pub has_hover: Property<bool>,
pub mouse_x: Property<LogicalLength>,
pub mouse_y: Property<LogicalLength>,
pub text: Property<crate::styled_text::StyledText>,
pub delay: Property<i64>,
pub offset: Property<LogicalLength>,
pub show: Callback<VoidArg>,
pub hide: Callback<VoidArg>,
pub cached_rendering_data: CachedRenderingData,
popup_visible: Cell<bool>,
timer: crate::timers::Timer,
}
impl Item for TooltipArea {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_cross_axis_constraint: Coord,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo::default()
}
fn input_event_filter_before_children(
self: Pin<&Self>,
event: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventFilterResult {
// Track hover in the filter stage so enter/leave transitions are reliable,
// independent of later input handling.
if matches!(event, MouseEvent::DragMove { .. } | MouseEvent::Drop { .. }) {
self.set_hover_state(false, self_rc);
return InputEventFilterResult::ForwardAndIgnore;
}
if let Some(pos) = event.position() {
Self::FIELD_OFFSETS.mouse_x().apply_pin(self).set(pos.x_length());
Self::FIELD_OFFSETS.mouse_y().apply_pin(self).set(pos.y_length());
}
let next_hover = !matches!(event, MouseEvent::Exit);
self.set_hover_state(next_hover, self_rc);
if next_hover && !self.popup_visible.get() && matches!(event, MouseEvent::Moved { .. }) {
self.schedule_show(self_rc);
}
// Observe without claiming: siblings still receive the event; the routing tracks
// this item on its observers side-list and delivers Exit when the pointer leaves.
InputEventFilterResult::ForwardAndObserve
}
fn input_event(
self: Pin<&Self>,
event: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursorInner,
) -> InputEventResult {
if matches!(event, MouseEvent::Exit) {
self.set_hover_state(false, _self_rc);
}
InputEventResult::EventIgnored
}
fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
_backend: &mut ItemRendererRef,
_self_rc: &ItemRc,
_size: LogicalSize,
) -> RenderingResult {
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}
fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}
impl TooltipArea {
fn schedule_show(self: Pin<&Self>, self_rc: &ItemRc) {
let delay_ms = self.delay().max(0) as u64;
if delay_ms == 0 {
if self.has_hover() {
self.show.call(&());
self.popup_visible.set(true);
}
return;
}
let self_weak = self_rc.downgrade();
// Start on the context this item's window belongs to, not on whichever one is
// current: a component built with `new_with_context` must keep its timers there.
let Some(window_adapter) = self_rc.window_adapter() else { return };
let ctx = crate::window::WindowInner::from_pub(window_adapter.window()).context();
self.timer.start_on(
ctx,
crate::timers::TimerMode::SingleShot,
Duration::from_millis(delay_ms),
move || {
let Some(self_rc) = self_weak.upgrade() else { return };
let Some(tooltip_area) = self_rc.downcast::<TooltipArea>() else { return };
let tooltip_area = tooltip_area.as_pin_ref();
if tooltip_area.has_hover() {
tooltip_area.show.call(&());
tooltip_area.popup_visible.set(true);
}
},
);
}
fn hide_now(self: Pin<&Self>) {
self.timer.stop();
if self.popup_visible.replace(false) {
self.hide.call(&());
}
}
fn set_hover_state(self: Pin<&Self>, new_hover: bool, self_rc: &ItemRc) {
let old_hover = self.has_hover();
if old_hover == new_hover {
return;
}
Self::FIELD_OFFSETS.has_hover().apply_pin(self).set(new_hover);
if new_hover {
self.schedule_show(self_rc);
} else {
self.hide_now();
}
}
}
impl ItemConsts for TooltipArea {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
TooltipArea,
CachedRenderingData,
> = TooltipArea::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}
declare_item_vtable! {
fn slint_get_TooltipAreaVTable() -> TooltipAreaVTable for TooltipArea
}
/// Expands to a builtin struct field's declared default value,
/// or to the zero value of the field's type when no default is declared.
/// Number literals for `Coord` fields are cast, because `Coord` is `f32` or `i32`
/// depending on `cfg(slint_int_coord)`.
macro_rules! builtin_struct_field_default {
(Coord, $default:expr) => {
($default) as Coord
};
($field_type:ident, $default:expr) => {
$default
};
($field_type:ident) => {
::core::default::Default::default()
};
}
/// Expands to the documentation text of a builtin struct field's declared default value:
/// an intra-doc link to the variant for an enum value, plain code for a literal.
/// The parentheses an enum value needs to be a single token tree are dropped.
macro_rules! builtin_struct_field_default_doc {
(($($default:tt)*)) => {
builtin_struct_field_default_doc!($($default)*)
};
($enum:ident :: $value:ident) => {
concat!("[`", stringify!($enum), "::", stringify!($value), "`]")
};
($default:literal) => {
concat!("`", stringify!($default), "`")
};
}
macro_rules! declare_builtin_structs {
($(
$(#[$struct_attr:meta])*
$vis:vis struct $Name:ident {
$( $(#[$field_attr:meta])* $field:ident : $field_type:ident $(= $field_default:tt)?, )*
}
)*) => {
$(
#[derive(Clone, Debug, PartialEq)]
#[repr(C)]
$(#[$struct_attr])*
pub struct $Name {
$(
$(#[$field_attr])*
$(
#[doc = ""]
#[doc = concat!("Defaults to ", builtin_struct_field_default_doc!($field_default), ".")]
)?
pub $field : $field_type,
)*
}
// Not derived, so that the fields take their declared default values
impl ::core::default::Default for $Name {
fn default() -> Self {
Self {
$($field: builtin_struct_field_default!($field_type $(, $field_default)?),)*
}
}
}
)*
};
}
i_slint_common::for_each_builtin_structs!(declare_builtin_structs);
#[test]
fn builtin_struct_field_defaults() {
// Fields without a declared default value take the zero value of their type,
// like with derive(Default)
let table_column = TableColumn::default();
assert_eq!(table_column.sort_order, SortOrder::Unsorted);
assert_eq!(table_column.min_width, 0 as Coord);
assert_eq!(table_column.horizontal_stretch, 0.0);
assert_eq!(table_column.title, SharedString::default());
assert!(!KeyEvent::default().repeat);
assert_eq!(PointerEvent::default().touch_finger_id, 0);
// Fields with a declared default value take it
let hints = InputMethodHints::default();
assert_eq!(hints.capitalization, CapitalizationMode::Sentences);
assert!(hints.auto_correct);
assert!(hints.auto_complete);
}
#[cfg(feature = "ffi")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_item_absolute_position(
self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
self_index: u32,
) -> crate::lengths::LogicalPoint {
let self_rc = ItemRc::new(self_component.clone(), self_index);
// Map the item's own geometry origin through the ancestor transforms so the result is the
// item's absolute position, not its parent's.
self_rc.map_to_window(self_rc.geometry().origin)
}