slint/sixtyfps_runtime/corelib/graphics.rs

764 lines
24 KiB
Rust

extern crate alloc;
use crate::input::{MouseEvent, MouseEventType};
use crate::properties::InterpolatedPropertyValue;
#[cfg(feature = "rtti")]
use crate::rtti::{BuiltinItem, FieldInfo, FieldOffset, PropertyInfo, ValueType};
use cgmath::Matrix4;
use const_field_offset::FieldOffsets;
use sixtyfps_corelib_macros::*;
use std::cell::RefCell;
use std::rc::Rc;
/// 2D Rectangle
pub type Rect = euclid::default::Rect<f32>;
/// 2D Point
pub type Point = euclid::default::Point2D<f32>;
/// 2D Size
pub type Size = euclid::default::Size2D<f32>;
/// RGBA color
#[derive(Copy, Clone, PartialEq, Debug, Default)]
#[repr(C)]
pub struct Color {
red: u8,
green: u8,
blue: u8,
alpha: u8,
}
impl Color {
/// Construct a color from an integer encoded as `0xAARRGGBB`
pub const fn from_argb_encoded(encoded: u32) -> Color {
Color {
red: (encoded >> 16) as u8,
green: (encoded >> 8) as u8,
blue: encoded as u8,
alpha: (encoded >> 24) as u8,
}
}
/// Construct a color from its RGBA components as u8
pub const fn from_rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
Color { red, green, blue, alpha }
}
/// Construct a color from its RGB components as u8
pub const fn from_rgb(red: u8, green: u8, blue: u8) -> Color {
Color::from_rgba(red, green, blue, 0xff)
}
/// Returns `(red, green, blue, alpha)` encoded as f32
pub fn as_rgba_f32(&self) -> (f32, f32, f32, f32) {
(
(self.red as f32) / 255.0,
(self.green as f32) / 255.0,
(self.blue as f32) / 255.0,
(self.alpha as f32) / 255.0,
)
}
/// Returns `(red, green, blue, alpha)` encoded as u8
pub fn as_rgba_u8(&self) -> (u8, u8, u8, u8) {
(self.red, self.green, self.blue, self.alpha)
}
/// Returns `(alpha, red, green, blue)` encoded as u32
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)
}
/// A constant for the black color
pub const BLACK: Color = Color::from_rgb(0, 0, 0);
/// A constant for the white color
pub const WHITE: Color = Color::from_rgb(255, 255, 255);
/// A constant for the transparent color
pub const TRANSPARENT: Color = Color::from_rgba(0, 0, 0, 0);
}
impl From<u32> for Color {
fn from(encoded: u32) -> Self {
Color::from_argb_encoded(encoded)
}
}
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)
}
}
pub enum FillStyle {
SolidColor(Color),
}
/// A resource is a reference to binary data, for example images. They can be accessible on the file
/// system or embedded in the resulting binary. Or they might be URLs to a web server and a downloaded
/// is necessary before they can be used.
#[derive(Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum Resource {
/// A resource that does not represent any data.
None,
/// A resource that points to a file in the file system
AbsoluteFilePath(crate::SharedString),
/// A resource that is embedded in the program and accessible via pointer
/// The format is the same as in a file
EmbeddedData(super::slice::Slice<'static, u8>),
/// Raw ARGB
#[allow(missing_docs)]
EmbeddedRgbaImage { width: u32, height: u32, data: super::sharedarray::SharedArray<u8> },
}
impl Default for Resource {
fn default() -> Self {
Resource::None
}
}
/// Each item return a RenderingPrimitive to the backend with information about what to draw.
#[derive(PartialEq, Debug)]
#[repr(C)]
#[allow(missing_docs)]
pub enum HighLevelRenderingPrimitive {
/// There is nothing to draw
NoContents,
Rectangle {
x: f32,
y: f32,
width: f32,
height: f32,
color: Color,
},
BorderRectangle {
x: f32,
y: f32,
width: f32,
height: f32,
color: Color,
border_width: f32,
border_radius: f32,
border_color: Color,
},
Image {
x: f32,
y: f32,
source: crate::Resource,
},
Text {
x: f32,
y: f32,
text: crate::SharedString,
font_family: crate::SharedString,
font_pixel_size: f32,
color: Color,
},
Path {
x: f32,
y: f32,
width: f32,
height: f32,
elements: crate::PathData,
fill_color: Color,
stroke_color: Color,
stroke_width: f32,
},
}
pub trait HasRenderingPrimitive {
fn primitive(&self) -> &HighLevelRenderingPrimitive;
}
pub trait Frame {
type LowLevelRenderingPrimitive: HasRenderingPrimitive;
fn render_primitive(
&mut self,
primitive: &Self::LowLevelRenderingPrimitive,
transform: &Matrix4<f32>,
);
}
pub trait RenderingPrimitivesBuilder {
type LowLevelRenderingPrimitive: HasRenderingPrimitive;
fn create(
&mut self,
primitive: HighLevelRenderingPrimitive,
) -> Self::LowLevelRenderingPrimitive;
}
pub trait GraphicsBackend: Sized {
type LowLevelRenderingPrimitive: HasRenderingPrimitive;
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;
}
enum RenderingCacheEntry<RenderingPrimitive> {
AllocateEntry(RenderingPrimitive),
FreeEntry(Option<usize>), // contains next free index if exists
}
pub struct RenderingCache<Backend: GraphicsBackend> {
nodes: Vec<RenderingCacheEntry<Backend::LowLevelRenderingPrimitive>>,
next_free: Option<usize>,
len: usize,
}
impl<Backend: GraphicsBackend> Default for RenderingCache<Backend> {
fn default() -> Self {
Self { nodes: vec![], next_free: None, len: 0 }
}
}
impl<Backend: GraphicsBackend> RenderingCache<Backend> {
pub fn allocate_entry(&mut self, content: Backend::LowLevelRenderingPrimitive) -> usize {
let idx = {
if let Some(free_idx) = self.next_free {
let node = &mut self.nodes[free_idx];
if let RenderingCacheEntry::FreeEntry(next_free) = node {
self.next_free = *next_free;
} else {
unreachable!();
}
*node = RenderingCacheEntry::AllocateEntry(content);
free_idx
} else {
self.nodes.push(RenderingCacheEntry::AllocateEntry(content));
self.nodes.len() - 1
}
};
self.len = self.len + 1;
idx
}
pub fn entry_at(&self, idx: usize) -> &Backend::LowLevelRenderingPrimitive {
match self.nodes[idx] {
RenderingCacheEntry::AllocateEntry(ref data) => return data,
_ => unreachable!(),
}
}
pub fn set_entry_at(&mut self, idx: usize, primitive: Backend::LowLevelRenderingPrimitive) {
match self.nodes[idx] {
RenderingCacheEntry::AllocateEntry(ref mut data) => *data = primitive,
_ => unreachable!(),
}
}
pub fn free_entry(&mut self, idx: usize) {
self.len = self.len - 1;
self.nodes[idx] = RenderingCacheEntry::FreeEntry(self.next_free);
self.next_free = Some(idx);
}
pub fn len(&self) -> usize {
self.len
}
}
pub struct GraphicsWindow<Backend: GraphicsBackend + 'static> {
graphics_backend_factory:
Box<dyn Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend>,
graphics_backend: Option<Backend>,
rendering_cache: RenderingCache<Backend>,
}
impl<Backend: GraphicsBackend + 'static> GraphicsWindow<Backend> {
pub fn new(
graphics_backend_factory: impl Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend
+ 'static,
) -> Rc<RefCell<Self>> {
let this = Rc::new(RefCell::new(Self {
graphics_backend_factory: Box::new(graphics_backend_factory),
graphics_backend: None,
rendering_cache: RenderingCache::default(),
}));
this
}
pub fn id(&self) -> Option<winit::window::WindowId> {
self.graphics_backend.as_ref().map(|backend| backend.window().id())
}
}
impl<Backend: GraphicsBackend> Drop for GraphicsWindow<Backend> {
fn drop(&mut self) {
if let Some(backend) = self.graphics_backend.as_ref() {
crate::eventloop::unregister_window(backend.window().id());
}
}
}
impl<Backend: GraphicsBackend> crate::eventloop::GenericWindow
for RefCell<GraphicsWindow<Backend>>
{
fn draw(&self, component: crate::ComponentRefPin) {
let mut this = self.borrow_mut();
{
let mut rendering_primitives_builder =
this.graphics_backend.as_mut().unwrap().new_rendering_primitives_builder();
// Generate cached rendering data once
crate::item_tree::visit_items(
component,
|_, item, _| {
crate::item_rendering::update_item_rendering_data(
item,
&mut this.rendering_cache,
&mut rendering_primitives_builder,
);
},
(),
);
this.graphics_backend.as_mut().unwrap().finish_primitives(rendering_primitives_builder);
}
let window = this.graphics_backend.as_ref().unwrap().window();
let size = window.inner_size();
let mut frame = this.graphics_backend.as_mut().unwrap().new_frame(
size.width,
size.height,
&Color::WHITE,
);
crate::item_rendering::render_component_items(
component,
&mut frame,
&mut this.rendering_cache,
);
this.graphics_backend.as_mut().unwrap().present_frame(frame);
}
fn process_mouse_input(
&self,
pos: winit::dpi::PhysicalPosition<f64>,
what: MouseEventType,
component: crate::ComponentRefPin,
) {
crate::input::process_mouse_event(
component,
MouseEvent { pos: euclid::point2(pos.x as _, pos.y as _), what },
);
}
fn window_handle(&self) -> std::cell::Ref<winit::window::Window> {
std::cell::Ref::map(self.borrow(), |mw| mw.graphics_backend.as_ref().unwrap().window())
}
fn map_window(self: Rc<Self>, event_loop: &crate::eventloop::EventLoop) {
if self.borrow().graphics_backend.is_some() {
return;
}
let id = {
let window_builder = winit::window::WindowBuilder::new();
let mut this = self.borrow_mut();
let factory = this.graphics_backend_factory.as_mut();
let backend = factory(&event_loop, window_builder);
let window_id = backend.window().id();
this.graphics_backend = Some(backend);
window_id
};
crate::eventloop::register_window(
id,
self.clone() as Rc<dyn crate::eventloop::GenericWindow>,
);
}
fn request_redraw(&self) {
if let Some(backend) = self.borrow().graphics_backend.as_ref() {
backend.window().request_redraw();
}
}
fn size(&self) -> Size {
if let Some(backend) = self.borrow().graphics_backend.as_ref() {
let size = backend.window().inner_size();
Size::new(size.width as _, size.height as _)
} else {
Size::default()
}
}
}
#[repr(C)]
#[derive(FieldOffsets, Default, BuiltinItem, Clone, Debug, PartialEq)]
#[pin]
/// PathLineTo describes the event of moving the cursor on the path to the specified location
/// along a straight line.
pub struct PathLineTo {
#[rtti_field]
/// The x coordinate where the line should go to.
pub x: f32,
#[rtti_field]
/// The y coordinate where the line should go to.
pub y: f32,
}
#[repr(C)]
#[derive(FieldOffsets, Default, BuiltinItem, Clone, Debug, PartialEq)]
#[pin]
/// PathArcTo describes the event of moving the cursor on the path across an arc to the specified
/// x/y coordinates, with the specified x/y radius and additional properties.
pub struct PathArcTo {
#[rtti_field]
/// The x coordinate where the arc should end up.
pub x: f32,
#[rtti_field]
/// The y coordinate where the arc should end up.
pub y: f32,
#[rtti_field]
/// The radius on the x-axis of the arc.
pub radius_x: f32,
#[rtti_field]
/// The radius on the y-axis of the arc.
pub radius_y: f32,
#[rtti_field]
/// The rotation along the x-axis of the arc in degress.
pub x_rotation: f32,
#[rtti_field]
/// large_arc indicates whether to take the long or the shorter path to complete the arc.
pub large_arc: bool,
#[rtti_field]
/// sweep indicates the direction of the arc. If true, a clockwise direction is chosen,
/// otherwise counter-clockwise.
pub sweep: bool,
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
/// PathElement describes a single element on a path, such as move-to, line-to, etc.
pub enum PathElement {
/// The LineTo variant describes a line.
LineTo(PathLineTo),
/// The PathArcTo variant describes an arc.
ArcTo(PathArcTo),
/// Indicates that the path should be closed now by connecting to the starting point.
Close,
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
/// PathEvent is a low-level data structure describing the composition of a path. Typically it is
/// generated at compile time from a higher-level description, such as SVG commands.
pub enum PathEvent {
/// The beginning of the path.
Begin,
/// A straight line on the path.
Line,
/// A quadratic bezier curve on the path.
Quadratic,
/// A cubic bezier curve on the path.
Cubic,
/// The end of the path that remains open.
EndOpen,
/// The end of a path that is closed.
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>
{
}
/// PathDataIterator is a data structure that acts as starting point for iterating
/// through the low-level events of a path. If the path was constructed from said
/// events, then it is a very thin abstraction. If the path was created from higher-level
/// elements, then an intermediate lyon path is required/built.
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> {
/// Create a new iterator for path traversal.
pub fn iter(
&'a self,
) -> Box<dyn 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) => {
Box::new(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,
));
}
}
fn apply_transform(
&'a self,
event_it: impl Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a,
) -> Box<dyn Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a>
{
match self.transform {
Some(transform) => Box::new(TransformedLyonPathIterator { it: event_it, transform }),
None => Box::new(event_it),
}
}
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
/// PathData represents a path described by either high-level elements or low-level
/// events and coordinates.
pub enum PathData {
/// None is the variant when the path is empty.
None,
/// The Elements variant is used to make a Path from shared arrays of elements.
Elements(crate::SharedArray<PathElement>),
/// The Events variant describes the path as a series of low-level events and
/// associated coordinates.
Events(crate::SharedArray<PathEvent>, crate::SharedArray<Point>),
}
impl Default for PathData {
fn default() -> Self {
Self::None
}
}
impl PathData {
/// This function returns an iterator that allows traversing the path by means of lyon events.
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,
}
}
/// This function returns an iterator that allows traversing the path by means of lyon events.
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 = ();
/// Expand Rect so that cbindgen can see it. ( is in fact euclid::default::Rect<f32>)
#[cfg(cbindgen)]
#[repr(C)]
struct Rect {
x: f32,
y: f32,
width: f32,
height: f32,
}
/// Expand Point so that cbindgen can see it. ( is in fact euclid::default::PointD2<f32>)
#[cfg(cbindgen)]
#[repr(C)]
struct Point {
x: f32,
y: f32,
}
#[no_mangle]
/// This function is used for the low-level C++ interface to allocate the backing vector for a shared path element array.
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]
/// This function is used for the low-level C++ interface to allocate the backing vector for a shared path event array.
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());
}
}