~vpzom/savg

9474a49d4854a6e39e36471122b199582a7750fb — Colin Reeder 4 years ago 3ec25b0
Migrate to 2018
8 files changed, 262 insertions(+), 220 deletions(-)

M Cargo.lock
M Cargo.toml
M src/color.rs
M src/document.rs
M src/draw.rs
M src/main.rs
M src/save.rs
M src/types.rs
M Cargo.lock => Cargo.lock +2 -0
@@ 1,3 1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "atk-sys"
version = "0.7.0"

M Cargo.toml => Cargo.toml +1 -0
@@ 2,6 2,7 @@
name = "savg"
version = "0.1.0"
authors = ["Colin Reeder <vpzomtrrfrt@gmail.com>"]
edition = "2018"

[dependencies]
gtk = "0.5.0"

M src/color.rs => src/color.rs +3 -13
@@ 34,19 34,9 @@ impl Color {
        let a = src.alpha as f32;

        if a < 1.0 {
            Color::RGBA(RGBA {
                r,
                g,
                b,
                a,
            })
        }
        else {
            Color::RGB(RGB {
                r,
                g,
                b,
            })
            Color::RGBA(RGBA { r, g, b, a })
        } else {
            Color::RGB(RGB { r, g, b })
        }
    }
}

M src/document.rs => src/document.rs +14 -25
@@ 1,5 1,5 @@
use color;
use types::*;
use crate::color;
use crate::types::*;

use std::borrow::Cow;



