core: Move drawing api out from `movie_clip` into `drawing`

This commit is contained in:
Nathan Adams 2020-05-21 15:49:59 +02:00
parent 4f69566f77
commit 99574cfa72
3 changed files with 220 additions and 183 deletions

View File

@ -2,16 +2,16 @@
use crate::avm1::{Avm1, Object, StageObject, TObject, Value};
use crate::backend::audio::AudioStreamHandle;
use crate::backend::render::ShapeHandle;
use crate::character::Character;
use crate::context::{ActionType, RenderContext, UpdateContext};
use crate::display_object::{
Bitmap, Button, DisplayObjectBase, EditText, Graphic, MorphShapeStatic, TDisplayObject, Text,
};
use crate::drawing::Drawing;
use crate::events::{ButtonKeyCode, ClipEvent};
use crate::font::Font;
use crate::prelude::*;
use crate::shape_utils::{DistilledShape, DrawCommand, DrawPath};
use crate::shape_utils::DrawCommand;
use crate::tag_utils::{self, DecodeResult, SwfMovie, SwfSlice, SwfStream};
use enumset::{EnumSet, EnumSetType};
use gc_arena::{Collect, Gc, GcCell, MutationContext};
@ -46,14 +46,7 @@ pub struct MovieClipData<'gc> {
clip_actions: SmallVec<[ClipAction; 2]>,
flags: EnumSet<MovieClipFlags>,
avm1_constructor: Option<Object<'gc>>,
custom_shape: Option<ShapeHandle>,
custom_shape_bounds: BoundingBox,
custom_edge_bounds: BoundingBox,
dirty_shape: bool,
custom_fills: Vec<(FillStyle, Vec<DrawCommand>)>,
custom_lines: Vec<(LineStyle, Vec<DrawCommand>)>,
current_fill: Option<(FillStyle, Vec<DrawCommand>)>,
current_line: Option<(LineStyle, Vec<DrawCommand>)>,
drawing: Drawing,
}
impl<'gc> MovieClip<'gc> {
@ -72,14 +65,7 @@ impl<'gc> MovieClip<'gc> {
clip_actions: SmallVec::new(),
flags: EnumSet::empty(),
avm1_constructor: None,
custom_shape: None,
custom_shape_bounds: BoundingBox::default(),
custom_edge_bounds: BoundingBox::default(),
dirty_shape: false,
custom_fills: Vec::new(),
custom_lines: Vec::new(),
current_fill: None,
current_line: None,
drawing: Drawing::new(),
},
))
}
@ -112,14 +98,7 @@ impl<'gc> MovieClip<'gc> {
clip_actions: SmallVec::new(),
flags: MovieClipFlags::Playing.into(),
avm1_constructor: None,
custom_shape: None,
custom_shape_bounds: BoundingBox::default(),
custom_edge_bounds: BoundingBox::default(),
dirty_shape: false,
custom_fills: Vec::new(),
custom_lines: Vec::new(),
current_fill: None,
current_line: None,
drawing: Drawing::new(),
},
))
}
@ -585,28 +564,12 @@ impl<'gc> MovieClip<'gc> {
style: Option<FillStyle>,
) {
let mut mc = self.0.write(context.gc_context);
// TODO: If current_fill is not closed, we should close it and also close current_line
if let Some(existing) = mc.current_fill.take() {
mc.custom_fills.push(existing);
}
if let Some(style) = style {
mc.current_fill = Some((style, Vec::new()));
}
mc.dirty_shape = true;
mc.drawing.set_fill_style(style);
}
pub fn clear(self, context: &mut UpdateContext<'_, 'gc, '_>) {
let mut mc = self.0.write(context.gc_context);
mc.current_fill = None;
mc.current_line = None;
mc.custom_fills.clear();
mc.custom_lines.clear();
mc.custom_edge_bounds = BoundingBox::default();
mc.custom_shape_bounds = BoundingBox::default();
mc.dirty_shape = true;
mc.drawing.clear();
}
pub fn set_line_style(
@ -615,94 +578,12 @@ impl<'gc> MovieClip<'gc> {
style: Option<LineStyle>,
) {
let mut mc = self.0.write(context.gc_context);
if let Some(existing) = mc.current_line.take() {
mc.custom_lines.push(existing);
}
if let Some(style) = style {
mc.current_line = Some((style, Vec::new()));
}
mc.dirty_shape = true;
mc.drawing.set_line_style(style);
}
pub fn draw_command(self, context: &mut UpdateContext<'_, 'gc, '_>, command: DrawCommand) {
let mut mc = self.0.write(context.gc_context);
let mut include_last = false;
match command {
DrawCommand::MoveTo { .. } => {}
DrawCommand::LineTo { x, y } => {
mc.custom_shape_bounds.encompass(x, y);
mc.custom_edge_bounds.encompass(x, y);
include_last = true;
}
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
mc.custom_shape_bounds.encompass(x1, y1);
mc.custom_shape_bounds.encompass(x2, y2);
mc.custom_edge_bounds.encompass(x1, y1);
mc.custom_edge_bounds.encompass(x2, y2);
include_last = true;
}
}
if let Some((_, commands)) = &mut mc.current_line {
commands.push(command.clone());
}
if let Some((_, commands)) = &mut mc.current_fill {
commands.push(command);
}
if include_last {
if let Some(command) = mc
.current_fill
.as_ref()
.and_then(|(_, commands)| commands.last().cloned())
{
match command {
DrawCommand::MoveTo { x, y } => {
mc.custom_shape_bounds.encompass(x, y);
mc.custom_edge_bounds.encompass(x, y);
}
DrawCommand::LineTo { x, y } => {
mc.custom_shape_bounds.encompass(x, y);
mc.custom_edge_bounds.encompass(x, y);
}
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
mc.custom_shape_bounds.encompass(x1, y1);
mc.custom_shape_bounds.encompass(x2, y2);
mc.custom_edge_bounds.encompass(x1, y1);
mc.custom_edge_bounds.encompass(x2, y2);
}
}
}
if let Some(command) = mc
.current_line
.as_ref()
.and_then(|(_, commands)| commands.last().cloned())
{
match command {
DrawCommand::MoveTo { x, y } => {
mc.custom_shape_bounds.encompass(x, y);
mc.custom_edge_bounds.encompass(x, y);
}
DrawCommand::LineTo { x, y } => {
mc.custom_shape_bounds.encompass(x, y);
mc.custom_edge_bounds.encompass(x, y);
}
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
mc.custom_shape_bounds.encompass(x1, y1);
mc.custom_shape_bounds.encompass(x2, y2);
mc.custom_edge_bounds.encompass(x1, y1);
mc.custom_edge_bounds.encompass(x2, y2);
}
}
}
}
mc.dirty_shape = true;
mc.drawing.draw_command(command);
}
}
@ -742,70 +623,18 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
mc.run_clip_postaction((*self).into(), context, ClipEvent::Load);
}
if mc.dirty_shape {
mc.dirty_shape = false;
let mut paths = Vec::new();
for (style, commands) in &mc.custom_fills {
paths.push(DrawPath::Fill {
style,
commands: commands.to_owned(),
})
}
// TODO: If the current_fill is not closed, we should automatically close current_line
if let Some((style, commands)) = &mc.current_fill {
paths.push(DrawPath::Fill {
style,
commands: commands.to_owned(),
})
}
for (style, commands) in &mc.custom_lines {
paths.push(DrawPath::Stroke {
style,
commands: commands.to_owned(),
is_closed: false, // TODO: Determine this
})
}
if let Some((style, commands)) = &mc.current_line {
paths.push(DrawPath::Stroke {
style,
commands: commands.to_owned(),
is_closed: false, // TODO: Determine this
})
}
let shape = DistilledShape {
paths,
shape_bounds: mc.custom_shape_bounds.clone(),
edge_bounds: mc.custom_shape_bounds.clone(),
id: mc.id(),
};
if let Some(handle) = mc.custom_shape {
context.renderer.replace_shape(shape, handle);
} else {
mc.custom_shape = Some(context.renderer.register_shape(shape));
}
}
mc.drawing.run_frame(context);
}
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
context.transform_stack.push(&*self.transform());
crate::display_object::render_children(context, &self.0.read().children);
if let Some(handle) = self.0.read().custom_shape {
context
.renderer
.render_shape(handle, context.transform_stack.transform());
}
self.0.read().drawing.render(context);
context.transform_stack.pop();
}
fn self_bounds(&self) -> BoundingBox {
self.0.read().custom_shape_bounds.clone()
self.0.read().drawing.self_bounds()
}
fn hit_test(&self, point: (Twips, Twips)) -> bool {

207
core/src/drawing.rs Normal file
View File

@ -0,0 +1,207 @@
use crate::backend::render::ShapeHandle;
use crate::bounding_box::BoundingBox;
use crate::context::{RenderContext, UpdateContext};
use crate::shape_utils::{DistilledShape, DrawCommand, DrawPath};
use swf::{FillStyle, LineStyle};
#[derive(Clone, Debug)]
pub struct Drawing {
render_handle: Option<ShapeHandle>,
shape_bounds: BoundingBox,
edge_bounds: BoundingBox,
dirty: bool,
fills: Vec<(FillStyle, Vec<DrawCommand>)>,
lines: Vec<(LineStyle, Vec<DrawCommand>)>,
current_fill: Option<(FillStyle, Vec<DrawCommand>)>,
current_line: Option<(LineStyle, Vec<DrawCommand>)>,
}
impl Drawing {
pub fn new() -> Self {
Self {
render_handle: None,
shape_bounds: BoundingBox::default(),
edge_bounds: BoundingBox::default(),
dirty: false,
fills: Vec::new(),
lines: Vec::new(),
current_fill: None,
current_line: None,
}
}
pub fn set_fill_style(&mut self, style: Option<FillStyle>) {
// TODO: If current_fill is not closed, we should close it and also close current_line
if let Some(existing) = self.current_fill.take() {
self.fills.push(existing);
}
if let Some(style) = style {
self.current_fill = Some((style, Vec::new()));
}
self.dirty = true;
}
pub fn clear(&mut self) {
self.current_fill = None;
self.current_line = None;
self.fills.clear();
self.lines.clear();
self.edge_bounds = BoundingBox::default();
self.shape_bounds = BoundingBox::default();
self.dirty = true;
}
pub fn set_line_style(&mut self, style: Option<LineStyle>) {
if let Some(existing) = self.current_line.take() {
self.lines.push(existing);
}
if let Some(style) = style {
self.current_line = Some((style, Vec::new()));
}
self.dirty = true;
}
pub fn draw_command(&mut self, command: DrawCommand) {
let mut include_last = false;
match command {
DrawCommand::MoveTo { .. } => {}
DrawCommand::LineTo { x, y } => {
self.shape_bounds.encompass(x, y);
self.edge_bounds.encompass(x, y);
include_last = true;
}
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
self.shape_bounds.encompass(x1, y1);
self.shape_bounds.encompass(x2, y2);
self.edge_bounds.encompass(x1, y1);
self.edge_bounds.encompass(x2, y2);
include_last = true;
}
}
if let Some((_, commands)) = &mut self.current_line {
commands.push(command.clone());
}
if let Some((_, commands)) = &mut self.current_fill {
commands.push(command);
}
if include_last {
if let Some(command) = self
.current_fill
.as_ref()
.and_then(|(_, commands)| commands.last().cloned())
{
match command {
DrawCommand::MoveTo { x, y } => {
self.shape_bounds.encompass(x, y);
self.edge_bounds.encompass(x, y);
}
DrawCommand::LineTo { x, y } => {
self.shape_bounds.encompass(x, y);
self.edge_bounds.encompass(x, y);
}
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
self.shape_bounds.encompass(x1, y1);
self.shape_bounds.encompass(x2, y2);
self.edge_bounds.encompass(x1, y1);
self.edge_bounds.encompass(x2, y2);
}
}
}
if let Some(command) = self
.current_line
.as_ref()
.and_then(|(_, commands)| commands.last().cloned())
{
match command {
DrawCommand::MoveTo { x, y } => {
self.shape_bounds.encompass(x, y);
self.edge_bounds.encompass(x, y);
}
DrawCommand::LineTo { x, y } => {
self.shape_bounds.encompass(x, y);
self.edge_bounds.encompass(x, y);
}
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
self.shape_bounds.encompass(x1, y1);
self.shape_bounds.encompass(x2, y2);
self.edge_bounds.encompass(x1, y1);
self.edge_bounds.encompass(x2, y2);
}
}
}
}
self.dirty = true;
}
pub fn run_frame(&mut self, context: &mut UpdateContext) {
if self.dirty {
self.dirty = false;
let mut paths = Vec::new();
for (style, commands) in &self.fills {
paths.push(DrawPath::Fill {
style,
commands: commands.to_owned(),
})
}
// TODO: If the current_fill is not closed, we should automatically close current_line
if let Some((style, commands)) = &self.current_fill {
paths.push(DrawPath::Fill {
style,
commands: commands.to_owned(),
})
}
for (style, commands) in &self.lines {
paths.push(DrawPath::Stroke {
style,
commands: commands.to_owned(),
is_closed: false, // TODO: Determine this
})
}
if let Some((style, commands)) = &self.current_line {
paths.push(DrawPath::Stroke {
style,
commands: commands.to_owned(),
is_closed: false, // TODO: Determine this
})
}
let shape = DistilledShape {
paths,
shape_bounds: self.shape_bounds.clone(),
edge_bounds: self.shape_bounds.clone(),
id: 0,
};
if let Some(handle) = self.render_handle {
context.renderer.replace_shape(shape, handle);
} else {
self.render_handle = Some(context.renderer.register_shape(shape));
}
}
}
pub fn render(&self, context: &mut RenderContext) {
if let Some(handle) = self.render_handle {
context
.renderer
.render_shape(handle, context.transform_stack.transform());
}
}
pub fn self_bounds(&self) -> BoundingBox {
self.shape_bounds.clone()
}
}

View File

@ -14,6 +14,7 @@ mod bounding_box;
mod character;
pub mod color_transform;
mod context;
mod drawing;
pub mod events;
mod font;
mod library;