Compare commits

..

No commits in common. "cb8bc30a150cc798db2d28f4d9b65924fdc6641f" and "e51f6c431c1a418d6bc8e64726695b072dde77fc" have entirely different histories.

11 changed files with 630 additions and 459 deletions

781
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,26 +7,25 @@ default-run = "stiletto"
[dependencies]
log = "0.4"
druid = { version = "0.7", features = ["im", "svg"] }
druid = { version = "0.7.0", features = ["im", "svg"] }
im = { version = "*" }
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.3.0"
serde_json = "1.0"
clap = "3.1"
clap = "3.0.0-beta.2"
[target.'cfg(target_os="linux")'.dependencies.gtk]
version = "0.14"
version = "0.9.2"
features = ["v3_22"]
[target.'cfg(target_os="linux")'.dependencies.gio]
version = "0.14"
version = "0.9.1"
features = ["v2_56"]
[target.'cfg(target_os="linux")'.dependencies.gdk]
version = "0.14"
version = "0.13.2"
features = ["v3_22"]
[patch.crates-io]
#druid = { git = "https://github.com/jneem/druid", branch = "pointer", features = ["im", "svg"] }
druid = { path= "../druid/druid/" }
druid = { git = "https://github.com/jneem/druid", branch = "pointer", features = ["im", "svg"] }

View File

@ -14,39 +14,35 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use clap::{Arg, Command};
use clap::{Arg, App};
use stiletto::migration::open_stiletto_document;
use std::fs::File;
use std::path::PathBuf;
fn main() {
let matches = Command::new("Stiletto Migration CLI")
let matches = App::new("Stiletto Migration CLI")
.version("0.1.0")
.author("Stiletto Authors")
.about("Migrate a stlt file to the latest version")
.arg(
Arg::new("INPUT")
.help("Sets the input file to use")
.required(true)
.index(1),
)
.arg(
Arg::new("OUTPUT")
.help("Sets the output file to use")
.required(true)
.index(2),
)
.arg(Arg::new("INPUT")
.about("Sets the input file to use")
.required(true)
.index(1))
.arg(Arg::new("OUTPUT")
.about("Sets the output file to use")
.required(true)
.index(2))
.get_matches();
let input_path = matches.value_of("INPUT").unwrap();
let output_path = matches.value_of("OUTPUT").unwrap();
let input_path = matches.value_of("INPUT").unwrap();
let output_path = matches.value_of("OUTPUT").unwrap();
let out_file = File::create(&output_path).expect("Cannot create output fle");
let out_file = File::create(&output_path).expect("Cannot create output fle");
let result = open_stiletto_document(&PathBuf::from(&input_path));
if let Ok(document) = result {
let snapshot = document.migrate_to_latest();
snapshot.to_writer(out_file);
}
let result = open_stiletto_document(&PathBuf::from(&input_path));
if let Ok(document) = result {
let snapshot = document.migrate_to_latest();
snapshot.to_writer(out_file);
}
}

View File

