use crate::media::{BoxShadows, Color, IBrush, IEffect, IExperimentalAcrylicMaterial, IPen, RenderOptions};
use crate::media::imaging::BitmapBlendingMode;
use crate::platform::{
AlphaFormat, IBitmapImpl, IDrawingContextImpl, IDrawingContextImplWithEffects, IDrawingContextLayerImpl,
IDrawingContextWithAcrylicLikeSupport, IGeometryImpl, IGlyphRunImpl, ILockedFramebuffer,
IPlatformRenderInterfaceRegion, IReadableBitmapImpl, IRenderTargetBitmapImpl, PixelFormat, PixelFormats,
};
use crate::{Matrix, PixelSize, Rect, RoundedRect, Vector};
use std::any::{Any, TypeId};
use std::rc::Rc;
#[derive(Clone, Default)]
pub struct DrawingLog(std::sync::Arc<std::sync::Mutex<Vec<String>>>);
impl DrawingLog {
pub fn new() -> Self {
Self::default()
}
pub fn push(&self, entry: impl Into<String>) {
self.0.lock().unwrap().push(entry.into());
}
pub fn entries(&self) -> Vec<String> {
self.0.lock().unwrap().clone()
}
pub fn take(&self) -> Vec<String> {
std::mem::take(&mut *self.0.lock().unwrap())
}
pub fn count(&self, prefix: &str) -> usize {
self.0.lock().unwrap().iter().filter(|e| e.starts_with(prefix)).count()
}
pub fn clear(&self) {
self.0.lock().unwrap().clear();
}
}
fn brush_text(brush: Option<&dyn IBrush>) -> String {
match brush {
None => "none".to_string(),
Some(brush) => match brush.as_solid_color_brush() {
Some(solid) => format!("{}", solid.color()),
None => "brush".to_string(),
},
}
}
fn effect_text(effect: &dyn IEffect) -> String {
if let Some(blur) = effect.as_blur_effect() {
format!("blur({})", blur.radius())
} else if let Some(shadow) = effect.as_drop_shadow_effect() {
format!("drop-shadow({} {} {})", shadow.offset_x(), shadow.offset_y(), shadow.blur_radius())
} else {
"effect".to_string()
}
}
fn pen_text(pen: Option<&dyn IPen>) -> String {
match pen {
None => "none".to_string(),
Some(pen) => format!("{}@{}", brush_text(pen.brush().as_deref()), pen.thickness()),
}
}
pub struct MockDrawingContextImpl {
log: DrawingLog,
transform: Matrix,
pub log_transforms: bool,
pub supports_effects: bool,
pub supports_compositing_layers: bool,
pub log_effects: bool,
pub supports_acrylic: bool,
pub supports_layer_effects: bool,
pub log_render_options: bool,
}
impl MockDrawingContextImpl {
pub fn new(log: DrawingLog) -> Self {
Self {
log,
transform: Matrix::IDENTITY,
log_transforms: true,
supports_effects: false,
supports_compositing_layers: false,
log_effects: false,
supports_acrylic: false,
supports_layer_effects: false,
log_render_options: false,
}
}
pub fn log(&self) -> &DrawingLog {
&self.log
}
}
impl IDrawingContextImpl for MockDrawingContextImpl {
fn transform(&self) -> Matrix {
self.transform
}
fn set_transform(&mut self, value: Matrix) {
self.transform = value;
if self.log_transforms {
self.log.push(format!("SetTransform {value}"));
}
}
fn clear(&mut self, color: Color) {
self.log.push(format!("Clear {color}"));
}
fn draw_bitmap(&mut self, _source: &dyn IBitmapImpl, opacity: f64, source_rect: Rect, dest_rect: Rect) {
self.log.push(format!("DrawBitmap {opacity} {source_rect} {dest_rect}"));
}
fn draw_bitmap_with_mask(
&mut self,
_source: &dyn IBitmapImpl,
opacity_mask: &dyn IBrush,
opacity_mask_rect: Rect,
dest_rect: Rect,
) {
self.log.push(format!(
"DrawBitmapWithMask {} {opacity_mask_rect} {dest_rect}",
brush_text(Some(opacity_mask))
));
}
fn draw_line(&mut self, pen: Option<&dyn IPen>, p1: crate::Point, p2: crate::Point) {
self.log.push(format!("DrawLine {} {p1} {p2}", pen_text(pen)));
}
fn draw_geometry(&mut self, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>, geometry: &dyn IGeometryImpl) {
self.log.push(format!("DrawGeometry {} {} {}", brush_text(brush), pen_text(pen), geometry.bounds()));
}
fn draw_rectangle(
&mut self,
brush: Option<&dyn IBrush>,
pen: Option<&dyn IPen>,
rect: RoundedRect,
box_shadows: &BoxShadows,
) {
self.log.push(format!(
"DrawRectangle {} {} {} shadows={}",
brush_text(brush),
pen_text(pen),
rect.rect,
box_shadows.count()
));
}
fn draw_region(
&mut self,
brush: Option<&dyn IBrush>,
pen: Option<&dyn IPen>,
_region: &dyn IPlatformRenderInterfaceRegion,
) {
self.log.push(format!("DrawRegion {} {}", brush_text(brush), pen_text(pen)));
}
fn draw_ellipse(&mut self, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>, rect: Rect) {
self.log.push(format!("DrawEllipse {} {} {rect}", brush_text(brush), pen_text(pen)));
}
fn draw_glyph_run(&mut self, foreground: Option<&dyn IBrush>, glyph_run: &dyn IGlyphRunImpl) {
self.log.push(format!("DrawGlyphRun {} {}", brush_text(foreground), glyph_run.bounds()));
}
fn create_layer(&mut self, size: PixelSize) -> Rc<dyn IDrawingContextLayerImpl> {
self.log.push(format!("CreateLayer {size}"));
Rc::new(MockDrawingContextLayerImpl::new(self.log.clone(), size))
}
fn push_clip(&mut self, clip: Rect) {
self.log.push(format!("PushClip {clip}"));
}
fn push_clip_rounded(&mut self, clip: RoundedRect) {
self.log.push(format!("PushRoundedClip {}", clip.rect));
}
fn push_clip_region(&mut self, _region: &dyn IPlatformRenderInterfaceRegion) {
self.log.push("PushClipRegion");
}
fn pop_clip(&mut self) {
self.log.push("PopClip");
}
fn push_layer(&mut self, bounds: Rect) {
self.log.push(format!("PushLayer {bounds}"));
}
fn pop_layer(&mut self) {
self.log.push("PopLayer");
}
fn try_push_compositing_layer(&mut self, blend: BitmapBlendingMode, opacity: f64, bounds: Rect) -> bool {
if !self.supports_compositing_layers {
return false;
}
self.log.push(format!("PushCompositingLayer {blend:?} {opacity} {bounds}"));
true
}
fn pop_compositing_layer(&mut self) {
self.log.push("PopCompositingLayer");
}
fn push_opacity(&mut self, opacity: f64, _bounds: Option<Rect>) {
self.log.push(format!("PushOpacity {opacity}"));
}
fn pop_opacity(&mut self) {
self.log.push("PopOpacity");
}
fn push_opacity_mask(&mut self, mask: &dyn IBrush, bounds: Rect) {
self.log.push(format!("PushOpacityMask {} {bounds}", brush_text(Some(mask))));
}
fn pop_opacity_mask(&mut self) {
self.log.push("PopOpacityMask");
}
fn push_geometry_clip(&mut self, clip: &dyn IGeometryImpl) {
self.log.push(format!("PushGeometryClip {}", clip.bounds()));
}
fn pop_geometry_clip(&mut self) {
self.log.push("PopGeometryClip");
}
fn push_render_options(&mut self, render_options: RenderOptions) {
if self.log_render_options {
self.log.push(format!(
"PushRenderOptions {:?} {:?} {:?} {:?} {:?}",
render_options.text_rendering_mode,
render_options.bitmap_interpolation_mode,
render_options.edge_mode,
render_options.bitmap_blending_mode,
render_options.requires_full_opacity_handling
));
} else {
self.log.push("PushRenderOptions");
}
}
fn pop_render_options(&mut self) {
self.log.push("PopRenderOptions");
}
fn push_text_options(&mut self, text_options: crate::media::TextOptions) {
self.log.push(format!(
"PushTextOptions {:?} {:?} {:?}",
text_options.text_rendering_mode, text_options.text_hinting_mode, text_options.baseline_pixel_alignment
));
}
fn pop_text_options(&mut self) {
self.log.push("PopTextOptions");
}
fn get_feature(&mut self, _feature_type: TypeId) -> Option<Rc<dyn Any>> {
None
}
fn as_drawing_context_impl_with_effects(&mut self) -> Option<&mut dyn IDrawingContextImplWithEffects> {
if self.supports_effects {
Some(self)
} else {
None
}
}
fn as_drawing_context_impl_with_layer_effects(
&mut self,
) -> Option<&mut dyn crate::platform::IDrawingContextImplWithLayerEffects> {
if self.supports_layer_effects {
Some(self)
} else {
None
}
}
fn as_drawing_context_with_acrylic_like_support(
&mut self,
) -> Option<&mut dyn IDrawingContextWithAcrylicLikeSupport> {
if self.supports_acrylic {
Some(self)
} else {
None
}
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn dispose(&mut self) {
self.log.push("Dispose");
}
}
impl IDrawingContextImplWithEffects for MockDrawingContextImpl {
fn push_effect(&mut self, clip_rect: Option<Rect>, effect: &dyn IEffect) {
let clip = match clip_rect {
Some(clip_rect) => clip_rect.to_string(),
None => "none".to_string(),
};
if self.log_effects {
self.log.push(format!("PushEffect {clip} {}", effect_text(effect)));
} else {
self.log.push(format!("PushEffect {clip}"));
}
}
fn pop_effect(&mut self) {
self.log.push("PopEffect");
}
}
impl crate::platform::IDrawingContextImplWithLayerEffects for MockDrawingContextImpl {
fn push_blend_layer(&mut self, bounds: Rect, mode: crate::media::imaging::BitmapBlendingMode) {
self.log.push(format!("PushBlendLayer {bounds} {mode:?}"));
}
fn pop_blend_layer(&mut self) {
self.log.push("PopBlendLayer");
}
fn push_color_matrix_layer(
&mut self,
bounds: Rect,
_matrix: &crate::media::ColorMatrix,
alpha_mode: crate::media::ColorMatrixAlphaMode,
) {
self.log.push(format!("PushColorMatrixLayer {bounds} {alpha_mode:?}"));
}
fn pop_color_matrix_layer(&mut self) {
self.log.push("PopColorMatrixLayer");
}
fn push_multiply_layer(&mut self, bounds: Rect, factors: &crate::platform::MultiplyLayerFactors) {
self.log.push(format!(
"PushMultiplyLayer {bounds} {}x{} {}",
factors.size.width, factors.size.height, factors.scale
));
}
fn pop_multiply_layer(&mut self) {
self.log.push("PopMultiplyLayer");
}
}
impl IDrawingContextWithAcrylicLikeSupport for MockDrawingContextImpl {
fn draw_rectangle_with_material(&mut self, material: &dyn IExperimentalAcrylicMaterial, rect: RoundedRect) {
self.log.push(format!("DrawRectangleWithMaterial {} {}", material.tint_color(), rect.rect));
}
}
pub struct MockDrawingContextLayerImpl {
log: DrawingLog,
size: PixelSize,
}
impl MockDrawingContextLayerImpl {
pub fn new(log: DrawingLog, size: PixelSize) -> Self {
Self { log, size }
}
}
impl IBitmapImpl for MockDrawingContextLayerImpl {
fn dpi(&self) -> Vector {
Vector::new(96.0, 96.0)
}
fn pixel_size(&self) -> PixelSize {
self.size
}
fn version(&self) -> i32 {
0
}
fn save(
&self,
_stream: &mut dyn std::io::Write,
_options: &crate::media::imaging::BitmapEncoderOptions,
) -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Unsupported, "the mock layer has no pixels to save"))
}
fn dispose(&self) {}
fn as_any(&self) -> &dyn Any {
self
}
}
impl IDrawingContextLayerImpl for MockDrawingContextLayerImpl {
fn blit(&self, _context: &mut dyn IDrawingContextImpl) {
self.log.push("Blit");
}
fn can_blit(&self) -> bool {
true
}
fn is_corrupted(&self) -> bool {
false
}
fn create_drawing_context(&self) -> Box<dyn IDrawingContextImpl> {
Box::new(MockDrawingContextImpl::new(self.log.clone()))
}
fn create_shared_snapshot(&self) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
std::sync::Arc::new(MockRenderTargetBitmapImpl::new(self.log.clone(), self.size, Vector::new(96.0, 96.0)))
}
}
pub struct MockRenderTargetBitmapImpl {
log: DrawingLog,
size: PixelSize,
dpi: Vector,
}
impl MockRenderTargetBitmapImpl {
pub fn new(log: DrawingLog, size: PixelSize, dpi: Vector) -> Self {
Self { log, size, dpi }
}
}
impl IBitmapImpl for MockRenderTargetBitmapImpl {
fn dpi(&self) -> Vector {
self.dpi
}
fn pixel_size(&self) -> PixelSize {
self.size
}
fn version(&self) -> i32 {
0
}
fn save(
&self,
_stream: &mut dyn std::io::Write,
_options: &crate::media::imaging::BitmapEncoderOptions,
) -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Unsupported, "the mock bitmap has no pixels to save"))
}
fn dispose(&self) {}
fn as_any(&self) -> &dyn Any {
self
}
fn as_readable_bitmap(&self) -> Option<&dyn IReadableBitmapImpl> {
Some(self)
}
}
impl IReadableBitmapImpl for MockRenderTargetBitmapImpl {
fn format(&self) -> Option<PixelFormat> {
None
}
fn alpha_format(&self) -> Option<AlphaFormat> {
None
}
fn lock(&self) -> Rc<dyn ILockedFramebuffer> {
let (width, height) = (self.size.width.max(0) as usize, self.size.height.max(0) as usize);
Rc::new(MockLockedFramebuffer {
data: std::cell::RefCell::new(vec![0; width * height * 4]),
size: self.size,
dpi: self.dpi,
})
}
}
struct MockLockedFramebuffer {
data: std::cell::RefCell<Vec<u8>>,
size: PixelSize,
dpi: Vector,
}
impl ILockedFramebuffer for MockLockedFramebuffer {
fn address(&self) -> *mut u8 {
self.data.borrow_mut().as_mut_ptr()
}
fn with_data(&self, access: &mut dyn FnMut(&mut [u8])) {
access(&mut self.data.borrow_mut());
}
fn size(&self) -> PixelSize {
self.size
}
fn row_bytes(&self) -> i32 {
self.size.width * 4
}
fn dpi(&self) -> Vector {
self.dpi
}
fn format(&self) -> PixelFormat {
PixelFormats::RGBA8888
}
fn alpha_format(&self) -> AlphaFormat {
AlphaFormat::Premul
}
fn dispose(&self) {}
}
impl IRenderTargetBitmapImpl for MockRenderTargetBitmapImpl {
fn create_drawing_context(&self) -> Box<dyn IDrawingContextImpl> {
Box::new(MockDrawingContextImpl::new(self.log.clone()))
}
}