blob: 4962de9c3356a431515ed92db05ad234dfc0405b [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::cell::Cell;
use crate::{
component::Component,
component_dirt::ComponentDirt,
container_component::ContainerComponent,
core::{Core, CoreContext, Object, ObjectRef, OnAdded, Property},
dyn_vec::DynVec,
math,
node::Node,
option_cell::OptionCell,
renderer::{GradientBuilder, GradientType},
shapes::{
paint::{shape_paint_mutator::ShapePaintMutator, GradientStop, RadialGradient},
ShapePaintContainer,
},
status_code::StatusCode,
TransformComponent,
};
#[derive(Debug)]
pub struct LinearGradient {
container_component: ContainerComponent,
shape_paint_mutator: ShapePaintMutator,
start_x: Property<f32>,
start_y: Property<f32>,
end_x: Property<f32>,
end_y: Property<f32>,
opacity: Property<f32>,
stops: DynVec<Object<GradientStop>>,
paints_in_world_space: Cell<bool>,
shape_paint_container: OptionCell<Object<Node>>,
}
impl ObjectRef<'_, LinearGradient> {
pub fn start_x(&self) -> f32 {
self.start_x.get()
}
pub fn set_start_x(&self, start_x: f32) {
if self.start_x() == start_x {
return;
}
self.start_x.set(start_x);
self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
}
pub fn start_y(&self) -> f32 {
self.start_y.get()
}
pub fn set_start_y(&self, start_y: f32) {
if self.start_y() == start_y {
return;
}
self.start_y.set(start_y);
self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
}
pub fn end_x(&self) -> f32 {
self.end_x.get()
}
pub fn set_end_x(&self, end_x: f32) {
if self.end_x() == end_x {
return;
}
self.end_x.set(end_x);
self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
}
pub fn end_y(&self) -> f32 {
self.end_y.get()
}
pub fn set_end_y(&self, end_y: f32) {
if self.end_y() == end_y {
return;
}
self.end_y.set(end_y);
self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
}
pub fn opacity(&self) -> f32 {
self.opacity.get()
}
pub fn set_opacity(&self, opacity: f32) {
if self.opacity() == opacity {
return;
}
self.opacity.set(opacity);
self.mark_gradient_dirty();
}
}
impl ObjectRef<'_, LinearGradient> {
pub fn push_stop(&self, stop: Object<GradientStop>) {
self.stops.push(stop);
}
pub fn paints_in_world_space(&self) -> bool {
self.paints_in_world_space.get()
}
pub fn set_paints_in_world_space(&self, paints_in_world_space: bool) {
if self.paints_in_world_space() != paints_in_world_space {
self.paints_in_world_space.set(paints_in_world_space);
self.cast::<Component>().add_dirt(ComponentDirt::PAINT, false);
}
}
pub fn mark_gradient_dirty(&self) {
self.cast::<Component>().add_dirt(ComponentDirt::PAINT, false);
}
pub fn mark_stops_dirty(&self) {
self.cast::<Component>().add_dirt(ComponentDirt::PAINT & ComponentDirt::STOPS, false);
}
pub fn build_dependencies(&self) {
let component = self.cast::<Component>();
let parent = component.parent();
if let Some(parents_parent) =
parent.and_then(|parent| parent.cast::<Component>().as_ref().parent())
{
// Parent's parent must be a shape paint container.
let object: Object = Object::from(parents_parent.clone());
assert!(Object::<ShapePaintContainer>::try_from(object).is_ok());
// TODO: see if artboard should inherit from some TransformComponent
// that can return a world transform. We store the container just for
// doing the transform to world in update. If it's the artboard, then
// we're already in world so no need to transform.
self.shape_paint_container.set(parents_parent.try_cast::<Node>());
parents_parent.cast::<Component>().as_ref().push_dependent(component.as_object());
}
}
pub fn update(&self, value: ComponentDirt) {
let mut builder = GradientBuilder::new(match self.try_cast::<RadialGradient>() {
Some(_) => GradientType::Radial,
None => GradientType::Linear,
});
// Do the stops need to be re-ordered?
if Component::value_has_dirt(value, ComponentDirt::STOPS) {
self.stops
.sort_by(|a, b| a.as_ref().position().partial_cmp(&b.as_ref().position()).unwrap());
}
let world_transformed = Component::value_has_dirt(value, ComponentDirt::WORLD_TRANSFORM);
let rebuild_gradient = Component::value_has_dirt(
value,
ComponentDirt::PAINT | ComponentDirt::RENDER_OPACITY | ComponentDirt::TRANSFORM,
) || self.paints_in_world_space() && world_transformed;
if !rebuild_gradient {
return;
}
let start = math::Vec::new(self.start_x(), self.start_y());
let end = math::Vec::new(self.end_x(), self.end_y());
// Check if we need to update the world space gradient (if there's no
// shape container, presumably it's the artboard and we're already in
// world).
match (self.paints_in_world_space(), self.shape_paint_container.get()) {
(true, Some(shape_paint_container)) => {
// Get the start and end of the gradient in world coordinates (world
// transform of the shape).
let world =
shape_paint_container.cast::<TransformComponent>().as_ref().world_transform();
let world_start = world * start;
let world_end = world * end;
builder.start(world_start).end(world_end);
}
_ => {
builder.start(start).end(end);
}
}
let ro = self.opacity() * self.cast::<ShapePaintMutator>().render_opacity();
for stop in self.stops.iter() {
builder.push_stop(stop.as_ref().color().mul_opacity(ro), stop.as_ref().position());
}
self.cast::<ShapePaintMutator>().set_gradient(builder.build());
}
}
impl Core for LinearGradient {
parent_types![
(container_component, ContainerComponent),
(shape_paint_mutator, ShapePaintMutator),
];
properties![
(42, start_x, set_start_x),
(33, start_y, set_start_y),
(34, end_x, set_end_x),
(35, end_y, set_end_y),
(46, opacity, set_opacity),
container_component,
];
}
impl OnAdded for ObjectRef<'_, LinearGradient> {
on_added!([on_added_clean, import], ContainerComponent);
fn on_added_dirty(&self, context: &dyn CoreContext) -> StatusCode {
let code = self.cast::<ContainerComponent>().on_added_dirty(context);
if code != StatusCode::Ok {
return code;
}
if let Some(parent) =
self.cast::<Component>().parent().and_then(|parent| parent.try_cast::<Component>())
{
self.cast::<ShapePaintMutator>().init_paint_mutator(parent);
StatusCode::Ok
} else {
StatusCode::MissingObject
}
}
}
impl Default for LinearGradient {
fn default() -> Self {
Self {
container_component: ContainerComponent::default(),
shape_paint_mutator: ShapePaintMutator::default(),
start_x: Property::new(0.0),
start_y: Property::new(0.0),
end_x: Property::new(0.0),
end_y: Property::new(0.0),
opacity: Property::new(1.0),
stops: DynVec::new(),
paints_in_world_space: Cell::new(false),
shape_paint_container: OptionCell::new(),
}
}
}