use std::{
ops,
sync::{atomic::AtomicUsize, Arc},
};
use serde::{Deserialize, Serialize};
use crate::{
api_extension::{ApiExtensionId, ApiExtensionPayload},
font::{FontId, GlyphInstance, GlyphOptions},
image::ImageTextureId,
window::FrameId,
AlphaType, BorderSide, ExtendMode, GradientStop, ImageRendering, LineOrientation, LineStyle, MixBlendMode, ReferenceFrameId,
RepeatMode, TransformStyle,
};
use zng_unit::*;
#[derive(Debug)]
pub struct DisplayListBuilder {
frame_id: FrameId,
list: Vec<DisplayItem>,
clip_len: usize,
mask_len: usize,
space_len: usize,
stacking_len: usize,
seg_id: SegmentId,
seg_id_gen: Arc<AtomicUsize>,
segments: Vec<(SegmentId, usize)>,
has_reuse_ranges: bool,
}
impl DisplayListBuilder {
pub fn new(frame_id: FrameId) -> Self {
Self::with_capacity(frame_id, 100)
}
pub fn with_capacity(frame_id: FrameId, capacity: usize) -> Self {
Self {
frame_id,
list: Vec::with_capacity(capacity),
clip_len: 1,
mask_len: 1,
space_len: 1,
stacking_len: 1,
seg_id: 0,
seg_id_gen: Arc::new(AtomicUsize::new(1)),
segments: vec![(0, 0)],
has_reuse_ranges: false,
}
}
pub fn frame_id(&self) -> FrameId {
self.frame_id
}
pub fn start_reuse_range(&mut self) -> ReuseStart {
ReuseStart {
frame_id: self.frame_id,
seg_id: self.seg_id,
start: self.list.len(),
clip_len: self.clip_len,
mask_len: self.mask_len,
space_len: self.space_len,
stacking_len: self.stacking_len,
}
}
pub fn finish_reuse_range(&mut self, start: ReuseStart) -> ReuseRange {
assert_eq!(self.frame_id, start.frame_id, "reuse range not started by the same builder");
assert_eq!(self.seg_id, start.seg_id, "reuse range not started by the same builder");
assert_eq!(
self.clip_len, start.clip_len,
"reuse range cannot finish before all clips pushed inside it are popped"
);
assert_eq!(
self.mask_len, start.mask_len,
"reuse range cannot finish before all masks pushed inside it are popped"
);
assert_eq!(
self.space_len, start.space_len,
"reuse range cannot finish before all reference frames pushed inside it are popped"
);
assert_eq!(
self.stacking_len, start.stacking_len,
"reuse range cannot finish before all stacking contexts pushed inside it are popped"
);
debug_assert!(start.start <= self.list.len());
self.has_reuse_ranges = true;
ReuseRange {
frame_id: self.frame_id,
seg_id: self.seg_id,
start: start.start,
end: self.list.len(),
}
}
pub fn push_reuse_range(&mut self, range: &ReuseRange) {
if !range.is_empty() {
self.list.push(DisplayItem::Reuse {
frame_id: range.frame_id,
seg_id: range.seg_id,
start: range.start,
end: range.end,
});
}
}
pub fn push_reference_frame(
&mut self,
key: ReferenceFrameId,
transform: FrameValue<PxTransform>,
transform_style: TransformStyle,
is_2d_scale_translation: bool,
) {
self.space_len += 1;
self.list.push(DisplayItem::PushReferenceFrame {
id: key,
transform,
transform_style,
is_2d_scale_translation,
});
}
pub fn pop_reference_frame(&mut self) {
debug_assert!(self.space_len > 1);
self.space_len -= 1;
self.list.push(DisplayItem::PopReferenceFrame);
}
pub fn push_stacking_context(&mut self, blend_mode: MixBlendMode, transform_style: TransformStyle, filters: &[FilterOp]) {
self.stacking_len += 1;
self.list.push(DisplayItem::PushStackingContext {
blend_mode,
transform_style,
filters: filters.to_vec().into_boxed_slice(),
})
}
pub fn pop_stacking_context(&mut self) {
debug_assert!(self.stacking_len > 1);
self.stacking_len -= 1;
self.list.push(DisplayItem::PopStackingContext);
}
pub fn push_clip_rect(&mut self, clip_rect: PxRect, clip_out: bool) {
self.clip_len += 1;
self.list.push(DisplayItem::PushClipRect { clip_rect, clip_out });
}
pub fn push_clip_rounded_rect(&mut self, clip_rect: PxRect, corners: PxCornerRadius, clip_out: bool) {
self.clip_len += 1;
self.list.push(DisplayItem::PushClipRoundedRect {
clip_rect,
corners,
clip_out,
});
}
pub fn pop_clip(&mut self) {
debug_assert!(self.clip_len > 1);
self.clip_len -= 1;
self.list.push(DisplayItem::PopClip);
}
pub fn push_mask(&mut self, image_id: ImageTextureId, rect: PxRect) {
self.mask_len += 1;
self.list.push(DisplayItem::PushMask { image_id, rect })
}
pub fn pop_mask(&mut self) {
debug_assert!(self.mask_len > 1);
self.mask_len -= 1;
self.list.push(DisplayItem::PopMask);
}
#[allow(clippy::too_many_arguments)]
pub fn push_border(
&mut self,
bounds: PxRect,
widths: PxSideOffsets,
top: BorderSide,
right: BorderSide,
bottom: BorderSide,
left: BorderSide,
radius: PxCornerRadius,
) {
self.list.push(DisplayItem::Border {
bounds,
widths,
sides: [top, right, bottom, left],
radius,
})
}
#[allow(clippy::too_many_arguments)]
pub fn push_nine_patch_border(
&mut self,
bounds: PxRect,
source: NinePatchSource,
widths: PxSideOffsets,
fill: bool,
repeat_horizontal: RepeatMode,
repeat_vertical: RepeatMode,
) {
self.list.push(DisplayItem::NinePatchBorder {
bounds,
source,
widths,
fill,
repeat_horizontal,
repeat_vertical,
})
}
pub fn push_text(
&mut self,
clip_rect: PxRect,
font_id: FontId,
glyphs: &[GlyphInstance],
color: FrameValue<Rgba>,
options: GlyphOptions,
) {
self.list.push(DisplayItem::Text {
clip_rect,
font_id,
glyphs: glyphs.to_vec().into_boxed_slice(),
color,
options,
});
}
#[allow(clippy::too_many_arguments)]
pub fn push_image(
&mut self,
clip_rect: PxRect,
image_id: ImageTextureId,
image_size: PxSize,
tile_size: PxSize,
tile_spacing: PxSize,
rendering: ImageRendering,
alpha_type: AlphaType,
) {
self.list.push(DisplayItem::Image {
clip_rect,
image_id,
image_size,
rendering,
alpha_type,
tile_size,
tile_spacing,
})
}
pub fn push_color(&mut self, clip_rect: PxRect, color: FrameValue<Rgba>) {
self.list.push(DisplayItem::Color { clip_rect, color })
}
pub fn push_backdrop_filter(&mut self, clip_rect: PxRect, filters: &[FilterOp]) {
self.list.push(DisplayItem::BackdropFilter {
clip_rect,
filters: filters.to_vec().into_boxed_slice(),
})
}
#[allow(clippy::too_many_arguments)]
pub fn push_linear_gradient(
&mut self,
clip_rect: PxRect,
start_point: euclid::Point2D<f32, Px>,
end_point: euclid::Point2D<f32, Px>,
extend_mode: ExtendMode,
stops: &[GradientStop],
tile_origin: PxPoint,
tile_size: PxSize,
tile_spacing: PxSize,
) {
self.list.push(DisplayItem::LinearGradient {
clip_rect,
start_point,
end_point,
extend_mode,
stops: stops.to_vec().into_boxed_slice(),
tile_origin,
tile_size,
tile_spacing,
})
}
#[allow(clippy::too_many_arguments)]
pub fn push_radial_gradient(
&mut self,
clip_rect: PxRect,
center: euclid::Point2D<f32, Px>,
radius: euclid::Size2D<f32, Px>,
start_offset: f32,
end_offset: f32,
extend_mode: ExtendMode,
stops: &[GradientStop],
tile_origin: PxPoint,
tile_size: PxSize,
tile_spacing: PxSize,
) {
self.list.push(DisplayItem::RadialGradient {
clip_rect,
center,
radius,
start_offset,
end_offset,
extend_mode,
stops: stops.to_vec().into_boxed_slice(),
tile_origin,
tile_size,
tile_spacing,
});
}
#[allow(clippy::too_many_arguments)]
pub fn push_conic_gradient(
&mut self,
clip_rect: PxRect,
center: euclid::Point2D<f32, Px>,
angle: AngleRadian,
start_offset: f32,
end_offset: f32,
extend_mode: ExtendMode,
stops: &[GradientStop],
tile_origin: PxPoint,
tile_size: PxSize,
tile_spacing: PxSize,
) {
self.list.push(DisplayItem::ConicGradient {
clip_rect,
center,
angle,
start_offset,
end_offset,
extend_mode,
stops: stops.to_vec().into_boxed_slice(),
tile_origin,
tile_size,
tile_spacing,
});
}
pub fn push_line(&mut self, clip_rect: PxRect, color: Rgba, style: LineStyle, orientation: LineOrientation) {
self.list.push(DisplayItem::Line {
clip_rect,
color,
style,
orientation,
})
}
pub fn push_extension(&mut self, extension_id: ApiExtensionId, payload: ApiExtensionPayload) {
self.list.push(DisplayItem::PushExtension { extension_id, payload })
}
pub fn pop_extension(&mut self, extension_id: ApiExtensionId) {
self.list.push(DisplayItem::PopExtension { extension_id })
}
pub fn set_backface_visibility(&mut self, visible: bool) {
self.list.push(DisplayItem::SetBackfaceVisibility { visible })
}
pub fn len(&self) -> usize {
self.list.len()
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
pub fn parallel_split(&self) -> Self {
Self {
frame_id: self.frame_id,
list: vec![],
clip_len: 1,
mask_len: 1,
space_len: 1,
stacking_len: 1,
seg_id: self.seg_id_gen.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
seg_id_gen: self.seg_id_gen.clone(),
segments: vec![],
has_reuse_ranges: false,
}
}
pub fn parallel_fold(&mut self, mut split: Self) {
assert!(
Arc::ptr_eq(&self.seg_id_gen, &split.seg_id_gen),
"cannot fold list not split from this one or parent"
);
assert_eq!(split.space_len, 1);
assert_eq!(split.clip_len, 1);
assert_eq!(split.mask_len, 1);
assert_eq!(split.stacking_len, 1);
if !self.list.is_empty() {
for (_, offset) in &mut split.segments {
*offset += self.list.len();
}
}
if self.segments.is_empty() {
self.segments = split.segments;
} else {
self.segments.append(&mut split.segments);
}
if split.has_reuse_ranges {
self.segments.push((split.seg_id, self.list.len()));
}
if self.list.is_empty() {
self.list = split.list;
} else {
self.list.append(&mut split.list);
}
}
pub fn finalize(self) -> DisplayList {
DisplayList {
frame_id: self.frame_id,
list: self.list,
segments: self.segments,
}
}
}
pub type SegmentId = usize;
pub struct ReuseStart {
frame_id: FrameId,
seg_id: SegmentId,
start: usize,
clip_len: usize,
mask_len: usize,
space_len: usize,
stacking_len: usize,
}
#[derive(Debug, Clone)]
pub struct ReuseRange {
frame_id: FrameId,
seg_id: SegmentId,
start: usize,
end: usize,
}
impl ReuseRange {
pub fn frame_id(&self) -> FrameId {
self.frame_id
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DisplayList {
frame_id: FrameId,
list: Vec<DisplayItem>,
segments: Vec<(SegmentId, usize)>,
}
impl DisplayList {
pub fn frame_id(&self) -> FrameId {
self.frame_id
}
pub fn into_parts(self) -> (FrameId, Vec<DisplayItem>, Vec<(SegmentId, usize)>) {
(self.frame_id, self.list, self.segments)
}
}
impl ops::Deref for DisplayList {
type Target = [DisplayItem];
fn deref(&self) -> &Self::Target {
&self.list
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct FrameValueId(u64);
impl FrameValueId {
pub const INVALID: Self = Self(0);
pub const fn first() -> Self {
Self(1)
}
#[must_use]
pub const fn next(self) -> Self {
let r = Self(self.0.wrapping_add(1));
if r.0 == Self::INVALID.0 {
Self::first()
} else {
r
}
}
#[must_use]
pub fn incr(&mut self) -> Self {
std::mem::replace(self, self.next())
}
pub const fn get(self) -> u64 {
self.0
}
pub const fn from_raw(id: u64) -> Self {
Self(id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FrameValue<T> {
Bind {
id: FrameValueId,
value: T,
animating: bool,
},
Value(T),
}
impl<T> FrameValue<T> {
pub fn value(&self) -> &T {
match self {
FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
}
}
pub fn into_value(self) -> T {
match self {
FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
}
}
fn update_bindable(value: &mut T, animating: &mut bool, update: &FrameValueUpdate<T>) -> bool
where
T: PartialEq + Copy,
{
let need_frame = (*animating != update.animating) || (!*animating && *value != update.value);
*animating = update.animating;
*value = update.value;
need_frame
}
fn update_value(value: &mut T, update: &FrameValueUpdate<T>) -> bool
where
T: PartialEq + Copy,
{
if value != &update.value {
*value = update.value;
true
} else {
false
}
}
}
impl<T> From<T> for FrameValue<T> {
fn from(value: T) -> Self {
FrameValue::Value(value)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct FrameValueUpdate<T> {
pub id: FrameValueId,
pub value: T,
pub animating: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FilterOp {
Blur(f32, f32),
Brightness(f32),
Contrast(f32),
Grayscale(f32),
HueRotate(f32),
Invert(f32),
Opacity(FrameValue<f32>),
Saturate(f32),
Sepia(f32),
DropShadow {
offset: euclid::Vector2D<f32, Px>,
color: Rgba,
blur_radius: f32,
},
ColorMatrix([f32; 20]),
Flood(Rgba),
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DisplayItem {
Reuse {
frame_id: FrameId,
seg_id: SegmentId,
start: usize,
end: usize,
},
PushReferenceFrame {
id: ReferenceFrameId,
transform: FrameValue<PxTransform>,
transform_style: TransformStyle,
is_2d_scale_translation: bool,
},
PopReferenceFrame,
PushStackingContext {
transform_style: TransformStyle,
blend_mode: MixBlendMode,
filters: Box<[FilterOp]>,
},
PopStackingContext,
PushClipRect {
clip_rect: PxRect,
clip_out: bool,
},
PushClipRoundedRect {
clip_rect: PxRect,
corners: PxCornerRadius,
clip_out: bool,
},
PopClip,
PushMask {
image_id: ImageTextureId,
rect: PxRect,
},
PopMask,
Border {
bounds: PxRect,
widths: PxSideOffsets,
sides: [BorderSide; 4],
radius: PxCornerRadius,
},
NinePatchBorder {
bounds: PxRect,
source: NinePatchSource,
widths: PxSideOffsets,
fill: bool,
repeat_horizontal: RepeatMode,
repeat_vertical: RepeatMode,
},
Text {
clip_rect: PxRect,
font_id: FontId,
glyphs: Box<[GlyphInstance]>,
color: FrameValue<Rgba>,
options: GlyphOptions,
},
Image {
clip_rect: PxRect,
image_id: ImageTextureId,
image_size: PxSize,
rendering: ImageRendering,
alpha_type: AlphaType,
tile_size: PxSize,
tile_spacing: PxSize,
},
Color {
clip_rect: PxRect,
color: FrameValue<Rgba>,
},
BackdropFilter {
clip_rect: PxRect,
filters: Box<[FilterOp]>,
},
LinearGradient {
clip_rect: PxRect,
start_point: euclid::Point2D<f32, Px>,
end_point: euclid::Point2D<f32, Px>,
extend_mode: ExtendMode,
stops: Box<[GradientStop]>,
tile_origin: PxPoint,
tile_size: PxSize,
tile_spacing: PxSize,
},
RadialGradient {
clip_rect: PxRect,
center: euclid::Point2D<f32, Px>,
radius: euclid::Size2D<f32, Px>,
start_offset: f32,
end_offset: f32,
extend_mode: ExtendMode,
stops: Box<[GradientStop]>,
tile_origin: PxPoint,
tile_size: PxSize,
tile_spacing: PxSize,
},
ConicGradient {
clip_rect: PxRect,
center: euclid::Point2D<f32, Px>,
angle: AngleRadian,
start_offset: f32,
end_offset: f32,
extend_mode: ExtendMode,
stops: Box<[GradientStop]>,
tile_origin: PxPoint,
tile_size: PxSize,
tile_spacing: PxSize,
},
Line {
clip_rect: PxRect,
color: Rgba,
style: LineStyle,
orientation: LineOrientation,
},
PushExtension {
extension_id: ApiExtensionId,
payload: ApiExtensionPayload,
},
PopExtension {
extension_id: ApiExtensionId,
},
SetBackfaceVisibility {
visible: bool,
},
}
impl DisplayItem {
pub fn update_transform(&mut self, t: &FrameValueUpdate<PxTransform>) -> bool {
match self {
DisplayItem::PushReferenceFrame {
transform:
FrameValue::Bind {
id,
value,
animating: animation,
},
..
} if *id == t.id => FrameValue::update_bindable(value, animation, t),
_ => false,
}
}
pub fn update_float(&mut self, t: &FrameValueUpdate<f32>) -> bool {
match self {
DisplayItem::PushStackingContext { filters, .. } => {
let mut new_frame = false;
for filter in filters.iter_mut() {
match filter {
FilterOp::Opacity(FrameValue::Bind {
id,
value,
animating: animation,
}) if *id == t.id => {
new_frame |= FrameValue::update_bindable(value, animation, t);
}
_ => {}
}
}
new_frame
}
_ => false,
}
}
pub fn update_color(&mut self, t: &FrameValueUpdate<Rgba>) -> bool {
match self {
DisplayItem::Color {
color:
FrameValue::Bind {
id,
value,
animating: animation,
},
..
} if *id == t.id => FrameValue::update_bindable(value, animation, t),
DisplayItem::Text {
color: FrameValue::Bind { id, value, .. },
..
} if *id == t.id => FrameValue::update_value(value, t),
_ => false,
}
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NinePatchSource {
Image {
image_id: ImageTextureId,
rendering: ImageRendering,
},
LinearGradient {
start_point: euclid::Point2D<f32, Px>,
end_point: euclid::Point2D<f32, Px>,
extend_mode: ExtendMode,
stops: Box<[GradientStop]>,
},
RadialGradient {
center: euclid::Point2D<f32, Px>,
radius: euclid::Size2D<f32, Px>,
start_offset: f32,
end_offset: f32,
extend_mode: ExtendMode,
stops: Box<[GradientStop]>,
},
ConicGradient {
center: euclid::Point2D<f32, Px>,
angle: AngleRadian,
start_offset: f32,
end_offset: f32,
extend_mode: ExtendMode,
stops: Box<[GradientStop]>,
},
}