#![warn(missing_docs)]
extern crate alloc;
use crate::input::{KeyEvent, KeyboardModifiers, MouseEvent, MouseEventType};
use crate::items::{ItemRc, ItemRef, ItemWeak};
use crate::properties::{InterpolatedPropertyValue, Property, PropertyTracker};
#[cfg(feature = "rtti")]
use crate::rtti::{BuiltinItem, FieldInfo, PropertyInfo, ValueType};
use crate::SharedArray;
#[cfg(feature = "rtti")]
use crate::Signal;
use crate::{
component::{ComponentRc, ComponentWeak},
slice::Slice,
};
use auto_enums::auto_enum;
use cgmath::Matrix4;
use const_field_offset::FieldOffsets;
use core::pin::Pin;
use sixtyfps_corelib_macros::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
pub type Rect = euclid::default::Rect<f32>;
pub type IntRect = euclid::default::Rect<i32>;
pub type Point = euclid::default::Point2D<f32>;
pub type Size = euclid::default::Size2D<f32>;
#[derive(Copy, Clone, PartialEq, Debug, Default)]
pub struct RgbaColor<T> {
pub alpha: T,
pub red: T,
pub green: T,
pub blue: T,
}
#[derive(Copy, Clone, PartialEq, Debug, Default)]
#[repr(C)]
pub struct Color {
red: u8,
green: u8,
blue: u8,
alpha: u8,
}
impl From<RgbaColor<u8>> for Color {
fn from(col: RgbaColor<u8>) -> Self {
Self { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
}
}
impl From<Color> for RgbaColor<u8> {
fn from(col: Color) -> Self {
RgbaColor { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
}
}
impl From<RgbaColor<u8>> for RgbaColor<f32> {
fn from(col: RgbaColor<u8>) -> Self {
Self {
red: (col.red as f32) / 255.0,
green: (col.green as f32) / 255.0,
blue: (col.blue as f32) / 255.0,
alpha: (col.alpha as f32) / 255.0,
}
}
}
impl From<Color> for RgbaColor<f32> {
fn from(col: Color) -> Self {
let u8col: RgbaColor<u8> = col.into();
u8col.into()
}
}
impl From<RgbaColor<f32>> for Color {
fn from(col: RgbaColor<f32>) -> Self {
Self {
red: (col.red * 255.) as u8,
green: (col.green * 255.) as u8,
blue: (col.blue * 255.) as u8,
alpha: (col.alpha * 255.) as u8,
}
}
}
impl Color {
pub const fn from_argb_encoded(encoded: u32) -> Color {
Self {
red: (encoded >> 16) as u8,
green: (encoded >> 8) as u8,
blue: encoded as u8,
alpha: (encoded >> 24) as u8,
}
}
pub fn as_argb_encoded(&self) -> u32 {
((self.red as u32) << 16)
| ((self.green as u32) << 8)
| (self.blue as u32)
| ((self.alpha as u32) << 24)
}
pub fn from_argb_u8(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
Self { red, green, blue, alpha }
}
pub fn from_rgb_u8(red: u8, green: u8, blue: u8) -> Self {
Self::from_argb_u8(255, red, green, blue)
}
pub fn from_argb_f32(alpha: f32, red: f32, green: f32, blue: f32) -> Self {
RgbaColor { alpha, red, green, blue }.into()
}
pub fn from_rgb_f32(red: f32, green: f32, blue: f32) -> Self {
Self::from_argb_f32(1.0, red, green, blue)
}
pub fn to_argb_u8(&self) -> RgbaColor<u8> {
RgbaColor::from(*self)
}
pub fn to_argb_f32(&self) -> RgbaColor<f32> {
RgbaColor::from(*self)
}
pub fn red(self) -> u8 {
self.red
}
pub fn green(self) -> u8 {
self.green
}
pub fn blue(self) -> u8 {
self.blue
}
pub fn alpha(self) -> u8 {
self.alpha
}
}
impl InterpolatedPropertyValue for Color {
fn interpolate(self, target_value: Self, t: f32) -> Self {
Self {
red: self.red.interpolate(target_value.red, t),
green: self.green.interpolate(target_value.green, t),
blue: self.blue.interpolate(target_value.blue, t),
alpha: self.alpha.interpolate(target_value.alpha, t),
}
}
}
impl std::fmt::Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "argb({}, {}, {}, {})", self.alpha, self.red, self.green, self.blue)
}
}
#[derive(Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum Resource {
None,
AbsoluteFilePath(crate::SharedString),
EmbeddedData(super::slice::Slice<'static, u8>),
#[allow(missing_docs)]
EmbeddedRgbaImage { width: u32, height: u32, data: super::sharedarray::SharedArray<u32> },
}
impl Default for Resource {
fn default() -> Self {
Resource::None
}
}
#[derive(PartialEq, Debug)]
#[repr(C)]
#[allow(missing_docs)]
pub enum HighLevelRenderingPrimitive {
NoContents,
Rectangle { width: f32, height: f32, border_width: f32, border_radius: f32 },
Image { source: crate::Resource, source_clip_rect: IntRect },
Text { text: crate::SharedString, font_request: super::font::FontRequest },
Path { width: f32, height: f32, elements: crate::PathData, stroke_width: f32 },
ClipRect { width: f32, height: f32 },
}
impl Default for HighLevelRenderingPrimitive {
fn default() -> Self {
Self::NoContents
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub enum RenderingVariable {
Translate(f32, f32),
Color(Color),
ScaledWidth(f32),
ScaledHeight(f32),
TextCursor(f32, f32, f32),
TextSelection(f32, f32, f32),
}
impl RenderingVariable {
pub fn as_color(&self) -> &Color {
match self {
RenderingVariable::Color(c) => c,
_ => panic!("internal error: expected color but found something else"),
}
}
pub fn as_scaled_width(&self) -> f32 {
match self {
RenderingVariable::ScaledWidth(w) => *w,
_ => panic!("internal error: expected scaled width but found something else"),
}
}
pub fn as_scaled_height(&self) -> f32 {
match self {
RenderingVariable::ScaledHeight(h) => *h,
_ => panic!("internal error: expected scaled height but found something else"),
}
}
}
pub trait Frame {
type LowLevelRenderingPrimitive;
fn render_primitive(
&mut self,
primitive: &Self::LowLevelRenderingPrimitive,
transform: &Matrix4<f32>,
variables: SharedArray<RenderingVariable>,
) -> Vec<Self::LowLevelRenderingPrimitive>;
}
pub trait RenderingPrimitivesBuilder {
type LowLevelRenderingPrimitive;
fn create(
&mut self,
primitive: HighLevelRenderingPrimitive,
) -> Self::LowLevelRenderingPrimitive;
}
pub trait GraphicsBackend: Sized {
type LowLevelRenderingPrimitive;
type Frame: Frame<LowLevelRenderingPrimitive = Self::LowLevelRenderingPrimitive>;
type RenderingPrimitivesBuilder: RenderingPrimitivesBuilder<
LowLevelRenderingPrimitive = Self::LowLevelRenderingPrimitive,
>;
fn new_rendering_primitives_builder(&mut self) -> Self::RenderingPrimitivesBuilder;
fn finish_primitives(&mut self, builder: Self::RenderingPrimitivesBuilder);
fn new_frame(&mut self, width: u32, height: u32, clear_color: &Color) -> Self::Frame;
fn present_frame(&mut self, frame: Self::Frame);
fn window(&self) -> &winit::window::Window;
}
pub struct TrackingRenderingPrimitive<Backend: GraphicsBackend> {
pub primitive: Backend::LowLevelRenderingPrimitive,
pub dependency_tracker: core::pin::Pin<Box<crate::properties::PropertyTracker>>,
}
impl<Backend: GraphicsBackend> TrackingRenderingPrimitive<Backend> {
pub fn new(update_fn: impl FnOnce() -> Backend::LowLevelRenderingPrimitive) -> Self {
let dependency_tracker = Box::pin(crate::properties::PropertyTracker::default());
let primitive = dependency_tracker.as_ref().evaluate(update_fn);
Self { primitive, dependency_tracker }
}
}
pub type RenderingCache<Backend> = vec_arena::Arena<TrackingRenderingPrimitive<Backend>>;
type WindowFactoryFn<Backend> =
dyn Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend;
struct MappedWindow<Backend: GraphicsBackend + 'static> {
backend: RefCell<Backend>,
rendering_cache: RefCell<RenderingCache<Backend>>,
constraints: Cell<crate::layout::LayoutInfo>,
}
enum GraphicsWindowBackendState<Backend: GraphicsBackend + 'static> {
Unmapped,
Mapped(MappedWindow<Backend>),
}
impl<Backend: GraphicsBackend + 'static> GraphicsWindowBackendState<Backend> {
fn as_mapped(&self) -> &MappedWindow<Backend> {
match self {
GraphicsWindowBackendState::Unmapped => panic!(
"internal error: tried to access window functions that require a mapped window"
),
GraphicsWindowBackendState::Mapped(mw) => &mw,
}
}
}
#[derive(FieldOffsets)]
#[repr(C)]
#[pin]
struct WindowProperties {
scale_factor: Property<f32>,
width: Property<f32>,
height: Property<f32>,
}
impl Default for WindowProperties {
fn default() -> Self {
Self {
scale_factor: Property::new(1.0),
width: Property::new(800.),
height: Property::new(600.),
}
}
}
pub struct GraphicsWindow<Backend: GraphicsBackend + 'static> {
window_factory: Box<WindowFactoryFn<Backend>>,
map_state: RefCell<GraphicsWindowBackendState<Backend>>,
properties: Pin<Box<WindowProperties>>,
cursor_blinker: std::cell::RefCell<pin_weak::rc::PinWeak<TextCursorBlinker>>,
keyboard_modifiers: std::cell::Cell<KeyboardModifiers>,
component: std::cell::RefCell<ComponentWeak>,
layout_listener: Pin<Rc<PropertyTracker>>,
focus_item: std::cell::RefCell<ItemWeak>,
mouse_input_state: std::cell::Cell<crate::input::MouseInputState>,
active_popup: std::cell::RefCell<Option<(ComponentRc, Point)>>,
}
impl<Backend: GraphicsBackend + 'static> GraphicsWindow<Backend> {
pub fn new(
graphics_backend_factory: impl Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend
+ 'static,
) -> Rc<Self> {
Rc::new(Self {
window_factory: Box::new(graphics_backend_factory),
map_state: RefCell::new(GraphicsWindowBackendState::Unmapped),
properties: Box::pin(WindowProperties::default()),
cursor_blinker: Default::default(),
keyboard_modifiers: Default::default(),
component: Default::default(),
layout_listener: Rc::pin(Default::default()),
focus_item: Default::default(),
mouse_input_state: Default::default(),
active_popup: Default::default(),
})
}
pub fn id(&self) -> Option<winit::window::WindowId> {
Some(self.map_state.borrow().as_mapped().backend.borrow().window().id())
}
fn apply_geometry_constraint(&self, constraints: crate::layout::LayoutInfo) {
match &*self.map_state.borrow() {
GraphicsWindowBackendState::Unmapped => {}
GraphicsWindowBackendState::Mapped(window) => {
if constraints != window.constraints.get() {
let min_width = constraints.min_width.min(constraints.max_width);
let min_height = constraints.min_height.min(constraints.max_height);
let max_width = constraints.max_width.max(constraints.min_width);
let max_height = constraints.max_height.max(constraints.min_height);
window.backend.borrow().window().set_min_inner_size(
if min_width > 0. || min_height > 0. {
Some(winit::dpi::PhysicalSize::new(min_width, min_height))
} else {
None
},
);
window.backend.borrow().window().set_max_inner_size(
if max_width < f32::MAX || max_height < f32::MAX {
Some(winit::dpi::PhysicalSize::new(
max_width.min(65535.),
max_height.min(65535.),
))
} else {
None
},
);
window.constraints.set(constraints);
}
}
}
}
}
impl<Backend: GraphicsBackend> Drop for GraphicsWindow<Backend> {
fn drop(&mut self) {
match &*self.map_state.borrow() {
GraphicsWindowBackendState::Unmapped => {}
GraphicsWindowBackendState::Mapped(mw) => {
crate::eventloop::unregister_window(mw.backend.borrow().window().id());
}
}
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
existing_blinker.stop();
}
}
}
impl<Backend: GraphicsBackend> crate::eventloop::GenericWindow for GraphicsWindow<Backend> {
fn set_component(self: Rc<Self>, component: &ComponentRc) {
*self.component.borrow_mut() = vtable::VRc::downgrade(&component)
}
fn draw(self: Rc<Self>) {
let component_rc = self.component.borrow().upgrade().unwrap();
let component = ComponentRc::borrow_pin(&component_rc);
{
if self.layout_listener.as_ref().is_dirty() {
self.layout_listener.as_ref().evaluate(|| {
self.apply_geometry_constraint(component.as_ref().layout_info());
component.as_ref().apply_layout(self.get_geometry());
if let Some((popup, pos)) = &*self.active_popup.borrow() {
let popup = ComponentRc::borrow_pin(popup);
let popup_root = popup.as_ref().get_item_ref(0);
let size = if let Some(window_item) = ItemRef::downcast_pin(popup_root) {
let layout_info = popup.as_ref().layout_info();
let width =
crate::items::Window::FIELD_OFFSETS.width.apply_pin(window_item);
let mut w = width.get();
if w < layout_info.min_width {
w = layout_info.min_width;
width.set(w);
}
let height =
crate::items::Window::FIELD_OFFSETS.height.apply_pin(window_item);
let mut h = height.get();
if h < layout_info.min_height {
h = layout_info.min_height;
height.set(h);
}
Size::new(h, w)
} else {
Size::default()
};
popup.as_ref().apply_layout(Rect::new(pos.clone(), size));
}
})
}
}
{
let map_state = self.map_state.borrow();
let window = map_state.as_mapped();
let mut backend = window.backend.borrow_mut();
let mut rendering_primitives_builder = backend.new_rendering_primitives_builder();
crate::item_tree::visit_items(
&component_rc,
crate::item_tree::TraversalOrder::BackToFront,
|_, item, _, _| {
crate::item_rendering::update_item_rendering_data(
item,
&window.rendering_cache,
&mut rendering_primitives_builder,
&self,
);
crate::item_tree::ItemVisitorResult::Continue(())
},
(),
);
if let Some(popup) = &*self.active_popup.borrow() {
crate::item_tree::visit_items(
&popup.0,
crate::item_tree::TraversalOrder::BackToFront,
|_, item, _, _| {
crate::item_rendering::update_item_rendering_data(
item,
&window.rendering_cache,
&mut rendering_primitives_builder,
&self,
);
crate::item_tree::ItemVisitorResult::Continue(())
},
(),
);
}
backend.finish_primitives(rendering_primitives_builder);
}
let map_state = self.map_state.borrow();
let window = map_state.as_mapped();
let mut backend = window.backend.borrow_mut();
let size = backend.window().inner_size();
let mut frame = backend.new_frame(
size.width,
size.height,
&RgbaColor { red: 255 as u8, green: 255, blue: 255, alpha: 255 }.into(),
);
crate::item_rendering::render_component_items(
&component_rc,
&mut frame,
&window.rendering_cache,
&self,
Point::default(),
);
if let Some(popup) = &*self.active_popup.borrow() {
crate::item_rendering::render_component_items(
&popup.0,
&mut frame,
&window.rendering_cache,
&self,
popup.1,
);
}
backend.present_frame(frame);
}
fn process_mouse_input(
self: Rc<Self>,
pos: winit::dpi::PhysicalPosition<f64>,
what: MouseEventType,
) {
let mut pos = euclid::point2(pos.x as _, pos.y as _);
let active_popup = (*self.active_popup.borrow()).clone();
let component = if let Some(popup) = &active_popup {
pos -= popup.1.to_vector();
if what == MouseEventType::MousePressed {
let geom =
ComponentRc::borrow_pin(&popup.0).as_ref().get_item_ref(0).as_ref().geometry();
if !geom.contains(pos) {
self.close_popup();
return;
}
}
popup.0.clone()
} else {
self.component.borrow().upgrade().unwrap()
};
self.mouse_input_state.set(crate::input::process_mouse_input(
component,
MouseEvent { pos, what },
&crate::eventloop::ComponentWindow::new(self.clone()),
self.mouse_input_state.take(),
));
if active_popup.is_some() {
if what == MouseEventType::MouseReleased {
self.close_popup();
}
}
}
fn process_key_input(self: Rc<Self>, event: &KeyEvent) {
if let Some(focus_item) = self.as_ref().focus_item.borrow().upgrade() {
let window = &crate::eventloop::ComponentWindow::new(self.clone());
focus_item.borrow().as_ref().key_event(event, &window);
}
}
fn with_platform_window(&self, callback: &dyn Fn(&winit::window::Window)) {
let map_state = self.map_state.borrow();
let window = map_state.as_mapped();
let backend = window.backend.borrow();
let handle = backend.window();
callback(handle);
}
fn map_window(self: Rc<Self>, event_loop: &crate::eventloop::EventLoop) {
if matches!(&*self.map_state.borrow(), GraphicsWindowBackendState::Mapped(..)) {
return;
}
let id = {
let window_builder = winit::window::WindowBuilder::new().with_title("SixtyFPS Window");
let backend = self.window_factory.as_ref()(&event_loop, window_builder);
let platform_window = backend.window();
if std::env::var("SIXTYFPS_FULLSCREEN").is_ok() {
platform_window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
}
let window_id = platform_window.id();
{
self.properties.as_ref().scale_factor.set(platform_window.scale_factor() as _);
let existing_size = platform_window.inner_size();
let mut new_size = existing_size;
let component = self.component.borrow().upgrade().unwrap();
let component = ComponentRc::borrow_pin(&component);
let root_item = component.as_ref().get_item_ref(0);
if let Some(window_item) = ItemRef::downcast_pin(root_item) {
let width =
crate::items::Window::FIELD_OFFSETS.width.apply_pin(window_item).get();
if width > 0. {
new_size.width = width as _;
}
let height =
crate::items::Window::FIELD_OFFSETS.height.apply_pin(window_item).get();
if height > 0. {
new_size.height = height as _;
}
{
let window = self.clone();
window_item.as_ref().width.set_binding(move || {
WindowProperties::FIELD_OFFSETS
.width
.apply_pin(window.properties.as_ref())
.get()
});
}
{
let window = self.clone();
window_item.as_ref().height.set_binding(move || {
WindowProperties::FIELD_OFFSETS
.height
.apply_pin(window.properties.as_ref())
.get()
});
}
}
if new_size != existing_size {
platform_window.set_inner_size(new_size)
}
self.properties.as_ref().width.set(new_size.width as _);
self.properties.as_ref().height.set(new_size.height as _);
}
self.map_state.replace(GraphicsWindowBackendState::Mapped(MappedWindow {
backend: RefCell::new(backend),
rendering_cache: Default::default(),
constraints: Default::default(),
}));
window_id
};
crate::eventloop::register_window(
id,
self.clone() as Rc<dyn crate::eventloop::GenericWindow>,
);
}
fn request_redraw(&self) {
match &*self.map_state.borrow() {
GraphicsWindowBackendState::Unmapped => {}
GraphicsWindowBackendState::Mapped(window) => {
window.backend.borrow().window().request_redraw()
}
}
}
fn unmap_window(self: Rc<Self>) {
self.map_state.replace(GraphicsWindowBackendState::Unmapped);
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
existing_blinker.stop();
}
}
fn scale_factor(&self) -> f32 {
WindowProperties::FIELD_OFFSETS.scale_factor.apply_pin(self.properties.as_ref()).get()
}
fn set_scale_factor(&self, factor: f32) {
self.properties.as_ref().scale_factor.set(factor);
}
fn set_width(&self, width: f32) {
self.properties.as_ref().width.set(width);
}
fn set_height(&self, height: f32) {
self.properties.as_ref().height.set(height);
}
fn get_geometry(&self) -> crate::graphics::Rect {
euclid::rect(
0.,
0.,
WindowProperties::FIELD_OFFSETS.width.apply_pin(self.properties.as_ref()).get(),
WindowProperties::FIELD_OFFSETS.height.apply_pin(self.properties.as_ref()).get(),
)
}
fn free_graphics_resources<'a>(self: Rc<Self>, items: &Slice<'a, Pin<ItemRef<'a>>>) {
match &*self.map_state.borrow() {
GraphicsWindowBackendState::Unmapped => {}
GraphicsWindowBackendState::Mapped(window) => {
crate::item_rendering::free_item_rendering_data(items, &window.rendering_cache)
}
}
}
fn set_cursor_blink_binding(&self, prop: &crate::properties::Property<bool>) {
let existing_blinker = self.cursor_blinker.borrow().clone();
let blinker = existing_blinker.upgrade().unwrap_or_else(|| {
let new_blinker = TextCursorBlinker::new();
*self.cursor_blinker.borrow_mut() =
pin_weak::rc::PinWeak::downgrade(new_blinker.clone());
new_blinker
});
TextCursorBlinker::set_binding(blinker, prop);
}
fn current_keyboard_modifiers(&self) -> KeyboardModifiers {
self.keyboard_modifiers.get()
}
fn set_current_keyboard_modifiers(&self, state: KeyboardModifiers) {
self.keyboard_modifiers.set(state)
}
fn set_focus_item(self: Rc<Self>, focus_item: &ItemRc) {
let window = crate::eventloop::ComponentWindow::new(self.clone());
if let Some(old_focus_item) = self.as_ref().focus_item.borrow().upgrade() {
old_focus_item
.borrow()
.as_ref()
.focus_event(&crate::input::FocusEvent::FocusOut, &window);
}
*self.as_ref().focus_item.borrow_mut() = focus_item.downgrade();
focus_item.borrow().as_ref().focus_event(&crate::input::FocusEvent::FocusIn, &window);
}
fn set_focus(self: Rc<Self>, have_focus: bool) {
let window = crate::eventloop::ComponentWindow::new(self.clone());
let event = if have_focus {
crate::input::FocusEvent::WindowReceivedFocus
} else {
crate::input::FocusEvent::WindowLostFocus
};
if let Some(focus_item) = self.as_ref().focus_item.borrow().upgrade() {
focus_item.borrow().as_ref().focus_event(&event, &window);
}
}
fn show_popup(&self, popup: &ComponentRc, position: Point) {
self.layout_listener.set_dirty();
*self.active_popup.borrow_mut() = Some((popup.clone(), position));
}
fn close_popup(&self) {
*self.active_popup.borrow_mut() = None;
}
}
#[repr(C)]
#[derive(FieldOffsets, Default, BuiltinItem, Clone, Debug, PartialEq)]
#[pin]
pub struct PathLineTo {
#[rtti_field]
pub x: f32,
#[rtti_field]
pub y: f32,
}
#[repr(C)]
#[derive(FieldOffsets, Default, BuiltinItem, Clone, Debug, PartialEq)]
#[pin]
pub struct PathArcTo {
#[rtti_field]
pub x: f32,
#[rtti_field]
pub y: f32,
#[rtti_field]
pub radius_x: f32,
#[rtti_field]
pub radius_y: f32,
#[rtti_field]
pub x_rotation: f32,
#[rtti_field]
pub large_arc: bool,
#[rtti_field]
pub sweep: bool,
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub enum PathElement {
LineTo(PathLineTo),
ArcTo(PathArcTo),
Close,
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub enum PathEvent {
Begin,
Line,
Quadratic,
Cubic,
EndOpen,
EndClosed,
}
struct ToLyonPathEventIterator<'a> {
events_it: std::slice::Iter<'a, PathEvent>,
coordinates_it: std::slice::Iter<'a, Point>,
first: Option<&'a Point>,
last: Option<&'a Point>,
}
impl<'a> Iterator for ToLyonPathEventIterator<'a> {
type Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>;
fn next(&mut self) -> Option<Self::Item> {
use lyon::path::Event;
self.events_it.next().map(|event| match event {
PathEvent::Begin => Event::Begin { at: self.coordinates_it.next().unwrap().clone() },
PathEvent::Line => Event::Line {
from: self.coordinates_it.next().unwrap().clone(),
to: self.coordinates_it.next().unwrap().clone(),
},
PathEvent::Quadratic => Event::Quadratic {
from: self.coordinates_it.next().unwrap().clone(),
ctrl: self.coordinates_it.next().unwrap().clone(),
to: self.coordinates_it.next().unwrap().clone(),
},
PathEvent::Cubic => Event::Cubic {
from: self.coordinates_it.next().unwrap().clone(),
ctrl1: self.coordinates_it.next().unwrap().clone(),
ctrl2: self.coordinates_it.next().unwrap().clone(),
to: self.coordinates_it.next().unwrap().clone(),
},
PathEvent::EndOpen => Event::End {
first: self.first.unwrap().clone(),
last: self.last.unwrap().clone(),
close: false,
},
PathEvent::EndClosed => Event::End {
first: self.first.unwrap().clone(),
last: self.last.unwrap().clone(),
close: true,
},
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.events_it.size_hint()
}
}
impl<'a> ExactSizeIterator for ToLyonPathEventIterator<'a> {}
struct TransformedLyonPathIterator<EventIt> {
it: EventIt,
transform: lyon::math::Transform,
}
impl<EventIt: Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>>> Iterator
for TransformedLyonPathIterator<EventIt>
{
type Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>;
fn next(&mut self) -> Option<Self::Item> {
self.it.next().map(|ev| ev.transformed(&self.transform))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.it.size_hint()
}
}
impl<EventIt: Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>>>
ExactSizeIterator for TransformedLyonPathIterator<EventIt>
{
}
pub struct PathDataIterator<'a> {
it: LyonPathIteratorVariant<'a>,
transform: Option<lyon::math::Transform>,
}
enum LyonPathIteratorVariant<'a> {
FromPath(lyon::path::Path),
FromEvents(&'a crate::SharedArray<PathEvent>, &'a crate::SharedArray<Point>),
}
impl<'a> PathDataIterator<'a> {
#[auto_enum(Iterator)]
pub fn iter(
&'a self,
) -> impl Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a {
match &self.it {
LyonPathIteratorVariant::FromPath(path) => self.apply_transform(path.iter()),
LyonPathIteratorVariant::FromEvents(events, coordinates) => {
self.apply_transform(ToLyonPathEventIterator {
events_it: events.iter(),
coordinates_it: coordinates.iter(),
first: coordinates.first(),
last: coordinates.last(),
})
}
}
}
fn fit(&mut self, width: f32, height: f32) {
if width > 0. || height > 0. {
let br = lyon::algorithms::aabb::bounding_rect(self.iter());
self.transform = Some(lyon::algorithms::fit::fit_rectangle(
&br,
&Rect::from_size(Size::new(width, height)),
lyon::algorithms::fit::FitStyle::Min,
));
}
}
#[auto_enum(Iterator)]
fn apply_transform(
&'a self,
event_it: impl Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a,
) -> impl Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a {
match self.transform {
Some(transform) => TransformedLyonPathIterator { it: event_it, transform },
None => event_it,
}
}
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub enum PathData {
None,
Elements(crate::SharedArray<PathElement>),
Events(crate::SharedArray<PathEvent>, crate::SharedArray<Point>),
}
impl Default for PathData {
fn default() -> Self {
Self::None
}
}
impl PathData {
pub fn iter(&self) -> PathDataIterator {
PathDataIterator {
it: match self {
PathData::None => LyonPathIteratorVariant::FromPath(lyon::path::Path::new()),
PathData::Elements(elements) => LyonPathIteratorVariant::FromPath(
PathData::build_path(elements.as_slice().iter()),
),
PathData::Events(events, coordinates) => {
LyonPathIteratorVariant::FromEvents(events, coordinates)
}
},
transform: None,
}
}
pub fn iter_fitted(&self, width: f32, height: f32) -> PathDataIterator {
let mut it = self.iter();
it.fit(width, height);
it
}
fn build_path(element_it: std::slice::Iter<PathElement>) -> lyon::path::Path {
use lyon::geom::SvgArc;
use lyon::math::{Angle, Point, Vector};
use lyon::path::{
builder::{Build, FlatPathBuilder, SvgBuilder},
ArcFlags,
};
let mut path_builder = lyon::path::Path::builder().with_svg();
for element in element_it {
match element {
PathElement::LineTo(PathLineTo { x, y }) => {
path_builder.line_to(Point::new(*x, *y))
}
PathElement::ArcTo(PathArcTo {
x,
y,
radius_x,
radius_y,
x_rotation,
large_arc,
sweep,
}) => {
let radii = Vector::new(*radius_x, *radius_y);
let x_rotation = Angle::degrees(*x_rotation);
let flags = ArcFlags { large_arc: *large_arc, sweep: *sweep };
let to = Point::new(*x, *y);
let svg_arc = SvgArc {
from: path_builder.current_position(),
radii,
x_rotation,
flags,
to,
};
if svg_arc.is_straight_line() {
path_builder.line_to(to);
} else {
path_builder.arc_to(radii, x_rotation, flags, to)
}
}
PathElement::Close => path_builder.close(),
}
}
path_builder.build()
}
}
pub(crate) mod ffi {
#![allow(unsafe_code)]
use super::*;
#[allow(non_camel_case_types)]
type c_void = ();
#[cfg(cbindgen)]
#[repr(C)]
struct Rect {
x: f32,
y: f32,
width: f32,
height: f32,
}
#[cfg(cbindgen)]
#[repr(C)]
struct IntRect {
x: i32,
y: i32,
width: i32,
height: i32,
}
#[cfg(cbindgen)]
#[repr(C)]
struct Point {
x: f32,
y: f32,
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_new_path_elements(
out: *mut c_void,
first_element: *const PathElement,
count: usize,
) {
let arr = crate::SharedArray::from(std::slice::from_raw_parts(first_element, count));
core::ptr::write(out as *mut crate::SharedArray<PathElement>, arr.clone());
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_new_path_events(
out_events: *mut c_void,
out_coordinates: *mut c_void,
first_event: *const PathEvent,
event_count: usize,
first_coordinate: *const Point,
coordinate_count: usize,
) {
let events = crate::SharedArray::from(std::slice::from_raw_parts(first_event, event_count));
core::ptr::write(out_events as *mut crate::SharedArray<PathEvent>, events.clone());
let coordinates = crate::SharedArray::from(std::slice::from_raw_parts(
first_coordinate,
coordinate_count,
));
core::ptr::write(out_coordinates as *mut crate::SharedArray<Point>, coordinates.clone());
}
}
#[derive(FieldOffsets)]
#[repr(C)]
#[pin]
struct TextCursorBlinker {
cursor_visible: Property<bool>,
cursor_blink_timer: crate::timers::Timer,
}
impl TextCursorBlinker {
fn new() -> Pin<Rc<Self>> {
Rc::pin(Self {
cursor_visible: Property::new(true),
cursor_blink_timer: Default::default(),
})
}
fn set_binding(instance: Pin<Rc<TextCursorBlinker>>, prop: &crate::properties::Property<bool>) {
instance.as_ref().cursor_visible.set(true);
Self::start(&instance);
prop.set_binding(move || {
TextCursorBlinker::FIELD_OFFSETS.cursor_visible.apply_pin(instance.as_ref()).get()
});
}
fn start(self: &Pin<Rc<Self>>) {
if self.cursor_blink_timer.running() {
self.cursor_blink_timer.restart();
} else {
let toggle_cursor = {
let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
move || {
if let Some(blinker) = weak_blinker.upgrade() {
let visible = TextCursorBlinker::FIELD_OFFSETS
.cursor_visible
.apply_pin(blinker.as_ref())
.get();
blinker.cursor_visible.set(!visible);
}
}
};
self.cursor_blink_timer.start(
crate::timers::TimerMode::Repeated,
std::time::Duration::from_millis(500),
toggle_cursor,
);
}
}
fn stop(&self) {
self.cursor_blink_timer.stop()
}
}