@@ 29,10 29,7 @@ impl<T> Object<T> {
        self.id
    }
    pub fn new(id: u64, value: T) -> Self {
        Self {
            id,
            value,
        }
        Self { id, value }
    }
    pub fn new_random_id(value: T) -> Self {
        Object::new(rand::thread_rng().gen(), value)


@@ 69,13 66,13 @@ impl Element {
            },
        }
    }
    pub fn as_scalable_mut(&mut self) -> Option<&mut ScalableElement> {
    pub fn as_scalable_mut(&mut self) -> Option<&mut dyn ScalableElement> {
        match self {
            Element::Rect(rect) => Some(rect),
            Element::Ellipse(ellipse) => Some(ellipse),
        }
    }
    pub fn as_scalable(&self) -> Option<&ScalableElement> {
    pub fn as_scalable(&self) -> Option<&dyn ScalableElement> {
        match self {
            Element::Rect(rect) => Some(rect),
            Element::Ellipse(ellipse) => Some(ellipse),


@@ 85,36 82,28 @@ impl Element {
        match self {
            Element::Rect(rect) => {
                rect.position += &diff;
            },
            }
            Element::Ellipse(ellipse) => {
                ellipse.center += &diff;
            },
            }
        }
    }
    pub fn presentation(&self) -> Option<&Presentation> {
        match self {
            Element::Rect(rect) => {
                Some(&rect.presentation)
            },
            Element::Ellipse(ellipse) => {
                Some(&ellipse.presentation)
            },
            Element::Rect(rect) => Some(&rect.presentation),
            Element::Ellipse(ellipse) => Some(&ellipse.presentation),
        }
    }
    pub fn presentation_mut(&mut self) -> Option<&mut Presentation> {
        match self {
            Element::Rect(rect) => {
                Some(&mut rect.presentation)
            },
            Element::Ellipse(ellipse) => {
                Some(&mut ellipse.presentation)
            },
            Element::Rect(rect) => Some(&mut rect.presentation),
            Element::Ellipse(ellipse) => Some(&mut ellipse.presentation),
        }
    }
}

pub trait ScalableElement {
    fn get_size(&self) -> Cow<Size2>;
    fn get_size(&self) -> Cow<'_, Size2>;
    fn set_size(&mut self, size: Size2);
}



@@ 126,7 115,7 @@ pub struct Rect {
}

impl ScalableElement for Rect {
    fn get_size(&self) -> Cow<Size2> {
    fn get_size(&self) -> Cow<'_, Size2> {
        Cow::Borrowed(&self.size)
    }
    fn set_size(&mut self, size: Size2) {


@@ 151,7 140,7 @@ pub struct Ellipse {
}

impl ScalableElement for Ellipse {
    fn get_size(&self) -> Cow<Size2> {
    fn get_size(&self) -> Cow<'_, Size2> {
        Cow::Owned(self.radius.clone() * 2.0)
    }
    fn set_size(&mut self, size: Size2) {

M src/draw.rs => src/draw.rs +35 -12
@@ 1,11 1,16 @@
use color;
use crate::color;

use document::{Element, Paint};
use {DragState, DrawContext};
use crate::document::{Element, Paint};
use crate::{DragState, DrawContext};

use std::borrow::Cow;

pub fn draw_elem(elem: &Element, gr: &cairo::Context, ctx: DrawContext, drag_state: &Option<DragState>) {
pub fn draw_elem(
    elem: &Element,
    gr: &cairo::Context,
    ctx: DrawContext<'_>,
    drag_state: &Option<DragState>,
) {
    match elem {
        Element::Rect(rect) => {
            let mut pos = Cow::Borrowed(&rect.position);


@@ 24,13 29,22 @@ pub fn draw_elem(elem: &Element, gr: &cairo::Context, ctx: DrawContext, drag_sta
            if let Some(ref fill) = rect.presentation.fill {
                match fill {
                    Paint::Color(ref color) => match color {
                        color::Color::RGB(rgb) => gr.set_source_rgb(rgb.r as f64 / 255.0, rgb.g as f64 / 255.0, rgb.b as f64 / 255.0),
                        color::Color::RGBA(rgba) => gr.set_source_rgba(rgba.r as f64 / 255.0, rgba.g as f64 / 255.0, rgba.b as f64 / 255.0, rgba.a as f64),
                    }
                        color::Color::RGB(rgb) => gr.set_source_rgb(
                            rgb.r as f64 / 255.0,
                            rgb.g as f64 / 255.0,
                            rgb.b as f64 / 255.0,
                        ),
                        color::Color::RGBA(rgba) => gr.set_source_rgba(
                            rgba.r as f64 / 255.0,
                            rgba.g as f64 / 255.0,
                            rgba.b as f64 / 255.0,
                            rgba.a as f64,
                        ),
                    },
                }
                gr.fill();
            }
        },
        }
        Element::Ellipse(ellipse) => {
            let mut pos = Cow::Borrowed(&ellipse.center);
            if let Some(DragState::TranslateBoth { start_pos }) = drag_state {


@@ 61,12 75,21 @@ pub fn draw_elem(elem: &Element, gr: &cairo::Context, ctx: DrawContext, drag_sta
            if let Some(ref fill) = ellipse.presentation.fill {
                match fill {
                    Paint::Color(ref color) => match color {
                        color::Color::RGB(rgb) => gr.set_source_rgb(rgb.r as f64 / 255.0, rgb.g as f64 / 255.0, rgb.b as f64 / 255.0),
                        color::Color::RGBA(rgba) => gr.set_source_rgba(rgba.r as f64 / 255.0, rgba.g as f64 / 255.0, rgba.b as f64 / 255.0, rgba.a as f64),
                    }
                        color::Color::RGB(rgb) => gr.set_source_rgb(
                            rgb.r as f64 / 255.0,
                            rgb.g as f64 / 255.0,
                            rgb.b as f64 / 255.0,
                        ),
                        color::Color::RGBA(rgba) => gr.set_source_rgba(
                            rgba.r as f64 / 255.0,
                            rgba.g as f64 / 255.0,
                            rgba.b as f64 / 255.0,
                            rgba.a as f64,
                        ),
                    },
                }
                gr.fill();
            }
        },
        }
    }
}

M src/main.rs => src/main.rs +168 -148
@@ 1,9 1,6 @@
extern crate cairo;
extern crate fragile;
extern crate gdk;
extern crate gtk;
extern crate quick_xml;
extern crate rand;
use fragile;
use gdk;
use gtk;

mod color;
mod document;


@@ 11,15 8,15 @@ mod draw;
mod save;
mod types;

use document::*;
use types::*;
use crate::document::*;
use crate::types::*;

use gtk::prelude::*;

use std::borrow::Cow;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::AtomicBool;

const HANDLE_SIZE: f64 = 8.0;



@@ 29,10 26,7 @@ fn main() {
    let instance = Arc::new(EditInstance {
        document: RwLock::new(Document::new(Size2::new(400.0, 400.0))),
        drag_state: RwLock::new(None),
        last_mouse_pos: RwLock::new(Point2 {
            x: 0.0,
            y: 0.0,
        }),
        last_mouse_pos: RwLock::new(Point2 { x: 0.0, y: 0.0 }),
        repaint: AtomicBool::new(false),
        selection: RwLock::new(None),
        update_sidebar: AtomicBool::new(true),


@@ 40,11 34,13 @@ fn main() {
    });

    let area = gtk::DrawingArea::new();
    area.set_events(area.get_events() | (
            gdk::EventMask::POINTER_MOTION_MASK |
            gdk::EventMask::BUTTON_PRESS_MASK |
            gdk::EventMask::BUTTON_RELEASE_MASK
            ).bits() as i32);
    area.set_events(
        area.get_events()
            | (gdk::EventMask::POINTER_MOTION_MASK
                | gdk::EventMask::BUTTON_PRESS_MASK
                | gdk::EventMask::BUTTON_RELEASE_MASK)
                .bits() as i32,
    );

    let toolbar = gtk::Box::new(gtk::Orientation::Vertical, 2);
    {


@@ 92,8 88,7 @@ fn main() {
                    if let Some(presentation) = elem.as_mut().presentation_mut() {
                        presentation.fill = Some(Paint::Color(new_color));
                        instance.queue_repaint();
                    }
                    else {
                    } else {
                        eprintln!("Tried to color a non-presentation element!");
                    }
                    break;


@@ 144,8 139,7 @@ fn main() {
                    let selection: &Option<_> = &selection;
                    if let Some(selection) = selection {
                        document.content.iter().find(|i| i.id() == *selection)
                    }
                    else {
                    } else {
                        None
                    }
                };


@@ 162,14 156,15 @@ fn main() {
                    } else {
                        false
                    };
                    draw::draw_elem(elem.as_ref(), ctx, DrawContext {
                        mouse_pos: &last_mouse_pos,
                        selected,
                    }, if selected {
                        &drag_state
                    } else {
                        &None
                    });
                    draw::draw_elem(
                        elem.as_ref(),
                        ctx,
                        DrawContext {
                            mouse_pos: &last_mouse_pos,
                            selected,
                        },
                        if selected { &drag_state } else { &None },
                    );
                }

                if drag_state.is_none() {


@@ 177,7 172,12 @@ fn main() {
                        let bbox = selection.as_ref().get_aabb();
                        ctx.set_source_rgb(0.0, 0.0, 0.0);
                        ctx.set_dash(&mut [5.0], 0.0);
                        ctx.rectangle(bbox.position.x, bbox.position.y, bbox.size.width, bbox.size.height);
                        ctx.rectangle(
                            bbox.position.x,
                            bbox.position.y,
                            bbox.size.width,
                            bbox.size.height,
                        );
                        ctx.stroke();

                        ctx.set_dash(&mut [], 0.0);


@@ 187,35 187,45 @@ fn main() {
                        if tool == &ToolMode::Select {
                            if selection.as_ref().as_scalable().is_some() {
                                println!("scalable");
                                let scale_handle_pos = &bbox.position + &bbox.size - Size2 {
                                    width: HANDLE_SIZE / 2.0,
                                    height: HANDLE_SIZE / 2.0,
                                };
                                let scale_handle_pos = &bbox.position + &bbox.size
                                    - Size2 {
                                        width: HANDLE_SIZE / 2.0,
                                        height: HANDLE_SIZE / 2.0,
                                    };
                                let handle_box = AABB {
                                    position: scale_handle_pos,
                                    size: Size2 {
                                        width: HANDLE_SIZE,
                                        height: HANDLE_SIZE,
                                    }
                                    },
                                };

                                let hovered = handle_box.contains(&instance.last_mouse_pos.read().unwrap());
                                let hovered =
                                    handle_box.contains(&instance.last_mouse_pos.read().unwrap());

                                if hovered {
                                    ctx.set_source_rgb(0.0, 0.0, 0.0);
                                }
                                else {
                                } else {
                                    ctx.set_source_rgb(1.0, 1.0, 1.0);
                                }
                                ctx.rectangle(handle_box.position.x, handle_box.position.y, handle_box.size.width, handle_box.size.height);
                                ctx.rectangle(
                                    handle_box.position.x,
                                    handle_box.position.y,
                                    handle_box.size.width,
                                    handle_box.size.height,
                                );
                                ctx.fill();
                                if hovered {
                                    ctx.set_source_rgb(1.0, 1.0, 1.0);
                                }
                                else {
                                } else {
                                    ctx.set_source_rgb(0.0, 0.0, 0.0);
                                }
                                ctx.rectangle(handle_box.position.x, handle_box.position.y, handle_box.size.width, handle_box.size.height);
                                ctx.rectangle(
                                    handle_box.position.x,
                                    handle_box.position.y,
                                    handle_box.size.width,
                                    handle_box.size.height,
                                );
                                ctx.stroke();
                            }
                        }


@@ 243,10 253,7 @@ fn main() {
                ToolMode::Rectangle => {
                    let mut document = instance.document.write().unwrap();

                    let position = Point2 {
                        x: pos_x,
                        y: pos_y,
                    };
                    let position = Point2 { x: pos_x, y: pos_y };

                    let obj = Object::new_random_id(Element::Rect(Rect {
                        position: position.clone(),


@@ 271,14 278,11 @@ fn main() {
                    document.content.push(obj);

                    instance.queue_repaint();
                },
                }
                ToolMode::Ellipse => {
                    let mut document = instance.document.write().unwrap();

                    let position = Point2 {
                        x: pos_x,
                        y: pos_y,
                    };
                    let position = Point2 { x: pos_x, y: pos_y };

                    let obj = Object::new_random_id(Element::Ellipse(Ellipse {
                        center: position.clone(),


@@ 303,60 307,67 @@ fn main() {
                    document.content.push(obj);

                    instance.queue_repaint();
                },
                }
                ToolMode::Select => {
                    let document = instance.document.read().unwrap();

                    let mouse_pos = Point2 {
                        x: pos_x,
                        y: pos_y,
                    };
                    let mouse_pos = Point2 { x: pos_x, y: pos_y };

                    {
                        let current_selection = instance.selection.read().unwrap();
                        let current_selection = document.content.iter().find(|elem| Some(elem.id()) == *current_selection);
                        let current_selection = document
                            .content
                            .iter()
                            .find(|elem| Some(elem.id()) == *current_selection);
                        if let Some(selection) = current_selection {
                            let bbox = selection.as_ref().get_aabb();

                            let scale_box_pos = &bbox.position + &bbox.size - Size2 {
                                width: HANDLE_SIZE / 2.0,
                                height: HANDLE_SIZE / 2.0,
                            };
                            let scale_box_pos = &bbox.position + &bbox.size
                                - Size2 {
                                    width: HANDLE_SIZE / 2.0,
                                    height: HANDLE_SIZE / 2.0,
                                };
                            let scale_box = AABB {
                                position: scale_box_pos,
                                size: Size2 {
                                    width: HANDLE_SIZE,
                                    height: HANDLE_SIZE,
                                }
                                },
                            };

                            if scale_box.contains(&mouse_pos) {
                                *instance.drag_state.write().unwrap() = Some(DragState::ScaleBoth {
                                    start_pos: mouse_pos,
                                });
                                *instance.drag_state.write().unwrap() =
                                    Some(DragState::ScaleBoth {
                                        start_pos: mouse_pos,
                                    });
                                return Inhibit(true);
                            }

                            if bbox.contains(&mouse_pos) {
                                *instance.drag_state.write().unwrap() = Some(DragState::TranslateBoth {
                                    start_pos: mouse_pos,
                                });
                                *instance.drag_state.write().unwrap() =
                                    Some(DragState::TranslateBoth {
                                        start_pos: mouse_pos,
                                    });

                                return Inhibit(true);
                            }
                        }
                    }

                    let new_selection = document.content.iter().rev().find(|elem| elem.as_ref().get_aabb().contains(&mouse_pos));
                    let new_selection = document
                        .content
                        .iter()
                        .rev()
                        .find(|elem| elem.as_ref().get_aabb().contains(&mouse_pos));

                    *instance.selection.write().unwrap() = match new_selection {
                        Some(e) => Some(e.id()),
                        None => None
                        None => None,
                    };
                    instance.queue_update_sidebar();

                    instance.queue_repaint();
                },
                }
            }

            Inhibit(true)


@@ 373,13 384,8 @@ fn main() {

            if let Some(mode) = drag_state {
                match mode {
                    DragState::ScaleBoth {
                        start_pos,
                    } => {
                        let pos = Point2 {
                            x: pos_x,
                            y: pos_y,
                        };
                    DragState::ScaleBoth { start_pos } => {
                        let pos = Point2 { x: pos_x, y: pos_y };
                        let diff = &pos - start_pos;

                        let selection = *instance.selection.read().unwrap();


@@ 393,22 399,16 @@ fn main() {
                                        let new_size = scalable.get_size().into_owned() + &diff;
                                        scalable.set_size(new_size);
                                        instance.queue_repaint();
                                    }
                                    else {
                                    } else {
                                        eprintln!("scaling non-scalable!");
                                    }
                                    break;
                                }
                            }
                        }
                    },
                    DragState::TranslateBoth {
                        start_pos,
                    } => {
                        let pos = Point2 {
                            x: pos_x,
                            y: pos_y,
                        };
                    }
                    DragState::TranslateBoth { start_pos } => {
                        let pos = Point2 { x: pos_x, y: pos_y };
                        let diff = &pos - start_pos;

                        let selection = *instance.selection.read().unwrap();


@@ 423,7 423,7 @@ fn main() {
                                }
                            }
                        }
                    },
                    }
                }
            }



@@ 459,8 459,11 @@ fn main() {

                if let Some(selected) = selection {
                    let mut document = instance.document.write().unwrap();
                    
                    let pos = document.content.iter().position(|elem| elem.id() == *selected);

                    let pos = document
                        .content
                        .iter()
                        .position(|elem| elem.id() == *selected);

                    if let Some(pos) = pos {
                        document.content.remove(pos);


@@ 483,7 486,15 @@ fn main() {
        let instance = instance.clone();
        let window = window.clone();
        save_item.connect_activate(move |_| {
            let dialog = gtk::FileChooserDialog::with_buttons(Some("Save File"), Some(&window), gtk::FileChooserAction::Save, &[(&gtk::STOCK_CANCEL, gtk::ResponseType::Cancel), (&gtk::STOCK_SAVE, gtk::ResponseType::Accept)]);
            let dialog = gtk::FileChooserDialog::with_buttons(
                Some("Save File"),
                Some(&window),
                gtk::FileChooserAction::Save,
                &[
                    (&gtk::STOCK_CANCEL, gtk::ResponseType::Cancel),
                    (&gtk::STOCK_SAVE, gtk::ResponseType::Accept),
                ],
            );
            let filter = gtk::FileFilter::new();
            filter.add_pattern("*.svg");
            gtk::FileFilterExt::set_name(&filter, "SVG");


@@ 492,7 503,8 @@ fn main() {
            let res = dialog.run();
            dialog.hide();
            if gtk::ResponseType::Accept == res.into() {
                let result = dialog.get_filename()
                let result = dialog
                    .get_filename()
                    .ok_or(Cow::Borrowed("Unable to get filename"))
                    .and_then(|filename| {
                        std::fs::File::create(filename)


@@ 500,11 512,16 @@ fn main() {
                    })
                    .and_then(|file| {
                        let document = instance.document.read().unwrap();
                        save::save_svg(&document, file)
                            .map_err(|e| format!("{:?}", e).into())
                        save::save_svg(&document, file).map_err(|e| format!("{:?}", e).into())
                    });
                if let Err(err) = result {
                    let error_dialog = gtk::MessageDialog::new(Some(&window), gtk::DialogFlags::MODAL & gtk::DialogFlags::DESTROY_WITH_PARENT, gtk::MessageType::Error, gtk::ButtonsType::Ok, &format!("Failed to save file: {}", err));
                    let error_dialog = gtk::MessageDialog::new(
                        Some(&window),
                        gtk::DialogFlags::MODAL & gtk::DialogFlags::DESTROY_WITH_PARENT,
                        gtk::MessageType::Error,
                        gtk::ButtonsType::Ok,
                        &format!("Failed to save file: {}", err),
                    );
                    error_dialog.run();
                    error_dialog.destroy();
                }


@@ 522,56 539,61 @@ fn main() {
    let sidebar = Arc::new(fragile::Fragile::new(sidebar));
    let fill_btn = Arc::new(fragile::Fragile::new(fill_btn));

    std::thread::spawn(move || {
        loop {
            if instance.repaint.compare_and_swap(true, false, std::sync::atomic::Ordering::Acquire) {
                println!("repainting");
                let area = area.clone();
    std::thread::spawn(move || loop {
        if instance
            .repaint
            .compare_and_swap(true, false, std::sync::atomic::Ordering::Acquire)
        {
            println!("repainting");
            let area = area.clone();
            glib::idle_add(move || {
                area.get().queue_draw();

                Continue(false)
            });
        }
        {
            let instance = instance.clone();
            if instance.update_sidebar.compare_and_swap(
                true,
                false,
                std::sync::atomic::Ordering::Acquire,
            ) {
                let sidebar = sidebar.clone();
                let fill_btn = fill_btn.clone();
                glib::idle_add(move || {
                    area.get().queue_draw();
                    let sidebar = sidebar.get();
                    let fill_btn = fill_btn.get();

                    Continue(false)
                });
            }
            {
                let instance = instance.clone();
                if instance.update_sidebar.compare_and_swap(true, false, std::sync::atomic::Ordering::Acquire) {
                    let sidebar = sidebar.clone();
                    let fill_btn = fill_btn.clone();
                    glib::idle_add(move || {
                        let sidebar = sidebar.get();
                        let fill_btn = fill_btn.get();
                    let document = instance.document.read().unwrap();

                        let document = instance.document.read().unwrap();
                    let new_selection = instance.selection.read().unwrap();
                    let new_selection: &Option<u64> = &new_selection;
                    let new_selection = if let Some(new_selection) = new_selection {
                        document
                            .content
                            .iter()
                            .find(|elem| elem.id() == *new_selection)
                    } else {
                        None
                    };

                        let new_selection = instance.selection.read().unwrap();
                        let new_selection: &Option<u64> = &new_selection;
                        let new_selection = if let Some(new_selection) = new_selection {
                            document.content.iter().find(|elem| elem.id() == *new_selection)
                        }
                        else {
                            None
                        };

                        if let Some(obj) = new_selection {
                            if let Some(presentation) = obj.as_ref().presentation() {
                                if let Some(Paint::Color(ref color)) = presentation.fill {
                                    fill_btn.set_rgba(&color.to_gdk_rgba());
                                    fill_btn.show();
                                }
                            }
                            else {
                                fill_btn.hide();
                    if let Some(obj) = new_selection {
                        if let Some(presentation) = obj.as_ref().presentation() {
                            if let Some(Paint::Color(ref color)) = presentation.fill {
                                fill_btn.set_rgba(&color.to_gdk_rgba());
                                fill_btn.show();
                            }
                            sidebar.show();
                        }
                        else {
                            sidebar.hide();
                        } else {
                            fill_btn.hide();
                        }
                        sidebar.show();
                    } else {
                        sidebar.hide();
                    }

                        Continue(false)
                    });
                }
                    Continue(false)
                });
            }
        }
    });


@@ 591,10 613,12 @@ struct EditInstance {

impl EditInstance {
    pub fn queue_repaint(&self) {
        self.repaint.store(true, std::sync::atomic::Ordering::Release);
        self.repaint
            .store(true, std::sync::atomic::Ordering::Release);
    }
    pub fn queue_update_sidebar(&self) {
        self.update_sidebar.store(true, std::sync::atomic::Ordering::Release);
        self.update_sidebar
            .store(true, std::sync::atomic::Ordering::Release);
    }
}



@@ 606,12 630,8 @@ enum ToolMode {
}

pub enum DragState {
    ScaleBoth {
        start_pos: Point2
    },
    TranslateBoth {
        start_pos: Point2,
    }
    ScaleBoth { start_pos: Point2 },
    TranslateBoth { start_pos: Point2 },
}

pub struct DrawContext<'a> {

M src/save.rs => src/save.rs +33 -16
@@ 1,8 1,8 @@
use quick_xml;
use std;

use color;
use document;
use crate::color;
use crate::document;

use std::borrow::Cow;



@@ 24,27 24,36 @@ pub fn save_svg(doc: &document::Document, dest: impl std::io::Write) -> Result<(
    let width_str = doc.size.width.to_string();
    let height_str = doc.size.height.to_string();

    let root_attrs: Vec<quick_xml::events::attributes::Attribute> = vec![
    let root_attrs: Vec<quick_xml::events::attributes::Attribute<'_>> = vec![
        (&b"version"[..], &b"1.1"[..]).into(),
        (&b"xmlns"[..], &b"http://www.w3.org/2000/svg"[..]).into(),
        ("width", &width_str[..]).into(),
        ("height", &height_str[..]).into(),
    ];

    writer.write_event(quick_xml::events::Event::Decl(quick_xml::events::BytesDecl::new(b"1.0", None, None)))?;
    writer.write_event(quick_xml::events::Event::Start(quick_xml::events::BytesStart::borrowed_name(b"svg")
                                                       .with_attributes(root_attrs.into_iter())))?;
    writer.write_event(quick_xml::events::Event::Decl(
        quick_xml::events::BytesDecl::new(b"1.0", None, None),
    ))?;
    writer.write_event(quick_xml::events::Event::Start(
        quick_xml::events::BytesStart::borrowed_name(b"svg")
            .with_attributes(root_attrs.into_iter()),
    ))?;

    for elem in &doc.content {
        write_element_svg(elem.as_ref(), &mut writer)?;
    }

    writer.write_event(quick_xml::events::Event::End(quick_xml::events::BytesEnd::borrowed(b"svg")))?;
    writer.write_event(quick_xml::events::Event::End(
        quick_xml::events::BytesEnd::borrowed(b"svg"),
    ))?;

    Ok(())
}

fn write_element_svg(elem: &document::Element, writer: &mut quick_xml::Writer<impl std::io::Write>) -> Result<(), Error> {
fn write_element_svg(
    elem: &document::Element,
    writer: &mut quick_xml::Writer<impl std::io::Write>,
) -> Result<(), Error> {
    match elem {
        document::Element::Rect(rect) => {
            let x_str = rect.position.x.to_string();


@@ 64,11 73,15 @@ fn write_element_svg(elem: &document::Element, writer: &mut quick_xml::Writer<im
                    value: Cow::Owned(fill_str.as_bytes().to_owned()),
                });
            }
            writer.write_event(quick_xml::events::Event::Start(quick_xml::events::BytesStart::borrowed_name(b"rect")
                                                               .with_attributes(attrs.into_iter())))?;
            writer.write_event(quick_xml::events::Event::End(quick_xml::events::BytesEnd::borrowed(b"rect")))?;
            writer.write_event(quick_xml::events::Event::Start(
                quick_xml::events::BytesStart::borrowed_name(b"rect")
                    .with_attributes(attrs.into_iter()),
            ))?;
            writer.write_event(quick_xml::events::Event::End(
                quick_xml::events::BytesEnd::borrowed(b"rect"),
            ))?;
            Ok(())
        },
        }
        document::Element::Ellipse(ellipse) => {
            let x_str = ellipse.center.x.to_string();
            let y_str = ellipse.center.y.to_string();


@@ 87,11 100,15 @@ fn write_element_svg(elem: &document::Element, writer: &mut quick_xml::Writer<im
                    value: Cow::Owned(fill_str.as_bytes().to_owned()),
                });
            }
            writer.write_event(quick_xml::events::Event::Start(quick_xml::events::BytesStart::borrowed_name(b"ellipse")
                                                               .with_attributes(attrs.into_iter())))?;
            writer.write_event(quick_xml::events::Event::End(quick_xml::events::BytesEnd::borrowed(b"ellipse")))?;
            writer.write_event(quick_xml::events::Event::Start(
                quick_xml::events::BytesStart::borrowed_name(b"ellipse")
                    .with_attributes(attrs.into_iter()),
            ))?;
            writer.write_event(quick_xml::events::Event::End(
                quick_xml::events::BytesEnd::borrowed(b"ellipse"),
            ))?;
            Ok(())
        },
        }
    }
}


M src/types.rs => src/types.rs +6 -6
@@ 36,7 36,7 @@ impl<'a> std::ops::AddAssign<&'a Size2> for Point2 {

impl<'a> std::ops::Add<&'a Size2> for Cow<'a, Point2> {
    type Output = Cow<'static, Point2>;
    

    fn add(self, other: &Size2) -> Self::Output {
        match self {
            Cow::Borrowed(pt) => Cow::Owned(pt + other),


@@ 144,10 144,7 @@ impl std::ops::DivAssign<f64> for Size2 {

impl Size2 {
    pub fn new(width: f64, height: f64) -> Self {
        Size2 {
            width,
            height,
        }
        Size2 { width, height }
    }

    pub fn abs(mut self) -> Self {


@@ 164,6 161,9 @@ pub struct AABB {

impl AABB {
    pub fn contains(&self, point: &Point2) -> bool {
        return point.x > self.position.x && point.y > self.position.y && point.x < self.position.x + self.size.width && point.y < self.position.y + self.size.height;
        return point.x > self.position.x
            && point.y > self.position.y
            && point.x < self.position.x + self.size.width
            && point.y < self.position.y + self.size.height;
    }
}