Implement anchor and handle point rendering with the Path Tool (#353)

* Implement Path Tool

* Draw a red rectangle where the first point on the shape is

* Correctly render anchors, handles, and connecting lines

* Fix drain() which can panic

* Refactor frontend messages to work as return values not callbacks

* Reduce the number of unnecessary frontend updates

* Fix stack overflow by using a loop

* Group Document Render calls and put them at the end

* Speed hacks for dirtification

* Add performance

* Bunch folder changed updates

* Add triggers to redraw overlays to movement_handler

* Polish the pixel-perfect rendering of vector manipulators

* Restore scrollbars that were disabled

* Cleanup

* WIP Add shape outline rendering

* Fix compiling

* Add outlines to selected shapes

* Fix outlines rendering over handles and anchors

* Fix dirtification

* Add a comment

* Address code review feedback

* Formatting

* Small tweaks

Co-authored-by: Oliver Davies <oliver@psyfer.io>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Keavon Chambers
2021-08-29 00:10:54 -07:00
co-authored by Oliver Davies Dennis Kobert
parent 4421b79ec4
commit 024ce9dcfa
41 changed files with 759 additions and 282 deletions
+46 -39
View File
@@ -1,4 +1,4 @@
use crate::{frontend::FrontendMessageHandler, message_prelude::*, Callback, EditorError};
use crate::{message_prelude::*, EditorError};
pub use crate::document::DocumentsMessageHandler;
pub use crate::input::{InputMapper, InputPreprocessor};
@@ -8,49 +8,47 @@ use crate::global::GlobalMessageHandler;
use std::collections::VecDeque;
pub struct Dispatcher {
frontend_message_handler: FrontendMessageHandler,
input_preprocessor: InputPreprocessor,
input_mapper: InputMapper,
global_message_handler: GlobalMessageHandler,
tool_message_handler: ToolMessageHandler,
documents_message_handler: DocumentsMessageHandler,
messages: VecDeque<Message>,
pub responses: Vec<FrontendMessage>,
}
const GROUP_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateLayer),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::ExpandFolder),
MessageDiscriminant::Tool(ToolMessageDiscriminant::SelectedLayersChanged),
];
impl Dispatcher {
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> {
let message = message.into();
self.messages.push_back(message.into());
use Message::*;
if !(matches!(
message,
Message::InputPreprocessor(_)
| Message::InputMapper(_)
| Message::Documents(DocumentsMessage::Document(DocumentMessage::RenderDocument))
| Message::Frontend(FrontendMessage::UpdateCanvas { .. })
| Message::Frontend(FrontendMessage::UpdateScrollbars { .. })
| Message::Frontend(FrontendMessage::SetCanvasZoom { .. })
| Message::Frontend(FrontendMessage::SetCanvasRotation { .. })
) || MessageDiscriminant::from(&message).local_name().ends_with("MouseMove"))
{
log::trace!("Message: {:?}", message);
//log::trace!("Hints:{:?}", self.input_mapper.hints(self.collect_actions()));
}
match message {
NoOp => (),
Documents(message) => self.documents_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages),
Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages),
Tool(message) => self
.tool_message_handler
.process_action(message, (self.documents_message_handler.active_document(), &self.input_preprocessor), &mut self.messages),
Frontend(message) => self.frontend_message_handler.process_action(message, (), &mut self.messages),
InputPreprocessor(message) => self.input_preprocessor.process_action(message, (), &mut self.messages),
InputMapper(message) => {
let actions = self.collect_actions();
self.input_mapper.process_action(message, (&self.input_preprocessor, actions), &mut self.messages)
while let Some(message) = self.messages.pop_front() {
if GROUP_MESSAGES.contains(&message.to_discriminant()) && self.messages.contains(&message) {
continue;
}
log_message(&message);
match message {
NoOp => (),
Documents(message) => self.documents_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages),
Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages),
Tool(message) => self
.tool_message_handler
.process_action(message, (self.documents_message_handler.active_document(), &self.input_preprocessor), &mut self.messages),
Frontend(message) => self.responses.push(message),
InputPreprocessor(message) => self.input_preprocessor.process_action(message, (), &mut self.messages),
InputMapper(message) => {
let actions = self.collect_actions();
self.input_mapper.process_action(message, (&self.input_preprocessor, actions), &mut self.messages)
}
}
}
if let Some(message) = self.messages.pop_front() {
self.handle_message(message)?;
}
Ok(())
}
@@ -58,7 +56,6 @@ impl Dispatcher {
pub fn collect_actions(&self) -> ActionList {
//TODO: reduce the number of heap allocations
let mut list = Vec::new();
list.extend(self.frontend_message_handler.actions());
list.extend(self.input_preprocessor.actions());
list.extend(self.input_mapper.actions());
list.extend(self.global_message_handler.actions());
@@ -67,24 +64,36 @@ impl Dispatcher {
list
}
pub fn new(callback: Callback) -> Dispatcher {
pub fn new() -> Dispatcher {
Dispatcher {
frontend_message_handler: FrontendMessageHandler::new(callback),
input_preprocessor: InputPreprocessor::default(),
global_message_handler: GlobalMessageHandler::new(),
input_mapper: InputMapper::default(),
documents_message_handler: DocumentsMessageHandler::default(),
tool_message_handler: ToolMessageHandler::default(),
messages: VecDeque::new(),
responses: vec![],
}
}
}
fn log_message(message: &Message) {
use Message::*;
if log::max_level() == log::LevelFilter::Trace
&& !(matches!(
message,
InputPreprocessor(_) | Frontend(FrontendMessage::SetCanvasZoom { .. }) | Frontend(FrontendMessage::SetCanvasRotation { .. })
) || MessageDiscriminant::from(message).local_name().ends_with("MouseMove"))
{
log::trace!("Message: {:?}", message);
//log::trace!("Hints:{:?}", self.input_mapper.hints(self.collect_actions()));
}
}
#[cfg(test)]
mod test {
use crate::{document::DocumentMessageHandler, message_prelude::*, misc::test_utils::EditorTestUtils, Editor};
use graphene::{color::Color, Operation};
use log::info;
fn init_logger() {
let _ = env_logger::builder().is_test(true).try_init();
@@ -95,9 +104,7 @@ mod test {
/// 2. A blue shape
/// 3. A green ellipse
fn create_editor_with_three_layers() -> Editor {
let mut editor = Editor::new(Box::new(|e| {
info!("Got frontend message: {:?}", e);
}));
let mut editor = Editor::new();
editor.select_primary_color(Color::RED);
editor.draw_rect(100., 200., 300., 400.);
+2 -1
View File
@@ -1,5 +1,6 @@
use crate::message_prelude::*;
use graphite_proc_macros::*;
use serde::{Deserialize, Serialize};
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
@@ -16,7 +17,7 @@ where
}
#[impl_message]
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Message {
NoOp,
#[child]