///|
pub(all) enum DamageRegion {
Empty
FullSurface(String)
Rects(Array[Rect])
} derive(Eq, Debug, ToJson)
///|
/// Renderer-facing frame payload. `clear_color` owns frame initialization;
/// `commands` contains view content and does not need a leading `Clear`.
/// Adapters for legacy command-only renderers must materialize that clear when
/// lowering a frame back to an `Array[DrawCommand]`.
pub(all) struct DrawFrame {
commands : Array[DrawCommand]
platform_views : Array[PlatformViewPlacement]
/// Bounds of the runtime's view-level overlay paint, when present. Native
/// platform views may use this to expose the already-rendered overlay pixels
/// above their own surface without introducing a second renderer.
overlay_bounds : Rect?
damage : DamageRegion
clear_color : Color
} derive(Eq, Debug, ToJson)
///|
fn damage_region_max_rects() -> Int {
8
}
///|
fn damage_region_full_threshold() -> Double {
0.6
}
///|
pub fn DamageRegion::empty() -> DamageRegion {
DamageRegion::Empty
}
///|
pub fn DamageRegion::full(reason : String) -> DamageRegion {
DamageRegion::FullSurface(reason)
}
///|
pub fn DamageRegion::from_rects(
rects : Array[Rect],
surface : Rect,
) -> DamageRegion {
let clipped : Array[Rect] = []
for rect in rects {
match rect.clamp_to(surface) {
Some(clamped) => if !clamped.is_empty() { clipped.push(clamped) }
None => ()
}
}
if clipped.is_empty() {
DamageRegion::Empty
} else {
let coalesced = coalesce_damage_rects(clipped)
if coalesced.length() > damage_region_max_rects() {
DamageRegion::FullSurface("too many dirty rects")
} else {
let mut union_rect = coalesced[0]
for index in 1.. 0.0 &&
union_rect.area() / surface_area > damage_region_full_threshold() {
DamageRegion::FullSurface("dirty region covers most of the surface")
} else {
DamageRegion::Rects(coalesced)
}
}
}
}
///|
fn coalesce_damage_rects(rects : Array[Rect]) -> Array[Rect] {
let mut merged = rects.copy()
let mut changed = true
while changed {
changed = false
let used : Array[Bool] = []
for _ in merged {
used.push(false)
}
let next : Array[Rect] = []
for index, rect in merged {
if !used[index] {
let mut current = rect
used[index] = true
let mut expanded = true
while expanded {
expanded = false
for other_index, other in merged {
if !used[other_index] && current.touches_or_overlaps(other) {
current = current.union(other)
used[other_index] = true
expanded = true
changed = true
}
}
}
next.push(current)
}
}
merged = next
}
merged
}
///|
fn Rect::touches_or_overlaps(self : Rect, other : Rect) -> Bool {
!self.is_empty() &&
!other.is_empty() &&
self.origin.x <= other.max_x() &&
other.origin.x <= self.max_x() &&
self.origin.y <= other.max_y() &&
other.origin.y <= self.max_y()
}
///|
pub fn DamageRegion::rect_count(self : DamageRegion) -> Int {
match self {
Empty => 0
FullSurface(_) => 0
Rects(rects) => rects.length()
}
}
///|
pub fn DamageRegion::kind(self : DamageRegion) -> String {
match self {
Empty => "empty"
FullSurface(_) => "full"
Rects(_) => "rects"
}
}
///|
pub fn DamageRegion::full_reason(self : DamageRegion) -> String {
match self {
FullSurface(reason) => reason
_ => ""
}
}
///|
pub fn DrawFrame::new(
commands~ : Array[DrawCommand],
platform_views? : Array[PlatformViewPlacement] = [],
overlay_bounds? : Rect? = None,
damage~ : DamageRegion,
clear_color? : Color = Color::white(),
) -> DrawFrame {
{ commands, platform_views, overlay_bounds, damage, clear_color }
}
///|
pub fn optional_rect_union(left : Rect?, right : Rect?) -> Rect? {
match (left, right) {
(Some(a), Some(b)) => Some(a.union(b))
(Some(a), None) => Some(a)
(None, Some(b)) => Some(b)
(None, None) => None
}
}
///|
pub fn DrawCommand::bounds(self : DrawCommand) -> Rect? {
match self {
Clear(_) => None
FillRect(rect, _) => Some(rect.inflate(1.0))
StrokeRect(rect, _, width) => Some(rect.inflate(width.max(1.0)))
FillRoundedRect(rounded, _) => Some(rounded.rect.inflate(1.0))
StrokeRoundedRect(rounded, _, width) =>
Some(rounded.rect.inflate(width.max(1.0)))
FillRoundedRectBrush(rounded, _) => Some(rounded.rect.inflate(1.0))
StrokeRoundedRectBrush(rounded, _, width) =>
Some(rounded.rect.inflate(width.max(1.0)))
DrawShadow(shadow) =>
Some(
shadow.rect.rect
.inflate(shadow.blur_radius + shadow.spread.abs() + 2.0)
.offset(dx=shadow.offset.x, dy=shadow.offset.y),
)
DrawText(run) => Some(run.frame.inflate(1.0))
DrawImage(run) => Some(run.frame.inflate(1.0))
PushClip(rect) => Some(rect)
PopClip => None
PushRoundedClip(rounded) => Some(rounded.rect)
PopRoundedClip => None
PushTransform(_) => None
PopTransform => None
PushOpacity(_) => None
PopOpacity => None
PushLayer(layer) =>
match layer.mask {
NoMask => None
RectMask(rect) => Some(rect)
RoundedMask(rounded) => Some(rounded.rect)
}
PopLayer => None
BeginRetainedLayer(spec) => Some(spec.frame)
EndRetainedLayer(_) => None
PushFilter(filter) =>
match filter {
Blur(radius) =>
Some(Rect::new(x=0.0, y=0.0, width=0.0, height=0.0).inflate(radius))
_ => None
}
PopFilter => None
DrawPath(path) => path.bounds()
DrawShaderEffect(spec) => Some(spec.frame.inflate(1.0))
}
}
///|
pub fn PathSpec::bounds(self : PathSpec) -> Rect? {
let mut has_point = false
let mut min_x = 0.0
let mut min_y = 0.0
let mut max_x = 0.0
let mut max_y = 0.0
let add_point = (point : Point) => {
if !has_point {
min_x = point.x
min_y = point.y
max_x = point.x
max_y = point.y
has_point = true
} else {
min_x = min_x.min(point.x)
min_y = min_y.min(point.y)
max_x = max_x.max(point.x)
max_y = max_y.max(point.y)
}
}
for verb in self.verbs {
match verb {
MoveTo(point) | LineTo(point) => add_point(point)
QuadTo(a, b) => {
add_point(a)
add_point(b)
}
CubicTo(a, b, c) => {
add_point(a)
add_point(b)
add_point(c)
}
Close => ()
}
}
if has_point {
Some(
Rect::new(x=min_x, y=min_y, width=max_x - min_x, height=max_y - min_y).inflate(
self.stroke_width.max(1.0),
),
)
} else {
None
}
}