use super::*;
std::thread_local! {
static PAINT_CLIP_STACK: std::cell::RefCell<Vec<Rect>> =
std::cell::RefCell::new(Vec::new());
}
pub fn current_paint_clip() -> Option<Rect> {
PAINT_CLIP_STACK.with(|stack| stack.borrow().last().copied())
}
pub fn paint_subtree(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
if !widget.is_visible() {
if paint_subtree_unified_backbuffer(widget, ctx, true) {
return;
}
if ctx.supports_compositing_layers() {
if let Some(layer) = widget.compositing_layer() {
paint_subtree_layer(widget, ctx, true, layer);
}
}
return;
}
if paint_subtree_unified_backbuffer(widget, ctx, true) {
return;
} else if widget.backbuffer_cache_mut().is_some() {
paint_subtree_backbuffered(widget, ctx);
} else {
paint_subtree_direct(widget, ctx);
}
}
fn paint_subtree_unified_backbuffer(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
) -> bool {
let spec = widget.backbuffer_spec();
if spec.kind == BackbufferKind::None {
return false;
}
match spec.kind {
BackbufferKind::GlFbo if ctx.supports_retained_layers() => {
paint_subtree_gl_backbuffer(widget, ctx, include_overlay, spec);
true
}
BackbufferKind::SoftwareRgba | BackbufferKind::SoftwareLcd => {
if widget.backbuffer_cache_mut().is_some() {
paint_subtree_backbuffered(widget, ctx);
true
} else {
false
}
}
_ => false,
}
}
fn paint_subtree_gl_backbuffer(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
spec: BackbufferSpec,
) {
let b = widget.bounds();
let layer_w = (b.width + spec.outsets.left + spec.outsets.right).max(1.0);
let layer_h = (b.height + spec.outsets.bottom + spec.outsets.top).max(1.0);
let subtree_needs_draw = widget.needs_draw();
let theme_epoch = crate::theme::current_visuals_epoch();
let typography_epoch = crate::font_settings::current_typography_epoch();
let (key, needs_draw) = {
let Some(state) = widget.backbuffer_state_mut() else {
paint_subtree_direct(widget, ctx);
return;
};
let w = layer_w.ceil().max(1.0) as u32;
let h = layer_h.ceil().max(1.0) as u32;
let changed = state.width != w || state.height != h || state.spec_kind != spec.kind;
let style_changed =
state.theme_epoch != theme_epoch || state.typography_epoch != typography_epoch;
let needs = !spec.cached || state.dirty || changed || style_changed || subtree_needs_draw;
if changed {
state.width = w;
state.height = h;
state.spec_kind = spec.kind;
}
(state.id(), needs)
};
if spec.cached && !needs_draw {
ctx.save();
ctx.translate(-spec.outsets.left, -spec.outsets.bottom);
let composited = ctx.composite_retained_layer(key, layer_w, layer_h, spec.alpha);
ctx.restore();
if composited {
if let Some(state) = widget.backbuffer_state_mut() {
state.composite_count = state.composite_count.saturating_add(1);
}
return;
}
}
ctx.save();
ctx.translate(-spec.outsets.left, -spec.outsets.bottom);
if spec.cached {
ctx.push_retained_layer_with_alpha(key, layer_w, layer_h, spec.alpha);
} else {
ctx.push_layer_with_alpha(layer_w, layer_h, spec.alpha);
}
ctx.translate(spec.outsets.left, spec.outsets.bottom);
paint_subtree_direct_inner(widget, ctx, include_overlay, false);
ctx.pop_layer();
ctx.restore();
if let Some(state) = widget.backbuffer_state_mut() {
state.dirty = false;
state.theme_epoch = theme_epoch;
state.typography_epoch = typography_epoch;
state.repaint_count = state.repaint_count.saturating_add(1);
state.composite_count = state.composite_count.saturating_add(1);
}
}
fn paint_subtree_layer(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
layer: crate::widget::CompositingLayer,
) {
let b = widget.bounds();
let layer_w = (b.width + layer.outset_left + layer.outset_right).max(1.0);
let layer_h = (b.height + layer.outset_bottom + layer.outset_top).max(1.0);
ctx.save();
ctx.translate(-layer.outset_left, -layer.outset_bottom);
ctx.push_layer_with_alpha(layer_w, layer_h, layer.alpha);
ctx.translate(layer.outset_left, layer.outset_bottom);
paint_subtree_direct_inner(widget, ctx, include_overlay, false);
ctx.pop_layer();
ctx.restore();
}
pub fn paint_global_overlays(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
if !widget.is_visible() {
return;
}
let n = widget.children().len();
for i in 0..n {
let child = &mut widget.children_mut()[i];
let b = child.bounds();
ctx.save();
ctx.translate(b.x, b.y);
paint_global_overlays(child.as_mut(), ctx);
ctx.restore();
}
widget.paint_global_overlay(ctx);
}
fn paint_subtree_direct(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
paint_subtree_direct_inner(widget, ctx, true, true);
}
fn paint_subtree_direct_no_overlay(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
paint_subtree_direct_inner(widget, ctx, false, true);
}
fn paint_subtree_direct_inner(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
allow_compositing_layer: bool,
) {
if allow_compositing_layer && ctx.supports_compositing_layers() {
if let Some(layer) = widget.compositing_layer() {
paint_subtree_layer(widget, ctx, include_overlay, layer);
return;
}
}
let snap_this = widget.enforce_integer_bounds();
if snap_this {
ctx.save();
ctx.snap_to_pixel();
}
widget.paint(ctx);
let b = widget.bounds();
let (cx, cy, cw, ch) = widget
.clip_children_rect()
.unwrap_or((0.0, 0.0, b.width, b.height));
ctx.save();
ctx.clip_rect(cx, cy, cw, ch);
let clip = root_rect_from_local(ctx, cx, cy, cw, ch);
PAINT_CLIP_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
let clipped = if let Some(prev) = stack.last().copied() {
intersect_rects(prev, clip).unwrap_or_else(|| Rect::new(0.0, 0.0, 0.0, 0.0))
} else {
clip
};
stack.push(clipped);
});
let n = widget.children().len();
for i in 0..n {
let child_bounds = widget.children()[i].bounds();
let snap_to_pixel = widget.children()[i].enforce_integer_bounds();
ctx.save();
if snap_to_pixel {
ctx.translate(child_bounds.x.round(), child_bounds.y.round());
} else {
ctx.translate(child_bounds.x, child_bounds.y);
}
let child = &mut widget.children_mut()[i];
paint_subtree(child.as_mut(), ctx);
ctx.restore();
}
PAINT_CLIP_STACK.with(|stack| {
stack.borrow_mut().pop();
});
ctx.restore(); if include_overlay {
widget.paint_overlay(ctx);
}
widget.finish_paint(ctx);
if snap_this {
ctx.restore();
}
}
fn root_rect_from_local(ctx: &dyn DrawCtx, x: f64, y: f64, w: f64, h: f64) -> Rect {
let mut points = [(x, y), (x + w, y), (x, y + h), (x + w, y + h)];
let transform = ctx.root_transform();
for (px, py) in &mut points {
transform.transform(px, py);
}
let min_x = points.iter().map(|(x, _)| *x).fold(f64::INFINITY, f64::min);
let max_x = points
.iter()
.map(|(x, _)| *x)
.fold(f64::NEG_INFINITY, f64::max);
let min_y = points.iter().map(|(_, y)| *y).fold(f64::INFINITY, f64::min);
let max_y = points
.iter()
.map(|(_, y)| *y)
.fold(f64::NEG_INFINITY, f64::max);
Rect::new(
min_x,
min_y,
(max_x - min_x).max(0.0),
(max_y - min_y).max(0.0),
)
}
fn intersect_rects(a: Rect, b: Rect) -> Option<Rect> {
let x0 = a.x.max(b.x);
let y0 = a.y.max(b.y);
let x1 = (a.x + a.width).min(b.x + b.width);
let y1 = (a.y + a.height).min(b.y + b.height);
(x1 >= x0 && y1 >= y0).then(|| Rect::new(x0, y0, x1 - x0, y1 - y0))
}
fn paint_subtree_backbuffered(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
ctx.save();
ctx.snap_to_pixel();
let b = widget.bounds();
let dps = crate::device_scale::device_scale().max(1e-6);
let w_phys = (b.width * dps).ceil().max(1.0) as u32;
let h_phys = (b.height * dps).ceil().max(1.0) as u32;
let w_logical = w_phys as f64 / dps;
let h_logical = h_phys as f64 / dps;
let mode = widget.backbuffer_mode();
let mode_is_lcd = matches!(mode, BackbufferMode::LcdCoverage);
let theme_epoch = crate::theme::current_visuals_epoch();
let typography_epoch = crate::font_settings::current_typography_epoch();
let (needs_raster, has_bitmap) = {
let cache = widget
.backbuffer_cache_mut()
.expect("backbuffered widget must return Some from backbuffer_cache_mut");
let cache_is_lcd = cache.lcd_alpha.is_some();
let needs = cache.dirty
|| cache.pixels.is_none()
|| cache.width != w_phys
|| cache.height != h_phys
|| cache_is_lcd != mode_is_lcd
|| cache.theme_epoch != theme_epoch
|| cache.typography_epoch != typography_epoch;
(needs, cache.pixels.is_some())
};
if needs_raster {
let (pixels_bytes, lcd_alpha_bytes): (Vec<u8>, Option<Vec<u8>>) = match mode {
BackbufferMode::Rgba => {
let mut fb = Framebuffer::new(w_phys, h_phys);
{
let mut sub = GfxCtx::new(&mut fb);
sub.set_lcd_mode(false); if (dps - 1.0).abs() > 1e-6 {
sub.scale(dps, dps);
}
paint_subtree_direct_no_overlay(widget, &mut sub);
}
let mut pixels = fb.pixels_flipped();
crate::framebuffer::unpremultiply_rgba_inplace(&mut pixels);
(pixels, None)
}
BackbufferMode::LcdCoverage => {
let mut buf = LcdBuffer::new(w_phys, h_phys);
{
let mut sub = crate::lcd_gfx_ctx::LcdGfxCtx::new(&mut buf);
if (dps - 1.0).abs() > 1e-6 {
sub.scale(dps, dps);
}
paint_subtree_direct_no_overlay(widget, &mut sub);
}
(buf.color_plane_flipped(), Some(buf.alpha_plane_flipped()))
}
};
let pixels = Arc::new(pixels_bytes);
let lcd_alpha = lcd_alpha_bytes.map(Arc::new);
let cache = widget.backbuffer_cache_mut().unwrap();
cache.pixels = Some(Arc::clone(&pixels));
cache.lcd_alpha = lcd_alpha.as_ref().map(Arc::clone);
cache.width = w_phys;
cache.height = h_phys;
cache.dirty = false;
cache.theme_epoch = theme_epoch;
cache.typography_epoch = typography_epoch;
}
let cache = widget.backbuffer_cache_mut().unwrap();
let img_w = cache.width;
let img_h = cache.height;
match (cache.pixels.as_ref(), cache.lcd_alpha.as_ref()) {
(Some(color), Some(alpha)) => {
ctx.draw_lcd_backbuffer_arc(color, alpha, img_w, img_h, 0.0, 0.0, w_logical, h_logical);
}
(Some(bmp), None) => {
ctx.draw_image_rgba_arc(bmp, img_w, img_h, 0.0, 0.0, w_logical, h_logical);
}
_ => {}
}
let _ = has_bitmap;
widget.paint_overlay(ctx);
ctx.restore(); }