Add colors in Rust (#78)

* 🎨 Add colors in Rust

* 🌿 Use an option for the properties and #[repr(C)]

*  Remove WASM dependency on document.

* 😎 Wrap Fill and stroke in a style struct.

* 📦 Use crate::Color

* Merge Add transactions for temporary modifications to the document

* Run cargo fmt

* Color without a 'U'
This commit is contained in:
0HyperCube
2021-04-21 22:25:06 +01:00
committed by Keavon Chambers
parent 2849b99b59
commit 46c9ef02ca
26 changed files with 689 additions and 407 deletions

View File

@@ -1,58 +0,0 @@
use crate::EditorError;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Color {
red: f32,
green: f32,
blue: f32,
alpha: f32,
}
impl Color {
pub const BLACK: Color = Color::from_unsafe(0., 0., 0.);
pub const WHITE: Color = Color::from_unsafe(1., 1., 1.);
pub const RED: Color = Color::from_unsafe(1., 0., 0.);
pub const GREEN: Color = Color::from_unsafe(0., 1., 0.);
pub const BLUE: Color = Color::from_unsafe(0., 0., 1.);
pub fn from_rgbaf32(red: f32, green: f32, blue: f32, alpha: f32) -> Result<Color, EditorError> {
let color = Color { red, green, blue, alpha };
if [red, green, blue, alpha].iter().any(|c| c.is_sign_negative() || !c.is_finite()) {
Err(color)?
}
Ok(color)
}
const fn from_unsafe(red: f32, green: f32, blue: f32) -> Color {
Color { red, green, blue, alpha: 1. }
}
pub fn from_rgb8(red: u8, green: u8, blue: u8) -> Color {
Color::from_rgba8(red, green, blue, 255)
}
pub fn from_rgba8(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
let map = |int_color| int_color as f32 / 255.0;
Color {
red: map(red),
green: map(green),
blue: map(blue),
alpha: map(alpha),
}
}
pub fn r(&self) -> f32 {
self.red
}
pub fn g(&self) -> f32 {
self.green
}
pub fn b(&self) -> f32 {
self.blue
}
pub fn a(&self) -> f32 {
self.alpha
}
pub fn components(&self) -> (f32, f32, f32, f32) {
(self.red, self.green, self.blue, self.alpha)
}
}

View File

@@ -14,30 +14,30 @@ impl Dispatcher {
match event {
Event::SelectTool(tool_name) => {
editor_state.tool_state.active_tool_type = *tool_name;
editor_state.tool_state.tool_data.active_tool_type = *tool_name;
self.dispatch_response(Response::SetActiveTool { tool_name: tool_name.to_string() });
}
Event::SelectPrimaryColor(color) => {
editor_state.tool_state.primary_color = *color;
editor_state.tool_state.document_tool_data.primary_color = *color;
}
Event::SelectSecondaryColor(color) => {
editor_state.tool_state.secondary_color = *color;
editor_state.tool_state.document_tool_data.secondary_color = *color;
}
Event::SwapColors => {
editor_state.tool_state.swap_colors();
}
Event::ResetColors => {
editor_state.tool_state.primary_color = Color::BLACK;
editor_state.tool_state.secondary_color = Color::WHITE;
editor_state.tool_state.document_tool_data.primary_color = Color::BLACK;
editor_state.tool_state.document_tool_data.secondary_color = Color::WHITE;
}
Event::MouseDown(mouse_state) => {
editor_state.tool_state.mouse_state = *mouse_state;
editor_state.tool_state.document_tool_data.mouse_state = *mouse_state;
}
Event::MouseUp(mouse_state) => {
editor_state.tool_state.mouse_state = *mouse_state;
editor_state.tool_state.document_tool_data.mouse_state = *mouse_state;
}
Event::MouseMove(pos) => {
editor_state.tool_state.mouse_state.position = *pos;
editor_state.tool_state.document_tool_data.mouse_state.position = *pos;
}
Event::KeyUp(key) => (),
Event::KeyDown(key) => {
@@ -58,31 +58,31 @@ impl Dispatcher {
log::debug!("set log verbosity to trace");
}
Key::KeyV => {
editor_state.tool_state.active_tool_type = ToolType::Select;
editor_state.tool_state.tool_data.active_tool_type = ToolType::Select;
self.dispatch_response(Response::SetActiveTool {
tool_name: ToolType::Select.to_string(),
});
}
Key::KeyL => {
editor_state.tool_state.active_tool_type = ToolType::Line;
editor_state.tool_state.tool_data.active_tool_type = ToolType::Line;
self.dispatch_response(Response::SetActiveTool {
tool_name: ToolType::Line.to_string(),
});
}
Key::KeyM => {
editor_state.tool_state.active_tool_type = ToolType::Rectangle;
editor_state.tool_state.tool_data.active_tool_type = ToolType::Rectangle;
self.dispatch_response(Response::SetActiveTool {
tool_name: ToolType::Rectangle.to_string(),
});
}
Key::KeyY => {
editor_state.tool_state.active_tool_type = ToolType::Shape;
editor_state.tool_state.tool_data.active_tool_type = ToolType::Shape;
self.dispatch_response(Response::SetActiveTool {
tool_name: ToolType::Shape.to_string(),
});
}
Key::KeyE => {
editor_state.tool_state.active_tool_type = ToolType::Ellipse;
editor_state.tool_state.tool_data.active_tool_type = ToolType::Ellipse;
self.dispatch_response(Response::SetActiveTool {
tool_name: ToolType::Ellipse.to_string(),
});
@@ -95,7 +95,11 @@ impl Dispatcher {
}
}
let (responses, operations) = editor_state.tool_state.active_tool()?.handle_input(event, &editor_state.document);
let (responses, operations) = editor_state
.tool_state
.tool_data
.active_tool()?
.handle_input(event, &editor_state.document, &editor_state.tool_state.document_tool_data);
self.dispatch_operations(&mut editor_state.document, operations);
// TODO - Dispatch Responses

View File

@@ -1,7 +1,6 @@
#[macro_use]
mod macros;
mod color;
mod dispatcher;
mod error;
pub mod hint;
@@ -12,7 +11,7 @@ pub mod workspace;
pub use error::EditorError;
#[doc(inline)]
pub use color::Color;
pub use document_core::color::Color;
#[doc(inline)]
pub use dispatcher::events;
@@ -21,7 +20,7 @@ pub use dispatcher::events;
pub use dispatcher::Callback;
use dispatcher::Dispatcher;
use document_core::Document;
use document_core::document::Document;
use tools::ToolFsmState;
use workspace::Workspace;

View File

@@ -3,11 +3,13 @@ use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Crop;
impl Tool for Crop {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@@ -2,8 +2,11 @@ use crate::events::{Event, Response};
use crate::events::{Key, MouseKeys, ViewportPosition};
use crate::tools::{Fsm, Tool};
use crate::Document;
use document_core::layers::style;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Ellipse {
fsm_state: EllipseToolFsmState,
@@ -11,10 +14,10 @@ pub struct Ellipse {
}
impl Tool for Ellipse {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
let mut responses = Vec::new();
let mut operations = Vec::new();
self.fsm_state = self.fsm_state.transition(event, document, &mut self.data, &mut responses, &mut operations);
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
(responses, operations)
}
@@ -39,7 +42,7 @@ struct EllipseToolData {
impl Fsm for EllipseToolFsmState {
type ToolData = EllipseToolData;
fn transition(self, event: &Event, document: &Document, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
match (self, event) {
(EllipseToolFsmState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => {
data.drag_start = mouse_state.position;
@@ -62,6 +65,7 @@ impl Fsm for EllipseToolFsmState {
cx: data.drag_start.x as f64,
cy: data.drag_start.y as f64,
r: data.drag_start.distance(&mouse_state),
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
});
EllipseToolFsmState::LmbDown
@@ -78,6 +82,7 @@ impl Fsm for EllipseToolFsmState {
cx: data.drag_start.x as f64,
cy: data.drag_start.y as f64,
r: data.drag_start.distance(&mouse_state.position),
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
});
operations.push(Operation::CommitTransaction);

View File

@@ -2,8 +2,11 @@ use crate::events::{Event, Response};
use crate::events::{Key, MouseKeys, ViewportPosition};
use crate::tools::{Fsm, Tool};
use crate::Document;
use document_core::layers::style;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Line {
fsm_state: LineToolFsmState,
@@ -11,10 +14,10 @@ pub struct Line {
}
impl Tool for Line {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
let mut responses = Vec::new();
let mut operations = Vec::new();
self.fsm_state = self.fsm_state.transition(event, document, &mut self.data, &mut responses, &mut operations);
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
(responses, operations)
}
@@ -39,7 +42,7 @@ struct LineToolData {
impl Fsm for LineToolFsmState {
type ToolData = LineToolData;
fn transition(self, event: &Event, document: &Document, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
match (self, event) {
(LineToolFsmState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => {
data.drag_start = mouse_state.position;
@@ -64,6 +67,7 @@ impl Fsm for LineToolFsmState {
y0: start.y as f64,
x1: end.x as f64,
y1: end.y as f64,
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), None),
});
LineToolFsmState::Ready

View File

@@ -17,47 +17,64 @@ use document_core::Operation;
use std::{collections::HashMap, fmt};
pub trait Tool {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>);
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>);
}
pub trait Fsm {
type ToolData;
fn transition(self, event: &Event, document: &Document, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self;
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self;
}
pub struct ToolFsmState {
pub struct DocumentToolData {
pub mouse_state: MouseState,
pub mod_keys: ModKeys,
pub trace: Trace,
pub primary_color: Color,
pub secondary_color: Color,
}
pub struct ToolData {
pub active_tool_type: ToolType,
pub tools: HashMap<ToolType, Box<dyn Tool>>,
tool_settings: HashMap<ToolType, ToolSettings>,
}
impl ToolData {
pub fn active_tool(&mut self) -> Result<&mut Box<dyn Tool>, EditorError> {
self.tools.get_mut(&self.active_tool_type).ok_or(EditorError::UnknownTool)
}
}
pub struct ToolFsmState {
pub document_tool_data: DocumentToolData,
pub tool_data: ToolData,
pub trace: Trace,
}
impl Default for ToolFsmState {
fn default() -> Self {
ToolFsmState {
mouse_state: MouseState::default(),
mod_keys: ModKeys::default(),
trace: Trace::new(),
primary_color: Color::BLACK,
secondary_color: Color::WHITE,
active_tool_type: ToolType::Select,
tools: gen_tools_hash_map! {
Select => select::Select,
Crop => crop::Crop,
Navigate => navigate::Navigate,
Sample => sample::Sample,
Path => path::Path,
Pen => pen::Pen,
Line => line::Line,
Rectangle => rectangle::Rectangle,
Ellipse => ellipse::Ellipse,
Shape => shape::Shape,
tool_data: ToolData {
active_tool_type: ToolType::Select,
tools: gen_tools_hash_map! {
Select => select::Select,
Crop => crop::Crop,
Navigate => navigate::Navigate,
Sample => sample::Sample,
Path => path::Path,
Pen => pen::Pen,
Line => line::Line,
Rectangle => rectangle::Rectangle,
Ellipse => ellipse::Ellipse,
Shape => shape::Shape,
},
tool_settings: default_tool_settings(),
},
document_tool_data: DocumentToolData {
mouse_state: MouseState::default(),
mod_keys: ModKeys::default(),
primary_color: Color::BLACK,
secondary_color: Color::WHITE,
},
tool_settings: default_tool_settings(),
}
}
}
@@ -69,17 +86,13 @@ impl ToolFsmState {
pub fn record_trace_point(&mut self) {
self.trace.push(TracePoint {
mouse_state: self.mouse_state,
mod_keys: self.mod_keys,
mouse_state: self.document_tool_data.mouse_state,
mod_keys: self.document_tool_data.mod_keys,
})
}
pub fn active_tool(&mut self) -> Result<&mut Box<dyn Tool>, EditorError> {
self.tools.get_mut(&self.active_tool_type).ok_or(EditorError::UnknownTool)
}
pub fn swap_colors(&mut self) {
std::mem::swap(&mut self.primary_color, &mut self.secondary_color);
std::mem::swap(&mut self.document_tool_data.primary_color, &mut self.document_tool_data.secondary_color);
}
}

View File

@@ -3,11 +3,13 @@ use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Navigate;
impl Tool for Navigate {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@@ -3,11 +3,13 @@ use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Path;
impl Tool for Path {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@@ -3,11 +3,13 @@ use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Pen;
impl Tool for Pen {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@@ -2,8 +2,11 @@ use crate::events::{Event, Response};
use crate::events::{Key, MouseKeys, ViewportPosition};
use crate::tools::{Fsm, Tool};
use crate::Document;
use document_core::layers::style;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Rectangle {
fsm_state: RectangleToolFsmState,
@@ -11,10 +14,10 @@ pub struct Rectangle {
}
impl Tool for Rectangle {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
let mut responses = Vec::new();
let mut operations = Vec::new();
self.fsm_state = self.fsm_state.transition(event, document, &mut self.data, &mut responses, &mut operations);
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
(responses, operations)
}
@@ -39,7 +42,7 @@ struct RectangleToolData {
impl Fsm for RectangleToolFsmState {
type ToolData = RectangleToolData;
fn transition(self, event: &Event, document: &Document, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
match (self, event) {
(RectangleToolFsmState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => {
data.drag_start = mouse_state.position;
@@ -65,6 +68,7 @@ impl Fsm for RectangleToolFsmState {
y0: start.y as f64,
x1: end.x as f64,
y1: end.y as f64,
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
});
RectangleToolFsmState::Ready

View File

@@ -3,11 +3,13 @@ use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Sample;
impl Tool for Sample {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@@ -4,6 +4,8 @@ use crate::tools::{Fsm, Tool};
use crate::Document;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Select {
fsm_state: SelectToolFsmState,
@@ -11,10 +13,10 @@ pub struct Select {
}
impl Tool for Select {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
let mut responses = Vec::new();
let mut operations = Vec::new();
self.fsm_state = self.fsm_state.transition(event, document, &mut self.data, &mut responses, &mut operations);
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
(responses, operations)
}
@@ -39,7 +41,7 @@ struct SelectToolData;
impl Fsm for SelectToolFsmState {
type ToolData = SelectToolData;
fn transition(self, event: &Event, document: &Document, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
match (self, event) {
(SelectToolFsmState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => SelectToolFsmState::LmbDown,

View File

@@ -2,8 +2,11 @@ use crate::events::{Event, Response};
use crate::events::{Key, MouseKeys, ViewportPosition};
use crate::tools::{Fsm, Tool};
use crate::Document;
use document_core::layers::style;
use document_core::Operation;
use super::DocumentToolData;
#[derive(Default)]
pub struct Shape {
fsm_state: ShapeToolFsmState,
@@ -11,10 +14,10 @@ pub struct Shape {
}
impl Tool for Shape {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<Response>, Vec<Operation>) {
let mut responses = Vec::new();
let mut operations = Vec::new();
self.fsm_state = self.fsm_state.transition(event, document, &mut self.data, &mut responses, &mut operations);
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
(responses, operations)
}
@@ -40,7 +43,7 @@ struct ShapeToolData {
impl Fsm for ShapeToolFsmState {
type ToolData = ShapeToolData;
fn transition(self, event: &Event, document: &Document, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
match (self, event) {
(ShapeToolFsmState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => {
data.drag_start = mouse_state.position;
@@ -60,6 +63,7 @@ impl Fsm for ShapeToolFsmState {
let start = data.drag_start;
let end = mouse_state.position;
// TODO: Set the sides value and use it for the operation.
let sides = data.sides;
operations.push(Operation::AddShape {
path: vec![],
@@ -69,6 +73,7 @@ impl Fsm for ShapeToolFsmState {
x1: end.x as f64,
y1: end.y as f64,
sides: 6,
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
});
ShapeToolFsmState::Ready