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).
2275 lines
69 KiB
Rust
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)
|
|
}
|