use super::*;
use crate::draw_ctx::DrawCtx;
use std::sync::Arc;
#[test]
fn test_push_pop_layer_solid_composites_correctly() {
let mut fb = Framebuffer::new(20, 20);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
ctx.push_layer(20.0, 20.0);
ctx.set_fill_color(Color::rgba(1.0, 0.0, 0.0, 1.0));
ctx.begin_path();
ctx.rect(0.0, 0.0, 20.0, 20.0);
ctx.fill();
ctx.pop_layer();
drop(ctx);
let center = sample(&fb, 10, 10);
assert!(
is_red(center),
"After layer composite, centre must be red; got {center:?}"
);
}
#[test]
fn test_push_pop_layer_alpha_blends_into_parent() {
let mut fb = Framebuffer::new(20, 20);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
ctx.push_layer(20.0, 20.0);
ctx.set_fill_color(Color::rgba(1.0, 0.0, 0.0, 0.5));
ctx.begin_path();
ctx.rect(0.0, 0.0, 20.0, 20.0);
ctx.fill();
ctx.pop_layer();
drop(ctx);
let [r, g, b, _] = sample(&fb, 10, 10);
assert!(r > 200, "Red channel must be high; got {r}");
assert!(
g > 80 && g < 200,
"Green channel must be mid-tone (pink); got {g}"
);
assert!(
b > 80 && b < 200,
"Blue channel must be mid-tone (pink); got {b}"
);
}
#[test]
fn test_push_layer_preserves_device_scale_hidpi() {
let mut fb = Framebuffer::new(40, 40);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
ctx.scale(2.0, 2.0);
ctx.push_layer(10.0, 10.0);
ctx.set_fill_color(Color::rgba(1.0, 0.0, 0.0, 1.0));
ctx.begin_path();
ctx.rect(0.0, 0.0, 10.0, 10.0);
ctx.fill();
ctx.pop_layer();
drop(ctx);
assert!(
is_red(sample(&fb, 1, 1)),
"near-origin must be red; got {:?}",
sample(&fb, 1, 1)
);
assert!(
is_red(sample(&fb, 18, 18)),
"physical (18,18) inside the 20px layer must be red; got {:?}",
sample(&fb, 18, 18)
);
assert!(
is_white(sample(&fb, 30, 30)),
"outside the physical extent must stay white; got {:?}",
sample(&fb, 30, 30)
);
}
#[test]
fn test_push_layer_translated_origin_hidpi() {
let mut fb = Framebuffer::new(40, 40);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
ctx.scale(2.0, 2.0);
ctx.translate(5.0, 5.0);
ctx.push_layer(10.0, 10.0);
ctx.set_fill_color(Color::rgba(1.0, 0.0, 0.0, 1.0));
ctx.begin_path();
ctx.rect(0.0, 0.0, 10.0, 10.0);
ctx.fill();
ctx.pop_layer();
drop(ctx);
assert!(
is_red(sample(&fb, 11, 11)),
"just inside the physical origin (10,10) must be red; got {:?}",
sample(&fb, 11, 11)
);
assert!(
is_red(sample(&fb, 28, 28)),
"near the far physical corner (30,30) must be red; got {:?}",
sample(&fb, 28, 28)
);
assert!(
is_white(sample(&fb, 6, 6)),
"before the physical origin must stay white; got {:?}",
sample(&fb, 6, 6)
);
assert!(
is_white(sample(&fb, 34, 34)),
"beyond the physical extent must stay white; got {:?}",
sample(&fb, 34, 34)
);
}
#[test]
fn test_push_layer_fractional_scale() {
let mut fb = Framebuffer::new(40, 40);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
ctx.scale(1.5, 1.5);
ctx.translate(4.0, 4.0);
ctx.push_layer(10.0, 10.0);
ctx.set_fill_color(Color::rgba(1.0, 0.0, 0.0, 1.0));
ctx.begin_path();
ctx.rect(0.0, 0.0, 10.0, 10.0);
ctx.fill();
ctx.pop_layer();
drop(ctx);
assert!(
is_red(sample(&fb, 7, 7)),
"just inside physical origin (6,6) must be red; got {:?}",
sample(&fb, 7, 7)
);
assert!(
is_red(sample(&fb, 20, 20)),
"boundary pixel (20,20) inside [6,21) must be red — no gap from ceil \
sizing; got {:?}",
sample(&fb, 20, 20)
);
assert!(
is_white(sample(&fb, 4, 4)),
"before the physical origin must stay white; got {:?}",
sample(&fb, 4, 4)
);
assert!(
is_white(sample(&fb, 22, 22)),
"beyond the [6,21) extent must stay white; got {:?}",
sample(&fb, 22, 22)
);
}
#[test]
fn test_pop_layer_composite_respects_parent_clip() {
let mut fb = Framebuffer::new(20, 20);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
ctx.clip_rect(0.0, 0.0, 10.0, 20.0);
ctx.push_layer(20.0, 20.0);
ctx.set_fill_color(Color::rgba(1.0, 0.0, 0.0, 1.0));
ctx.begin_path();
ctx.rect(0.0, 0.0, 20.0, 20.0);
ctx.fill();
ctx.pop_layer();
drop(ctx);
assert!(
is_red(sample(&fb, 3, 10)),
"inside the parent clip must be red; got {:?}",
sample(&fb, 3, 10)
);
assert!(
is_white(sample(&fb, 15, 10)),
"outside the parent clip must stay white; got {:?}",
sample(&fb, 15, 10)
);
}
#[test]
fn test_backbuffer_rgba_blit_honors_global_alpha() {
let mut fb = Framebuffer::new(10, 10);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
let mut data = Vec::with_capacity(10 * 10 * 4);
for _ in 0..(10 * 10) {
data.extend_from_slice(&[255, 0, 0, 255]);
}
let data = Arc::new(data);
ctx.set_global_alpha(0.5);
ctx.draw_image_rgba_arc(&data, 10, 10, 0.0, 0.0, 10.0, 10.0);
drop(ctx);
let [r, g, b, _] = sample(&fb, 5, 5);
assert!(r > 200, "Red channel must stay high; got {r}");
assert!(
g > 80 && g < 200,
"Green channel must be mid-tone (fade over white); got {g}"
);
assert!(
b > 80 && b < 200,
"Blue channel must be mid-tone (fade over white); got {b}"
);
}
struct AlphaScope {
bounds: crate::Rect,
children: Vec<Box<dyn crate::widget::Widget>>,
alpha: f64,
}
impl crate::widget::Widget for AlphaScope {
fn type_name(&self) -> &'static str {
"AlphaScope"
}
fn bounds(&self) -> crate::Rect {
self.bounds
}
fn set_bounds(&mut self, b: crate::Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn crate::widget::Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn crate::widget::Widget>> {
&mut self.children
}
fn layout(&mut self, avail: crate::Size) -> crate::Size {
if let Some(c) = self.children.first_mut() {
let s = c.layout(avail);
c.set_bounds(crate::Rect::new(0.0, 0.0, s.width, s.height));
}
self.bounds = crate::Rect::new(0.0, 0.0, avail.width, avail.height);
avail
}
fn paint(&mut self, _ctx: &mut dyn crate::draw_ctx::DrawCtx) {}
fn on_event(&mut self, _e: &crate::event::Event) -> crate::event::EventResult {
crate::event::EventResult::Ignored
}
fn compositing_layer(&mut self) -> Option<crate::widget::CompositingLayer> {
Some(crate::widget::CompositingLayer::new(0.0, 0.0, 0.0, 0.0, self.alpha))
}
}
#[test]
fn test_lcd_text_in_alpha_layer_fades_once() {
use crate::widget::{paint_subtree, Widget};
use crate::widgets::Label;
use crate::{Rect, Size};
crate::theme::set_visuals(crate::theme::Visuals::light());
crate::font_settings::set_lcd_enabled(true);
let font = Arc::new(crate::text::Font::from_slice(TEST_FONT).unwrap());
let mk_label = || {
let mut l = Label::new("HHHH", Arc::clone(&font))
.with_font_size(20.0)
.with_color(Color::rgba(0.0, 0.0, 0.0, 1.0));
let _ = l.layout(Size::new(80.0, 30.0));
l.set_bounds(Rect::new(0.0, 0.0, 80.0, 30.0));
l
};
let mut scope = AlphaScope {
bounds: Rect::default(),
children: vec![Box::new(mk_label())],
alpha: 0.5,
};
let _ = scope.layout(Size::new(80.0, 30.0));
let mut fb = Framebuffer::new(80, 30);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
paint_subtree(&mut scope, &mut ctx);
drop(ctx);
let mut label_ref = mk_label();
let mut fb_ref = Framebuffer::new(80, 30);
let mut ctx_ref = GfxCtx::new(&mut fb_ref);
ctx_ref.clear(Color::white());
paint_subtree(&mut label_ref, &mut ctx_ref);
drop(ctx_ref);
let mut darkest_faded = 255u8;
let mut darkest_full = 255u8;
for y in 0..30 {
for x in 0..80 {
darkest_faded = darkest_faded.min(sample(&fb, x, y)[0]);
darkest_full = darkest_full.min(sample(&fb_ref, x, y)[0]);
}
}
crate::font_settings::clear_lcd_enabled_override();
assert!(
darkest_full < 40,
"full-opacity black text must be near-black; got {darkest_full}"
);
assert!(
(100..=150).contains(&darkest_faded),
"text in a 0.5 alpha layer must fade ONCE (mid-gray ~128); got \
{darkest_faded} (≥160 would indicate a double alpha application)"
);
}
#[test]
fn test_lcd_backbuffer_blit_honors_global_alpha() {
let mut fb = Framebuffer::new(4, 4);
let mut ctx = GfxCtx::new(&mut fb);
ctx.clear(Color::white());
let mut color = Vec::with_capacity(4 * 4 * 3);
let mut alpha = Vec::with_capacity(4 * 4 * 3);
for _ in 0..(4 * 4) {
color.extend_from_slice(&[255, 0, 0]);
alpha.extend_from_slice(&[255, 255, 255]);
}
let color = Arc::new(color);
let alpha = Arc::new(alpha);
ctx.set_global_alpha(0.5);
ctx.draw_lcd_backbuffer_arc(&color, &alpha, 4, 4, 0.0, 0.0, 4.0, 4.0);
drop(ctx);
let [r, g, b, _] = sample(&fb, 2, 2);
assert!(r > 200, "Red channel must stay high; got {r}");
assert!(
g > 80 && g < 200,
"Green channel must be mid-tone (fade over white); got {g}"
);
assert!(
b > 80 && b < 200,
"Blue channel must be mid-tone (fade over white); got {b}"
);
}