blob: 6e96847fa22b3f727fb34ee52b34a0bf4ad8365c [file] [log] [blame]
// Copyright 2020 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 crate::{
color::Color,
drawing::{
linebreak_text, path_for_corner_knockouts, path_for_cursor, path_for_rectangle, FontFace,
GlyphMap, Text,
},
geometry::IntPoint,
render::{
BlendMode, Composition, Context as RenderContext, Fill, FillRule, Layer, PreClear, Raster,
RenderExt, Shed, Style,
},
Coord, Point, Rect, Size, ViewAssistantContext,
};
use anyhow::{bail, Error};
use euclid::{default::Transform2D, point2, size2, vec2};
use fuchsia_zircon::{AsHandleRef, Event, Signals};
use std::{
any::Any,
collections::{BTreeMap, HashMap},
path::PathBuf,
sync::atomic::{AtomicUsize, Ordering},
};
#[derive(Debug)]
pub struct SetColorMessage {
pub color: Color,
}
pub struct SetTextMessage {
pub text: String,
}
pub struct SetLocationMessage {
pub location: Point,
}
pub trait Facet {
fn update_layers(
&mut self,
size: Size,
layer_group: &mut LayerGroup,
render_context: &mut RenderContext,
) -> Result<(), Error>;
fn handle_message(&mut self, msg: Box<dyn Any>) {
println!("Unhandled message {:#?}", msg);
}
}
pub type FacetPtr = Box<dyn Facet>;
pub type LayerIterator = Box<dyn Iterator<Item = Layer>>;
pub struct RectangleFacet {
bounds: Rect,
color: Color,
raster: Option<Raster>,
}
impl RectangleFacet {
pub fn new(bounds: Rect, color: Color) -> FacetPtr {
Box::new(Self { bounds, color, raster: None })
}
pub fn h_line(
y: Coord,
x_start: Coord,
x_end: Coord,
thickness: Coord,
color: Color,
) -> FacetPtr {
let x = x_start.min(x_end);
let half_thickness = thickness / 2.0;
let top_left = point2(x, y - half_thickness);
let line_bounds = Rect::new(top_left, size2((x_end - x_start).abs(), thickness));
Self::new(line_bounds, color)
}
pub fn v_line(
x: Coord,
y_start: Coord,
y_end: Coord,
thickness: Coord,
color: Color,
) -> FacetPtr {
let y = y_start.min(y_end);
let height = (y_end - y_start).abs();
let half_thickness = thickness / 2.0;
let top_left = point2(x - half_thickness, y);
let line_bounds = Rect::new(top_left, size2(thickness, height));
Self::new(line_bounds, color)
}
}
impl Facet for RectangleFacet {
fn update_layers(
&mut self,
_size: Size,
layer_group: &mut LayerGroup,
render_context: &mut RenderContext,
) -> Result<(), Error> {
let line_raster = self.raster.take().unwrap_or_else(|| {
let line_path = path_for_rectangle(&self.bounds, render_context);
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&line_path, None);
raster_builder.build()
});
let raster = line_raster.clone();
self.raster = Some(line_raster);
layer_group.replace_all(std::iter::once(Layer {
raster: raster,
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(self.color),
blend_mode: BlendMode::Over,
},
}));
Ok(())
}
fn handle_message(&mut self, msg: Box<dyn Any>) {
if let Some(set_color) = msg.downcast_ref::<SetColorMessage>() {
self.color = set_color.color;
}
}
}
pub enum TextHorizontalAlignment {
Left,
Right,
Center,
}
impl Default for TextHorizontalAlignment {
fn default() -> Self {
Self::Left
}
}
pub enum TextVerticalAlignment {
Baseline,
Top,
Bottom,
Center,
}
impl Default for TextVerticalAlignment {
fn default() -> Self {
Self::Baseline
}
}
#[derive(Default)]
pub struct TextFacetOptions {
pub horizontal_alignment: TextHorizontalAlignment,
pub vertical_alignment: TextVerticalAlignment,
pub color: Color,
pub max_width: Option<f32>,
}
pub struct TextFacet {
face: FontFace,
lines: Vec<String>,
size: f32,
location: Point,
options: TextFacetOptions,
rendered_text: Option<Text>,
glyphs: GlyphMap,
}
impl TextFacet {
fn wrap_lines(face: &FontFace, size: f32, text: &str, max_width: &Option<f32>) -> Vec<String> {
let lines: Vec<String> = text.lines().map(|line| String::from(line)).collect();
if let Some(max_width) = max_width {
let wrapped_lines = lines
.iter()
.map(|line| linebreak_text(face, size, line, *max_width))
.flatten()
.collect();
wrapped_lines
} else {
lines
}
}
pub fn new(face: FontFace, text: &str, size: f32, location: Point) -> FacetPtr {
Self::with_options(face, text, size, location, TextFacetOptions::default())
}
pub fn with_options(
face: FontFace,
text: &str,
size: f32,
location: Point,
options: TextFacetOptions,
) -> FacetPtr {
let lines = Self::wrap_lines(&face, size, text, &options.max_width);
Box::new(Self {
face,
lines,
size,
location,
options,
rendered_text: None,
glyphs: GlyphMap::new(),
})
}
}
impl Facet for TextFacet {
fn update_layers(
&mut self,
_size: Size,
layer_group: &mut LayerGroup,
render_context: &mut RenderContext,
) -> Result<(), Error> {
let rendered_text = self.rendered_text.take().unwrap_or_else(|| {
Text::new_with_lines(
render_context,
&self.lines,
self.size,
&self.face,
&mut self.glyphs,
)
});
let ascent = self.face.ascent(self.size);
let descent = self.face.descent(self.size);
let x = match self.options.horizontal_alignment {
TextHorizontalAlignment::Left => self.location.x,
TextHorizontalAlignment::Center => {
self.location.x - rendered_text.bounding_box.size.width / 2.0
}
TextHorizontalAlignment::Right => {
self.location.x - rendered_text.bounding_box.size.width
}
};
let y = match self.options.vertical_alignment {
TextVerticalAlignment::Baseline => self.location.y - ascent,
TextVerticalAlignment::Top => self.location.y,
TextVerticalAlignment::Bottom => self.location.y - ascent + descent,
TextVerticalAlignment::Center => {
let capital_height = self.face.capital_height(self.size).unwrap_or(self.size);
self.location.y + capital_height / 2.0 - ascent
}
};
let translation = vec2(x, y);
let raster = rendered_text.raster.clone().translate(translation.to_i32());
self.rendered_text = Some(rendered_text);
layer_group.replace_all(std::iter::once(Layer {
raster,
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(self.options.color),
blend_mode: BlendMode::Over,
},
}));
Ok(())
}
fn handle_message(&mut self, msg: Box<dyn Any>) {
if let Some(set_text) = msg.downcast_ref::<SetTextMessage>() {
self.lines =
Self::wrap_lines(&self.face, self.size, &set_text.text, &self.options.max_width);
self.rendered_text = None;
} else if let Some(set_color) = msg.downcast_ref::<SetColorMessage>() {
self.options.color = set_color.color;
}
}
}
pub struct RasterFacet {
raster: Raster,
style: Style,
location: Point,
}
impl RasterFacet {
pub fn new(raster: Raster, style: Style, location: Point) -> Self {
Self { raster, style, location }
}
}
impl Facet for RasterFacet {
fn update_layers(
&mut self,
_size: Size,
layer_group: &mut LayerGroup,
_render_context: &mut RenderContext,
) -> Result<(), Error> {
layer_group.replace_all(std::iter::once(Layer {
raster: self.raster.clone().translate(self.location.to_vector().to_i32()),
style: self.style.clone(),
}));
Ok(())
}
fn handle_message(&mut self, msg: Box<dyn Any>) {
if let Some(set_location) = msg.downcast_ref::<SetLocationMessage>() {
self.location = set_location.location;
}
}
}
pub struct ShedFacet {
path: PathBuf,
location: Point,
size: Size,
rasters: Option<Vec<(Raster, Style)>>,
}
impl ShedFacet {
pub fn new(path: PathBuf, location: Point, size: Size) -> Self {
Self { path, location, size, rasters: None }
}
}
impl Facet for ShedFacet {
fn update_layers(
&mut self,
_size: Size,
layer_group: &mut LayerGroup,
render_context: &mut RenderContext,
) -> Result<(), Error> {
let rasters = self.rasters.take().unwrap_or_else(|| {
if let Some(shed) = Shed::open(&self.path).ok() {
let shed_size = shed.size();
let scale_factor: Size =
size2(self.size.width / shed_size.width, self.size.height / shed_size.height);
let transform =
Transform2D::translation(-shed_size.width / 2.0, -shed_size.height / 2.0)
.then_scale(scale_factor.width, scale_factor.height);
shed.rasters(render_context, Some(&transform))
} else {
let placeholder_rect =
Rect::from_size(self.size).translate(self.size.to_vector() / -2.0);
let rect_path = path_for_rectangle(&placeholder_rect, render_context);
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&rect_path, None);
let raster = raster_builder.build();
vec![(
raster,
Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(Color::red()),
blend_mode: BlendMode::Over,
},
)]
}
});
let location = self.location;
layer_group.replace_all(rasters.iter().map(|(raster, style)| Layer {
raster: raster.clone().translate(location.to_vector().to_i32()),
style: *style,
}));
self.rasters = Some(rasters);
Ok(())
}
fn handle_message(&mut self, msg: Box<dyn Any>) {
if let Some(set_location) = msg.downcast_ref::<SetLocationMessage>() {
self.location = set_location.location;
}
}
}
struct Rendering {
size: Size,
previous_rasters: Vec<Raster>,
}
impl Rendering {
fn new() -> Rendering {
Rendering { previous_rasters: Vec::new(), size: Size::zero() }
}
}
fn raster_for_corner_knockouts(
bounds: &Rect,
corner_radius: Coord,
render_context: &mut RenderContext,
) -> Raster {
let path = path_for_corner_knockouts(bounds, corner_radius, render_context);
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&path, None);
raster_builder.build()
}
pub struct SceneOptions {
pub background_color: Color,
pub round_scene_corners: bool,
}
impl Default for SceneOptions {
fn default() -> Self {
Self { background_color: Color::new(), round_scene_corners: true }
}
}
fn create_mouse_cursor_raster(render_context: &mut RenderContext) -> Raster {
let path = path_for_cursor(Point::zero(), 20.0, render_context);
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&path, None);
raster_builder.build()
}
fn cursor_layer(cursor_raster: &Raster, position: IntPoint, color: &Color) -> Layer {
Layer {
raster: cursor_raster.clone().translate(position.to_vector()),
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(*color),
blend_mode: BlendMode::Over,
},
}
}
fn cursor_layer_pair(cursor_raster: &Raster, position: IntPoint) -> Vec<Layer> {
let black_pos = position + vec2(-1, -1);
vec![
cursor_layer(cursor_raster, position, &Color::fuchsia()),
cursor_layer(cursor_raster, black_pos, &Color::new()),
]
}
pub type FacetId = usize;
pub type FacetMap = BTreeMap<FacetId, FacetPtr>;
pub struct LayerGroup(Vec<Layer>);
impl LayerGroup {
pub fn replace_all(&mut self, new_layers: impl IntoIterator<Item = Layer>) {
self.0 = new_layers.into_iter().collect();
}
}
#[derive(Default)]
struct IdGenerator {}
impl Iterator for IdGenerator {
type Item = usize;
fn next(&mut self) -> Option<usize> {
static NEXT_ID: AtomicUsize = AtomicUsize::new(100);
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
// fetch_add wraps on overflow, which we'll use as a signal
// that this generator is out of ids.
if id == 0 {
None
} else {
Some(id)
}
}
}
type FacetIdGenerator = IdGenerator;
pub type LayerMap = BTreeMap<FacetId, LayerGroup>;
pub struct Scene {
renderings: HashMap<u64, Rendering>,
mouse_cursor_raster: Option<Raster>,
facet_id_generator: FacetIdGenerator,
facets: FacetMap,
facet_order: Vec<FacetId>,
layers: LayerMap,
composition: Composition,
options: SceneOptions,
}
impl Scene {
fn new_from_builder(
options: SceneOptions,
facets: FacetMap,
facet_id_generator: FacetIdGenerator,
) -> Self {
let facet_order: Vec<FacetId> = facets.iter().map(|(facet_id, _)| *facet_id).collect();
Self {
renderings: HashMap::new(),
mouse_cursor_raster: None,
facet_id_generator,
facets,
facet_order,
layers: LayerMap::new(),
composition: Composition::new(options.background_color),
options,
}
}
pub fn round_scene_corners(&mut self, round_scene_corners: bool) {
self.options.round_scene_corners = round_scene_corners;
}
pub fn add_facet(&mut self, facet: FacetPtr) -> FacetId {
let facet_id = self.facet_id_generator.next().expect("facet ID");
self.facets.insert(facet_id, facet);
self.facet_order.push(facet_id);
facet_id
}
pub fn remove_facet(&mut self, facet_id: FacetId) -> Result<(), Error> {
if let Some(_) = self.facets.remove(&facet_id).as_mut() {
self.layers.remove(&facet_id);
self.facet_order.retain(|fid| facet_id != *fid);
Ok(())
} else {
bail!("Tried to remove non-existant facet")
}
}
pub fn move_facet_forward(&mut self, facet_id: FacetId) -> Result<(), Error> {
if let Some(index) = self.facet_order.iter().position(|fid| *fid == facet_id) {
if index > 0 {
let new_index = index - 1;
self.facet_order.swap(new_index, index)
}
Ok(())
} else {
bail!("Tried to move_facet_forward non-existant facet")
}
}
pub fn move_facet_backward(&mut self, facet_id: FacetId) -> Result<(), Error> {
if let Some(index) = self.facet_order.iter().position(|fid| *fid == facet_id) {
if index < self.facet_order.len() - 1 {
let new_index = index + 1;
self.facet_order.swap(new_index, index)
}
Ok(())
} else {
bail!("Tried to move_facet_backward non-existant facet")
}
}
pub fn layers(&mut self, size: Size, render_context: &mut RenderContext) -> Vec<Layer> {
let mut layers = Vec::new();
for facet_id in &self.facet_order {
let facet = self.facets.get_mut(facet_id).expect("facet");
let facet_layers = if let Some(facet_layers) = self.layers.get(facet_id) {
facet_layers.0.clone()
} else {
Vec::new()
};
let mut layer_group = LayerGroup(facet_layers);
facet.update_layers(size, &mut layer_group, render_context).expect("update_layers");
layers.append(&mut layer_group.0.clone());
self.layers.insert(*facet_id, layer_group);
}
layers
}
fn create_or_update_rendering(
renderings: &mut HashMap<u64, Rendering>,
background_color: Color,
context: &ViewAssistantContext,
) -> Option<PreClear> {
let image_id = context.image_id;
let size_rendering = renderings.entry(image_id).or_insert_with(|| Rendering::new());
let size = context.size;
if size != size_rendering.size {
size_rendering.size = context.size;
size_rendering.previous_rasters.clear();
Some(PreClear { color: background_color })
} else {
None
}
}
fn update_composition(
image_id: u64,
layers: Vec<Layer>,
mouse_position: &Option<IntPoint>,
mouse_cursor_raster: &Option<Raster>,
corner_knockouts: &Option<Raster>,
renderings: &mut HashMap<u64, Rendering>,
background_color: Color,
composition: &mut Composition,
) -> Vec<Layer> {
let corner_knockouts_layer = corner_knockouts.as_ref().and_then(|raster| {
Some(Layer {
raster: raster.clone(),
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(Color::new()),
blend_mode: BlendMode::Over,
},
})
});
let cursor_layers: Vec<Layer> = mouse_position
.and_then(|position| {
let mouse_cursor_raster =
mouse_cursor_raster.as_ref().expect("mouse_cursor_raster");
Some(cursor_layer_pair(mouse_cursor_raster, position))
})
.into_iter()
.flatten()
.collect();
let clear_rendering = renderings.get_mut(&image_id).expect("rendering");
composition.replace(
..,
cursor_layers
.clone()
.into_iter()
.chain(corner_knockouts_layer.into_iter())
.chain(layers.into_iter())
.chain(clear_rendering.previous_rasters.drain(..).map(|raster| Layer {
raster,
style: Style {
fill_rule: FillRule::WholeTile,
fill: Fill::Solid(background_color),
blend_mode: BlendMode::Over,
},
})),
);
cursor_layers
}
pub fn render(
&mut self,
render_context: &mut RenderContext,
ready_event: Event,
context: &ViewAssistantContext,
) -> Result<(), Error> {
let image = render_context.get_current_image(context);
let image_id = context.image_id;
let background_color = self.options.background_color;
let pre_clear =
Self::create_or_update_rendering(&mut self.renderings, background_color, context);
let size = context.size;
let ext = RenderExt { pre_clear, ..Default::default() };
let corner_knockouts = if self.options.round_scene_corners {
Some(raster_for_corner_knockouts(&Rect::from_size(size), 10.0, render_context))
} else {
None
};
if context.mouse_cursor_position.is_some() && self.mouse_cursor_raster.is_none() {
self.mouse_cursor_raster = Some(create_mouse_cursor_raster(render_context));
}
let layers: Vec<Layer> = self.layers(size, render_context);
let cursor_layer = Self::update_composition(
image_id,
layers.clone(),
&context.mouse_cursor_position,
&self.mouse_cursor_raster,
&corner_knockouts,
&mut self.renderings,
background_color,
&mut self.composition,
);
render_context.render(&self.composition, None, image, &ext);
ready_event.as_handle_ref().signal(Signals::NONE, Signals::EVENT_SIGNALED)?;
let update_rendering = self.renderings.entry(image_id).or_insert_with(|| Rendering::new());
let previous_rasters: Vec<Raster> = layers
.iter()
.chain(cursor_layer.iter())
.map(|layer| layer.raster.clone())
.chain(corner_knockouts.into_iter())
.collect();
update_rendering.previous_rasters = previous_rasters;
Ok(())
}
pub fn send_message(&mut self, target: &FacetId, msg: Box<dyn Any>) {
if let Some(facet) = self.facets.get_mut(target) {
facet.handle_message(msg);
}
}
}
pub struct SceneBuilder {
background_color: Color,
round_scene_corners: bool,
facet_id_generator: FacetIdGenerator,
facets: FacetMap,
}
impl SceneBuilder {
pub fn new(background_color: Color) -> Self {
Self {
background_color,
round_scene_corners: false,
facet_id_generator: FacetIdGenerator::default(),
facets: FacetMap::new(),
}
}
pub fn round_scene_corners(&mut self, round: bool) {
self.round_scene_corners = round;
}
fn allocate_facet_id(&mut self) -> FacetId {
self.facet_id_generator.next().expect("facet_id")
}
fn push_facet(&mut self, facet: FacetPtr) -> FacetId {
let facet_id = self.allocate_facet_id();
self.facets.insert(facet_id.clone(), facet);
facet_id
}
pub fn rectangle(&mut self, bounds: Rect, color: Color) -> FacetId {
self.push_facet(RectangleFacet::new(bounds, color))
}
pub fn h_line(
&mut self,
y: Coord,
x_start: Coord,
x_end: Coord,
thickness: Coord,
color: Color,
) -> FacetId {
self.push_facet(RectangleFacet::h_line(y, x_start, x_end, thickness, color))
}
pub fn v_line(
&mut self,
x: Coord,
y_start: Coord,
y_end: Coord,
thickness: Coord,
color: Color,
) -> FacetId {
self.push_facet(RectangleFacet::v_line(x, y_start, y_end, thickness, color))
}
pub fn text(
&mut self,
face: FontFace,
text: &str,
size: f32,
location: Point,
options: TextFacetOptions,
) -> FacetId {
self.push_facet(TextFacet::with_options(face, text, size, location, options))
}
pub fn facet(&mut self, facet: FacetPtr) -> FacetId {
self.push_facet(facet)
}
pub fn scene_options(&self) -> SceneOptions {
SceneOptions {
background_color: self.background_color,
round_scene_corners: self.round_scene_corners,
}
}
pub fn build(self) -> Scene {
Scene::new_from_builder(self.scene_options(), self.facets, self.facet_id_generator)
}
}