use super::{
impl_server_render_resource, IAnimatedServerObject, IServerCompositionSurface, IServerObject,
IServerRenderResource, ServerCompositionSurfaceChanged, ServerCompositionVisual, ServerCompositor,
ServerRenderResource,
};
use crate::media::Colors;
use crate::platform::{IDrawingContextImpl, IDrawingContextLayerImpl, LtrbRect};
use crate::rendering::composition::generated::{
ServerCompositionVisualSurfaceHooks, ServerCompositionVisualSurfaceProps,
};
use crate::rendering::composition::transport::BatchStreamReader;
use crate::{Matrix, PixelSize, Rect, Size, Vector};
use std::any::{Any, TypeId};
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use std::time::Duration;
pub struct ServerCompositionVisualSurface {
base: ServerRenderResource,
props: ServerCompositionVisualSurfaceProps,
changed: ServerCompositionSurfaceChanged,
rendering: Cell<bool>,
freeze_requested: Cell<bool>,
frozen: Cell<bool>,
frozen_content: RefCell<Option<FrozenContent>>,
follows_source_bounds: Cell<bool>,
followed_source_bounds: Cell<Option<LtrbRect>>,
}
struct FrozenContent {
layer: Rc<dyn IDrawingContextLayerImpl>,
size: Size,
}
impl ServerCompositionVisualSurface {
pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionVisualSurface> {
let surface = Rc::new_cyclic(|this: &Weak<ServerCompositionVisualSurface>| ServerCompositionVisualSurface {
base: ServerRenderResource::new(
compositor,
this.clone() as Weak<dyn IAnimatedServerObject>,
this.clone() as Weak<dyn IServerRenderResource>,
),
props: ServerCompositionVisualSurfaceProps::new(),
changed: ServerCompositionSurfaceChanged::new(),
rendering: Cell::new(false),
freeze_requested: Cell::new(false),
frozen: Cell::new(false),
frozen_content: RefCell::new(None),
follows_source_bounds: Cell::new(false),
followed_source_bounds: Cell::new(None),
});
surface.base.object().activate();
compositor.register_visual_surface(Rc::downgrade(&surface));
surface
}
pub fn props(&self) -> &ServerCompositionVisualSurfaceProps {
&self.props
}
pub fn is_disposed(&self) -> bool {
self.base.is_disposed()
}
pub fn is_frozen(&self) -> bool {
self.frozen.get()
}
pub fn source_visual(&self) -> Option<Rc<ServerCompositionVisual>> {
self.props.source_visual().and_then(|visual| visual.into_any_rc().downcast::<ServerCompositionVisual>().ok())
}
pub fn source_rect(&self) -> Option<Rect> {
let source = self.source_visual()?;
let offset = self.props.source_offset();
let mut size = self.props.source_size();
if size == Vector::default() {
size = source.size();
}
(size.x > 0.0 && size.y > 0.0).then(|| Rect::new(offset.x, offset.y, size.x, size.y))
}
pub fn request_freeze(&self) {
if !self.frozen.get() {
self.freeze_requested.set(true);
}
}
pub fn update_live_surfaces(
compositor: &ServerCompositor,
surfaces: &RefCell<Vec<Weak<ServerCompositionVisualSurface>>>,
) -> bool {
let live: Vec<Rc<ServerCompositionVisualSurface>> = {
let mut surfaces = surfaces.borrow_mut();
surfaces.retain(|surface| surface.upgrade().is_some_and(|surface| !surface.is_disposed()));
surfaces.iter().filter_map(Weak::upgrade).collect()
};
let mut changed = false;
for surface in live {
if surface.freeze_requested.get() {
if compositor.is_rendering_stopped() || !compositor.render_interface().is_ready() {
continue;
}
surface.freeze(compositor);
changed = true;
} else if surface.source_changed() {
surface.content_changed();
changed = true;
}
}
changed
}
fn source_changed(&self) -> bool {
if self.frozen.get() {
return false;
}
let Some(source) = self.source_visual() else { return false };
if source.root().is_none() {
return false;
}
let mut changed = source.has_pending_render_changes();
if self.follows_source_bounds.get() {
let bounds = source.unclipped_sub_tree_bounds();
if self.followed_source_bounds.replace(bounds) != bounds {
changed = true;
}
}
changed
}
pub fn set_follows_source_bounds(&self, value: bool) {
self.follows_source_bounds.set(value);
}
pub fn render_source_subtree(&self, canvas: &mut dyn IDrawingContextImpl, clip: LtrbRect) {
let Some(source) = self.source_visual() else { return };
self.render_source(&source, canvas, clip);
}
fn content_changed(&self) {
self.base.core().invalidated(self);
self.changed.invoke();
}
pub fn freeze(&self, compositor: &ServerCompositor) {
self.freeze_requested.set(false);
if self.frozen.replace(true) {
return;
}
let (Some(source), Some(rect)) = (self.source_visual(), self.source_rect()) else {
self.content_changed();
return;
};
let scaling = source.root().map(|root| root.scaling()).filter(|scaling| *scaling > 0.0).unwrap_or(1.0);
let size = Size::new(rect.width, rect.height);
let pixel_size = PixelSize::from_size_ceiling(size, scaling);
if pixel_size.width < 1 || pixel_size.height < 1 {
self.content_changed();
return;
}
let current = compositor.render_interface().ensure_current();
let layer = compositor.render_interface().value().create_offscreen_render_target(
pixel_size,
Vector::new(scaling, scaling),
false,
);
{
let mut canvas = layer.create_drawing_context();
canvas.clear(Colors::TRANSPARENT);
canvas.set_transform(
Matrix::create_translation(-rect.x, -rect.y) * Matrix::create_scale(scaling, scaling),
);
self.render_source(&source, &mut *canvas, LtrbRect::INFINITE);
canvas.dispose();
}
current.dispose();
*self.frozen_content.borrow_mut() = Some(FrozenContent { layer, size });
self.content_changed();
}
fn render_source(&self, source: &Rc<ServerCompositionVisual>, canvas: &mut dyn IDrawingContextImpl, clip: LtrbRect) {
if self.rendering.replace(true) {
return;
}
source.render(canvas, clip, None, true, false, true);
self.rendering.set(false);
}
pub fn is_rendering(&self) -> bool {
self.rendering.get()
}
fn free_frozen_content(&self) {
if let Some(content) = self.frozen_content.borrow_mut().take() {
content.layer.dispose();
}
}
}
impl_server_render_resource!(
ServerCompositionVisualSurface,
base,
composition_property: ServerCompositionVisualSurfaceProps::get_composition_property
);
impl ServerCompositionVisualSurfaceHooks for ServerCompositionVisualSurface {
fn deserialize_changes_extra(&self, reader: &mut BatchStreamReader<'_>) {
if reader.read::<bool>() {
self.request_freeze();
}
}
}
impl IServerObject for ServerCompositionVisualSurface {
fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
self.props.deserialize_changes_core(self, reader, committed_at);
}
fn values_invalidated(&self) {
self.content_changed();
}
fn dispose(&self) {
self.free_frozen_content();
self.base.core().dispose();
}
fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
Some(self)
}
fn as_surface(self: Rc<Self>) -> Option<Rc<dyn IServerCompositionSurface>> {
Some(self)
}
fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
Some(self)
}
fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
self.props.find_props(type_id)
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
impl IServerCompositionSurface for ServerCompositionVisualSurface {
fn bitmap(&self) -> Option<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
None
}
fn changed(&self) -> &ServerCompositionSurfaceChanged {
&self.changed
}
fn natural_size(&self) -> Option<Size> {
if let Some(content) = self.frozen_content.borrow().as_ref() {
return Some(content.size);
}
if self.frozen.get() {
return None;
}
self.source_rect().map(|rect| Size::new(rect.width, rect.height))
}
fn draw(&self, canvas: &mut dyn IDrawingContextImpl, dest: Rect, opacity: f64) {
if dest.width <= 0.0 || dest.height <= 0.0 {
return;
}
if let Some(content) = self.frozen_content.borrow().as_ref() {
let pixel_size = content.layer.pixel_size();
canvas.draw_bitmap(
&*content.layer,
opacity,
Rect::new(0.0, 0.0, pixel_size.width as f64, pixel_size.height as f64),
dest,
);
return;
}
if self.frozen.get() || self.rendering.get() {
return;
}
let (Some(source), Some(rect)) = (self.source_visual(), self.source_rect()) else { return };
let saved = canvas.transform();
canvas.push_clip(dest);
if opacity != 1.0 {
canvas.push_opacity(opacity, Some(dest));
}
let map = Matrix::create_translation(-rect.x, -rect.y)
* Matrix::create_scale(dest.width / rect.width, dest.height / rect.height)
* Matrix::create_translation(dest.x, dest.y);
canvas.set_transform(map * saved);
let clip = LtrbRect::from_rect(dest).transform_to_aabb(saved);
self.render_source(&source, canvas, clip);
canvas.set_transform(saved);
if opacity != 1.0 {
canvas.pop_opacity();
}
canvas.pop_clip();
}
}