use gtk4::pango;
use crate::primitives::layout_metrics::pixel_toast_stack_layout;
use crate::primitives::toast::{ToastOverlay, ToastStackLayout};
#[cfg(test)]
use crate::theme::Theme;
#[allow(clippy::too_many_arguments)]
pub fn gtk_toast_stack_layout(
stack: &ToastOverlay,
pango_layout: &pango::Layout,
origin_x: f32,
origin_y: f32,
viewport_width: f32,
viewport_height: f32,
line_height: f64,
) -> ToastStackLayout {
pango_layout.set_attributes(None);
let measure = super::toolbar::PangoMeasure {
pango_layout: Some(pango_layout),
char_width: 0.0,
};
pixel_toast_stack_layout(
stack,
&measure,
origin_x,
origin_y,
viewport_width,
viewport_height,
line_height as f32,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::toast::{Toast, ToastCorner, ToastHit, ToastOverlay, ToastSeverity};
use crate::types::{Color, WidgetId};
use pangocairo::cairo::{Context, Format, ImageSurface};
const VIEW_W: f64 = 340.0;
const VIEW_H: f64 = 200.0;
const LINE_HEIGHT: f64 = 16.0;
const BOX_COLOR: Color = Color::rgb(10, 20, 30);
fn colored_toast(id: &str, title: &str) -> Toast {
Toast {
id: WidgetId::new(id),
title: title.into(),
body: String::new(),
severity: ToastSeverity::Info,
actions: Vec::new(),
accent: Some(BOX_COLOR),
}
}
fn stack_br(toasts: Vec<Toast>) -> ToastOverlay {
ToastOverlay {
id: WidgetId::new("toasts"),
corner: ToastCorner::BottomRight,
toasts,
focus: None,
}
}
fn pixel(data: &[u8], stride: usize, x: i32, y: i32) -> (u8, u8, u8) {
let off = y as usize * stride + x as usize * 4;
(data[off + 2], data[off + 1], data[off])
}
fn paint_and_click_round_trip_at(origin_x: f64, origin_y: f64) {
let canvas_w = (origin_x + VIEW_W).ceil() as i32;
let canvas_h = (origin_y + VIEW_H).ceil() as i32;
let mut surface =
ImageSurface::create(Format::ARgb32, canvas_w, canvas_h).expect("create ImageSurface");
let stack = stack_br(vec![colored_toast("t1", "Hello")]);
let layout = {
let cr = Context::new(&surface).expect("Context::new");
cr.set_source_rgb(1.0, 1.0, 1.0);
cr.paint().ok();
let pango_layout = pangocairo::functions::create_layout(&cr);
let mut raw = crate::gtk::surface::CairoSurface {
cr: &cr,
layout: Some(&pango_layout),
translucent_fill: true,
};
crate::primitives::toast::native_surface_paint::paint(
&stack,
&mut raw,
&Theme::default(),
origin_x as f32,
origin_y as f32,
VIEW_W as f32,
VIEW_H as f32,
LINE_HEIGHT as f32,
)
};
surface.flush();
let stride = surface.stride() as usize;
let data = surface.data().expect("surface data");
assert_eq!(layout.visible_toasts.len(), 1);
let vt = &layout.visible_toasts[0];
let probe_x = (vt.bounds.x + 2.0) as i32;
let probe_y = (vt.bounds.y + vt.bounds.height - 2.0) as i32;
assert_eq!(
pixel(&data, stride, probe_x, probe_y),
(BOX_COLOR.r, BOX_COLOR.g, BOX_COLOR.b),
"toast box should be painted at its own absolute bounds \
(origin=({origin_x}, {origin_y}), bounds={:?})",
vt.bounds,
);
let db = vt.dismiss_bounds.expect("dismiss bounds present");
let hit = layout.hit_test(db.x + db.width * 0.5, db.y + db.height * 0.5);
assert_eq!(hit, ToastHit::Dismiss(WidgetId::new("t1")));
let body_hit = layout.hit_test(vt.bounds.x + 5.0, vt.bounds.y + vt.bounds.height * 0.5);
assert_eq!(body_hit, ToastHit::Body(WidgetId::new("t1")));
}
#[test]
fn paint_and_click_round_trip() {
paint_and_click_round_trip_at(0.0, 0.0);
}
#[test]
fn paint_and_click_round_trip_at_nonzero_origin() {
paint_and_click_round_trip_at(7.0, 13.0);
}
}