@ -27,6 +27,12 @@ pub struct Path {
pub kurbo_path: druid::kurbo::BezPath,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "druid::Color")]
enum ColorDef {
Rgba32(u32),
}
struct Interval {
start_pos: f64,
end_pos: f64,
@ -80,29 +86,11 @@ pub enum CanvasElement {
Freehand {
path: Path,
thickness: f64,
#[serde(
serialize_with = "serialize_color",
deserialize_with = "deserialize_color"
)]
#[serde(with = "ColorDef")]
stroke_color: druid::Color,
},
}
fn serialize_color<S>(c: &druid::Color, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ser.serialize_u32(c.as_rgba_u32())
}
fn deserialize_color<'de, D>(de: D) -> Result<druid::Color, D::Error>
where
D: Deserializer<'de>,
{
let c = u32::deserialize(de)?;
Ok(druid::Color::from_rgba32_u32(c))
}
impl CanvasElement {
pub fn bounding_box(&self) -> druid::Rect {
match self {
@ -205,16 +193,11 @@ impl Serialize for Path {
{
use druid::kurbo::PathEl;
serializer.collect_seq(
self.kurbo_path
.iter()
.filter_map(|path_el| match path_el {
PathEl::MoveTo(pt) => Some(Into::<(f64, f64)>::into(pt)),
PathEl::LineTo(pt) => Some(Into::<(f64, f64)>::into(pt)),
_ => None,
})
.collect::<Vec<(f64, f64)>>(),
)
serializer.collect_seq(self.kurbo_path.iter().filter_map(|path_el| match path_el {
PathEl::MoveTo(pt) => Some(Into::<(f64, f64)>::into(pt)),
PathEl::LineTo(pt) => Some(Into::<(f64, f64)>::into(pt)),
_ => None,
}).collect::<Vec<(f64, f64)>>())
}
}

View File

@ -19,9 +19,9 @@ use std::vec::Vec;
pub mod canvas;
pub mod history;
pub mod migration;
pub mod tool;
pub mod widget;
pub mod migration;
pub mod commands {
use druid::Selector;

View File

@ -24,20 +24,23 @@ use druid::widget::prelude::*;
use druid::widget::{
Align, Button, Controller, CrossAxisAlignment, Flex, List, SizedBox, WidgetExt,
};
use druid::gesture::DruidGestureRecognizer;
use druid::{
AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec,
Handled, Lens, Target, WidgetId, WindowDesc,
Handled, Lens, PointerEventPolicy, Target, WidgetId, WindowDesc,
};
use im::Vector;
use stiletto::tool::CanvasToolParams;
use stiletto::widget::tool_ctx::CanvasToolCtx;
use stiletto::widget::tool_ctx::{CanvasToolCtx};
use stiletto::widget::{build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget};
use stiletto::DocumentSnapshot;
pub fn main() {
let window = WindowDesc::new(build_ui())
let window = WindowDesc::new(build_ui)
.set_pointer_event_policy(PointerEventPolicy::UseMixedApi)
.set_gesture_recognizer(Box::new(DruidGestureRecognizer::new()))
.window_size((1024.0, 1400.0))
.title(|data: &StilettoState, _env: &Env| {
let doc_name = if let Some(path) = &data.current_file_path {
@ -77,7 +80,7 @@ pub fn main() {
current_file_path: None,
};
AppLauncher::with_window(window)
.log_to_console()
.use_simple_logger()
.delegate(Delegate)
.launch(canvas_data)
.expect("launch failed");
@ -137,19 +140,17 @@ fn build_ui() -> impl Widget<StilettoState> {
ctx.submit_command(Command::new(CanvasWidget::SCROLL, 30.0, canvas_id));
},
))
.with_child(
Button::new("Save").on_click(move |ctx, data: &mut StilettoState, _| {
if data.current_file_path.is_some() {
ctx.submit_command(Command::new(commands::SAVE_FILE, (), Target::Auto));
} else {
ctx.submit_command(Command::new(
druid::commands::SHOW_SAVE_PANEL,
save_dialog_options_clone.clone(),
Target::Auto,
))
}
}),
)
.with_child(Button::new("Save").on_click(move |ctx, data: &mut StilettoState, _| {
if data.current_file_path.is_some() {
ctx.submit_command(Command::new(commands::SAVE_FILE, (), Target::Auto));
} else {
ctx.submit_command(Command::new(
druid::commands::SHOW_SAVE_PANEL,
save_dialog_options_clone.clone(),
Target::Auto,
))
}
}))
.with_child(Button::new("Save As").on_click(move |ctx, _, _| {
ctx.submit_command(Command::new(
druid::commands::SHOW_SAVE_PANEL,
@ -276,7 +277,8 @@ impl AppDelegate<StilettoState> for Delegate {
let res_file = File::create(&path_buf);
if let Ok(f) = res_file {
let write_res = data.canvas.get_document_snapshot().to_writer(f);
let write_res =
data.canvas.get_document_snapshot().to_writer(f);
if write_res.is_err() {
warn!("Error while saving: {:?}", write_res.err());
} else {

View File

@ -47,9 +47,7 @@ impl StilettoDocument {
pub fn migrate_to_next(self) -> StilettoDocument {
match self {
StilettoDocument::DocumentSnapshot0_1(snapshot) => {
StilettoDocument::DocumentSnapshot0_2(snapshot)
}
StilettoDocument::DocumentSnapshot0_1(snapshot) => StilettoDocument::DocumentSnapshot0_2(snapshot) ,
StilettoDocument::DocumentSnapshot0_2(_) => self,
}
}
@ -62,11 +60,12 @@ impl StilettoDocument {
assert!(matches!(self, StilettoDocument::DocumentSnapshot0_2(_)));
match self {
StilettoDocument::DocumentSnapshot0_2(snapshot) => snapshot,
_ => panic!("Wrong Document Snapshot Version"),
_ => panic!("Wrong Document Snapshot Version")
}
}
}
impl fmt::Display for MigrationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
@ -75,11 +74,10 @@ impl fmt::Display for MigrationError {
MigrationError::IoError(e) => e.fmt(f),
MigrationError::UnexpectedVersion(major, minor, e_major, e_minor) => {
write!(
f,
"Unexpected version ({}, {}): was expecting ({}, {})",
f, "Unexpected version ({}, {}): was expecting ({}, {})",
major, minor, e_major, e_minor
)
}
},
}
}
}
@ -116,12 +114,12 @@ impl From<serde_bare::Error> for MigrationError {
fn open_0_1(path: &PathBuf) -> Result<StilettoDocument, MigrationError> {
let f = File::open(path)?;
let document_snapshot: DocumentSnapshot = serde_json::from_reader(f)?;
if document_snapshot.format_version_major != 0 || document_snapshot.format_version_minor != 1 {
if document_snapshot.format_version_major != 0 ||
document_snapshot.format_version_minor != 1 {
Err(MigrationError::UnexpectedVersion(
document_snapshot.format_version_major,
document_snapshot.format_version_minor,
0,
1,
0, 1
))
} else {
Ok(StilettoDocument::DocumentSnapshot0_1(document_snapshot))
@ -141,3 +139,4 @@ pub fn open_stiletto_document(path: &PathBuf) -> Result<StilettoDocument, Migrat
open_0_2(path)
}

View File

@ -16,7 +16,7 @@
use druid::{Color, Data};
#[derive(Clone, Data, Debug)]
#[derive(Clone, Data)]
pub enum CanvasToolParams {
Pen { thickness: f64, color: Color },
Eraser,

View File

@ -16,17 +16,18 @@
use im::Vector;
use super::tool_ctx::CanvasToolCtx;
use super::tool_ctx::{CanvasToolCtx};
use crate::canvas::Canvas;
use crate::history::VersionedCanvas;
use crate::DocumentSnapshot;
use druid::kurbo::Point;
use druid::scroll_component::ScrollComponent;
use druid::widget::prelude::*;
use druid::widget::Viewport;
use druid::kurbo::Point;
use druid::{Affine, Color, Data, Env, Event, PointerType, Selector};
#[derive(Clone, Data)]
pub struct CanvasState {
versioned_canvas: VersionedCanvas,
@ -86,13 +87,7 @@ impl CanvasState {
&mut self.tool_ctx
}
pub fn handle_event(
&mut self,
ctx: &mut EventCtx,
event: &Event,
transform: Affine,
env: &Env,
) {
pub fn handle_event(&mut self, ctx: &mut EventCtx, event: &Event, transform: Affine, env: &Env) {
self.tool_ctx
.handle_event(ctx, event, &mut self.versioned_canvas, transform, env);
}
@ -109,8 +104,7 @@ impl CanvasWidget {
CanvasWidget {
viewport: Viewport {
content_size: Size::new(0.0, 0.0),
view_origin: Point::new(0.0, 0.0),
view_size: Size::new(0.0, 0.0),
rect: druid::Rect::new(0.0, 0.0, 0.0, 0.0),
},
widget_size: Size::new(0.0, 0.0),
scroll_component: ScrollComponent::new(),
@ -118,14 +112,16 @@ impl CanvasWidget {
}
fn widget_to_viewport(&self) -> Affine {
if !self.viewport.view_size.is_empty() && !self.widget_size.is_empty() {
let scale_x = self.viewport.view_size.width / self.widget_size.width;
let scale_y = self.viewport.view_size.height / self.widget_size.height;
Affine::translate(self.viewport.view_origin.to_vec2())
* Affine::scale_non_uniform(scale_x, scale_y)
let viewport_rect_size = self.viewport.rect.size();
if !viewport_rect_size.is_empty() && !self.widget_size.is_empty() {
let scale_x = viewport_rect_size.width / self.widget_size.width;
let scale_y = viewport_rect_size.height / self.widget_size.height;
Affine::translate(self.viewport.rect.origin().to_vec2())
* Affine::scale_non_uniform(scale_x, scale_y)
} else {
Affine::scale(1.0) // identity
}
}
pub const SCROLL: Selector<f64> = Selector::new("scroll_canvas");
@ -158,25 +154,44 @@ impl Widget<CanvasState> for CanvasWidget {
viewport_transform = Some(Affine::translate((-30.0, 0.0)));
}
}
Event::MouseDown(pointer_event) => {
Event::MouseDown(mouse_event) => {
toggle_eraser_event = true;
enable_temporary_erasing = pointer_event.pointer_type == PointerType::Eraser;
enable_temporary_erasing = false; //mouse_event.pointer_type == PointerType::Eraser;
}
Event::MouseMove(pointer_event) => {
Event::MouseMove(mouse_event) => {
toggle_eraser_event = true;
enable_temporary_erasing = pointer_event.pointer_type == PointerType::Eraser;
enable_temporary_erasing = false; //mouse_event.pointer_type == PointerType::Eraser;
}
Event::MouseUp(pointer_event) => {
Event::MouseUp(mouse_event) => {
toggle_eraser_event = true;
enable_temporary_erasing = pointer_event.pointer_type == PointerType::Eraser;
enable_temporary_erasing = false; //mouse_event.pointer_type == PointerType::Eraser;
}
Event::Wheel(pointer) => {
Event::Wheel(mouse) => {
let transform = self.widget_to_viewport();
viewport_transform = Some(Affine::translate(
(pointer.wheel_delta.to_point()).to_vec2(),
));
viewport_transform = Some(
Affine::translate(
(mouse.wheel_delta.to_point()).to_vec2()
)
);
}
Event::GestureZoom { zoom, center } => {
let transform = self.widget_to_viewport();
let diff = (transform * *center).to_vec2()
- self.viewport.rect.center().to_vec2();
viewport_transform = Some(
Affine::scale(1.0/(1.0 + zoom))
);
log::debug!("{:#?}", viewport_transform);
}
Event::GesturePan(vec2) => {
let transform = self.widget_to_viewport();
viewport_transform = Some(
Affine::translate(
(vec2.to_point()).to_vec2()
)
);
}
_ => {}
}
// TODO: the first eraser toggle is not handled
@ -190,8 +205,8 @@ impl Widget<CanvasState> for CanvasWidget {
} else {
data.handle_event(ctx, event, self.widget_to_viewport(), env);
}
if let Some(transform) = viewport_transform {
let mut new_rect = transform.transform_rect_bbox(self.viewport.view_rect());
if let Some(transform) = viewport_transform {
let mut new_rect = transform.transform_rect_bbox(self.viewport.rect);
if new_rect.x0 <= 0f64 {
new_rect.x1 -= new_rect.x0;
new_rect.x0 = 0f64;
@ -200,7 +215,7 @@ impl Widget<CanvasState> for CanvasWidget {
new_rect.y1 -= new_rect.y0;
new_rect.y0 = 0f64;
}
self.viewport.view_origin = new_rect.origin();
self.viewport.rect = new_rect;
self.scroll_component
.reset_scrollbar_fade(|d| ctx.request_timer(d), env);
ctx.request_paint();
@ -269,14 +284,13 @@ impl Widget<CanvasState> for CanvasWidget {
let page_content_size = data.versioned_canvas.get().content_size();
let transform = self.widget_to_viewport();
//self.viewport.rect =
// transform.transform_rect_bbox(rect)
// .with_origin(self.viewport.rect.origin());
self.viewport.view_size = transform.transform_rect_bbox(rect).size();
self.viewport.rect =
transform.transform_rect_bbox(rect)
.with_origin(self.viewport.rect.origin());
self.viewport.content_size = druid::Size::new(
self.viewport.view_rect().x1.max(page_content_size.width),
self.viewport.view_rect().y1.max(page_content_size.height),
self.viewport.rect.x1.max(page_content_size.width),
self.viewport.rect.y1.max(page_content_size.height),
);
//log::debug!("{:#?}", &self.viewport);

View File

@ -1,6 +1,6 @@
pub mod canvas;
pub mod tool_ctx;
pub mod tool_icon;
pub mod tool_ctx;
pub use canvas::*;
pub use tool_icon::*;

View File

@ -14,14 +14,14 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::tool::{CanvasToolParams, CanvasToolType};
use crate::canvas::{Canvas, CanvasElement};
use crate::history::VersionedCanvas;
use crate::tool::{CanvasToolParams, CanvasToolType};
use druid::kurbo::BezPath;
use druid::{Affine, Data, Env, Event, EventCtx, PaintCtx, PointerButton, PointerEvent};
use druid::{Affine, Data, Env, Event, EventCtx, MouseButton, MouseEvent, PaintCtx};
#[derive(Clone, Data, Debug)]
#[derive(Clone, Data)]
pub enum CanvasToolState {
Idle,
DrawingFreehand {
@ -46,14 +46,9 @@ impl CanvasToolParams {
}
}
fn pressed(pointer_event: &PointerEvent) -> bool {
dbg!(&pointer_event);
dbg!(
pointer_event.buttons.contains(PointerButton::Left)
|| pointer_event.button == PointerButton::Left
);
pointer_event.buttons.contains(PointerButton::Left)
|| pointer_event.button == PointerButton::Left
fn pressed(mouse_event: &MouseEvent) -> bool {
mouse_event.buttons.contains(MouseButton::Left)
|| mouse_event.button == MouseButton::Left
}
impl CanvasToolCtx {
@ -87,13 +82,13 @@ impl CanvasToolCtx {
_env: &Env,
) {
match (&mut self.state, event) {
(CanvasToolState::Idle, Event::MouseDown(pointer_event)) if pressed(pointer_event) => {
(CanvasToolState::Idle, Event::MouseDown(mouse_event)) if pressed(mouse_event) => {
self.state = CanvasToolState::Erasing;
ctx.set_handled();
}
(CanvasToolState::Erasing, Event::MouseMove(pointer_event)) => {
(CanvasToolState::Erasing, Event::MouseMove(mouse_event)) if pressed(mouse_event) => {
let eraser_rect =
druid::Rect::from_center_size(transform * pointer_event.pos, (5.0, 5.0));
druid::Rect::from_center_size(transform * mouse_event.pos, (5.0, 5.0));
let old_elements = vcanvas.get().elements();
let mut new_elements = old_elements.clone();
new_elements.retain(|elem| {
@ -112,7 +107,7 @@ impl CanvasToolCtx {
}
ctx.set_handled();
}
(CanvasToolState::Erasing, Event::MouseUp(pointer_event)) if pressed(pointer_event) => {
(CanvasToolState::Erasing, Event::MouseUp(mouse_event)) if pressed(mouse_event) => {
self.state = CanvasToolState::Idle;
ctx.set_handled();
}
@ -129,9 +124,9 @@ impl CanvasToolCtx {
_env: &Env,
) {
match (&mut self.state, event) {
(CanvasToolState::Idle, Event::MouseDown(pointer_event)) if pressed(pointer_event) => {
(CanvasToolState::Idle, Event::MouseDown(mouse_event)) if pressed(mouse_event) => {
let mut kurbo_path = BezPath::new();
kurbo_path.move_to((pointer_event.pos.x, pointer_event.pos.y));
kurbo_path.move_to((mouse_event.pos.x, mouse_event.pos.y));
if let CanvasToolParams::Pen { thickness, color } = &self.initial_params {
self.state = CanvasToolState::DrawingFreehand {
pen_params: self.initial_params.clone(),
@ -149,33 +144,31 @@ impl CanvasToolCtx {
ref mut current_path,
..
},
Event::MouseMove(pointer_event),
) => {
// TODO(enrico): investigate why mouse move doesn't carry buttons anymore
//if pressed(pointer_event) {
let CanvasElement::Freehand { ref mut path, .. } = current_path;
path.kurbo_path
.line_to((pointer_event.pos.x, pointer_event.pos.y));
Event::MouseMove(mouse_event),
) => if pressed(mouse_event) {
if let CanvasElement::Freehand { ref mut path, .. } = current_path {
path.kurbo_path
.line_to((mouse_event.pos.x, mouse_event.pos.y));
}
ctx.set_handled();
//}
}
(CanvasToolState::DrawingFreehand { .. }, Event::MouseUp(pointer_event))
if pressed(pointer_event) =>
{
(CanvasToolState::DrawingFreehand { .. }, Event::MouseUp(mouse_event)) if pressed(mouse_event) => {
vcanvas.update(move |canvas: &mut Canvas| {
let current_state = std::mem::replace(&mut self.state, CanvasToolState::Idle);
if let CanvasToolState::DrawingFreehand { current_path, .. } = current_state {
let CanvasElement::Freehand {
if let CanvasElement::Freehand {
mut path,
thickness,
stroke_color,
} = current_path;
path.kurbo_path.apply_affine(transform);
canvas.add_element(CanvasElement::Freehand {
path,
thickness,
stroke_color,
});
} = current_path
{
path.kurbo_path.apply_affine(transform);
canvas.add_element(CanvasElement::Freehand {
path,
thickness,
stroke_color,
});
}
}
});
ctx.set_handled();