Skip to content

Fix bounds with artboards for zoom-to-fit and scrollbar scaling #473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ pub const FILE_EXPORT_SUFFIX: &str = ".svg";
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);

// Document
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.1";
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.2";
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
44 changes: 26 additions & 18 deletions editor/src/document/artboard_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ pub struct ArtboardMessageHandler {
pub artboard_ids: Vec<LayerId>,
}

impl ArtboardMessageHandler {
pub fn is_infinite_canvas(&self) -> bool {
self.artboard_ids.is_empty()
}
}

impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> for ArtboardMessageHandler {
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque<Message>) {
// let (layer_metadata, document, ipp) = data;
Expand All @@ -55,29 +61,31 @@ impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &In
)
.into(),
);
}
RenderArtboards => {}
}

// Render an infinite canvas if there are no artboards
if self.artboard_ids.is_empty() {
responses.push_back(
FrontendMessage::UpdateArtboards {
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
}
.into(),
)
} else {
responses.push_back(
FrontendMessage::UpdateArtboards {
svg: self.artboards_graphene_document.render_root(ViewMode::Normal),
responses.push_back(DocumentMessage::RenderDocument.into());
}
RenderArtboards => {
// Render an infinite canvas if there are no artboards
if self.artboard_ids.is_empty() {
responses.push_back(
FrontendMessage::UpdateArtboards {
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
}
.into(),
)
} else {
responses.push_back(
FrontendMessage::UpdateArtboards {
svg: self.artboards_graphene_document.render_root(ViewMode::Normal),
}
.into(),
);
}
.into(),
);
}
}
}

fn actions(&self) -> ActionList {
actions!(ArtBoardMessageDiscriminant;)
actions!(ArtboardMessageDiscriminant;)
}
}
40 changes: 29 additions & 11 deletions editor/src/document/document_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ use super::movement_handler::{MovementMessage, MovementMessageHandler};
use super::overlay_message_handler::OverlayMessageHandler;
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
use super::vectorize_layer_metadata;

use crate::consts::DEFAULT_DOCUMENT_NAME;
use crate::consts::GRAPHITE_DOCUMENT_VERSION;
use crate::consts::{ASYMPTOTIC_EFFECT, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING};
use crate::consts::{
ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,
};
use crate::document::Clipboard;
use crate::input::InputPreprocessor;
use crate::message_prelude::*;
Expand Down Expand Up @@ -82,7 +81,6 @@ pub struct DocumentMessageHandler {
#[serde(with = "vectorize_layer_metadata")]
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
layer_range_selection_reference: Vec<LayerId>,
#[serde(skip)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this allows the document position to be restored from saved files as well as autosave

movement_handler: MovementMessageHandler,
#[serde(skip)]
overlay_message_handler: OverlayMessageHandler,
Expand Down Expand Up @@ -182,6 +180,7 @@ pub enum DocumentMessage {
neighbor: Vec<LayerId>,
},
SetSnapping(bool),
ZoomCanvasToFitAll,
}

impl From<DocumentOperation> for DocumentMessage {
Expand Down Expand Up @@ -225,13 +224,10 @@ impl DocumentMessageHandler {
document
}

pub fn with_name_and_content(name: String, serialized_content: String, ipp: &InputPreprocessor) -> Result<Self, EditorError> {
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
match Self::deserialize_document(&serialized_content) {
Ok(mut document) => {
document.name = name;
let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
document.graphene_document.root.transform = starting_root_transform;
document.artboard_message_handler.artboards_graphene_document.root.transform = starting_root_transform;
Ok(document)
}
Err(DocumentError::InvalidFile(msg)) => Err(EditorError::Document(msg)),
Expand Down Expand Up @@ -540,6 +536,14 @@ impl DocumentMessageHandler {

Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec()))
}

pub fn document_bounds(&self) -> Option<[DVec2; 2]> {
if self.artboard_message_handler.is_infinite_canvas() {
self.graphene_document.viewport_bounding_box(&[]).ok().flatten()
} else {
self.artboard_message_handler.artboards_graphene_document.viewport_bounding_box(&[]).ok().flatten()
}
}
}

impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHandler {
Expand Down Expand Up @@ -577,7 +581,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
);
}
ExportDocument => {
let bbox = self.graphene_document.visible_layers_bounding_box().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
// TODO(MFISH33): Add Dialog to select artboards
let bbox = self.document_bounds().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
let size = bbox[1] - bbox[0];
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
true => self.name.clone().replace(FILE_SAVE_SUFFIX, FILE_EXPORT_SUFFIX),
Expand Down Expand Up @@ -863,7 +868,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
let viewport_size = ipp.viewport_bounds.size();
let viewport_mid = ipp.viewport_bounds.center();
let [bounds1, bounds2] = self.graphene_document.visible_layers_bounding_box().unwrap_or([viewport_mid; 2]);
let [bounds1, bounds2] = self.document_bounds().unwrap_or([viewport_mid; 2]);
let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;
let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;
let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING);
Expand Down Expand Up @@ -1063,6 +1068,18 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
SetSnapping(new_status) => {
self.snapping_enabled = new_status;
}
ZoomCanvasToFitAll => {
if let Some(bounds) = self.document_bounds() {
responses.push_back(
MovementMessage::FitViewportToBounds {
bounds,
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
prevent_zoom_past_100: true,
}
.into(),
)
}
}
}
}

Expand All @@ -1078,6 +1095,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
SetSnapping,
DebugPrintDocument,
MoveLayerInTree,
ZoomCanvasToFitAll,
);

if self.layer_metadata.values().any(|data| data.selected) {
Expand Down
2 changes: 1 addition & 1 deletion editor/src/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
document,
document_is_saved,
} => {
let document = DocumentMessageHandler::with_name_and_content(document_name, document, ipp);
let document = DocumentMessageHandler::with_name_and_content(document_name, document);
match document {
Ok(mut document) => {
document.set_save_state(document_is_saved);
Expand Down
52 changes: 32 additions & 20 deletions editor/src/document/movement_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ pub enum MovementMessage {
center_on_mouse: bool,
},
WheelCanvasZoom,
ZoomCanvasToFitAll,
FitViewportToBounds {
bounds: [DVec2; 2],
padding_scale_factor: Option<f32>,
prevent_zoom_past_100: bool,
},
TranslateCanvas(DVec2),
TranslateCanvasByViewportFraction(DVec2),
}
Expand Down Expand Up @@ -273,25 +277,34 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: self.snapped_angle() }.into());
}
ZoomCanvasToFitAll => {
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
let pos1 = document.root.transform.inverse().transform_point2(pos1);
let pos2 = document.root.transform.inverse().transform_point2(pos2);
let v1 = document.root.transform.inverse().transform_point2(DVec2::ZERO);
let v2 = document.root.transform.inverse().transform_point2(ipp.viewport_bounds.size());

let center = v1.lerp(v2, 0.5) - pos1.lerp(pos2, 0.5);
let size = (pos2 - pos1) / (v2 - v1);
let size = 1. / size;
let new_scale = size.min_element();

self.pan += center;
self.zoom *= new_scale;
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.zoom }.into());
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
FitViewportToBounds {
bounds: [bounds_corner_a, bounds_corner_b],
padding_scale_factor,
prevent_zoom_past_100,
} => {
let pos1 = document.root.transform.inverse().transform_point2(bounds_corner_a);
let pos2 = document.root.transform.inverse().transform_point2(bounds_corner_b);
let v1 = document.root.transform.inverse().transform_point2(DVec2::ZERO);
let v2 = document.root.transform.inverse().transform_point2(ipp.viewport_bounds.size());

let center = v1.lerp(v2, 0.5) - pos1.lerp(pos2, 0.5);
let size = (pos2 - pos1) / (v2 - v1);
let size = 1. / size;
let new_scale = size.min_element();

self.pan += center;
self.zoom *= new_scale;

self.zoom /= padding_scale_factor.unwrap_or(1.) as f64;

if self.zoom > 1. && prevent_zoom_past_100 {
self.zoom = 1.
}

responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.zoom }.into());
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
TranslateCanvas(delta) => {
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
Expand Down Expand Up @@ -321,7 +334,6 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
IncreaseCanvasZoom,
DecreaseCanvasZoom,
WheelCanvasTranslate,
ZoomCanvasToFitAll,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be removed, or just renamed to its new name of FitViewportToBounds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes since the document now gets the keyboard shortcut and sends a message to the movement handler

TranslateCanvas,
TranslateCanvasByViewportFraction,
);
Expand Down
2 changes: 1 addition & 1 deletion editor/src/input/input_mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ impl Default for Mapping {
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl]},
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]},
entry! {action=DocumentMessage::DebugPrintDocument, key_down=Key9},
entry! {action=DocumentMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
// Initiate Transform Layers
entry! {action=TransformLayerMessage::BeginGrab, key_down=KeyG},
entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR},
Expand All @@ -251,7 +252,6 @@ impl Default for Mapping {
entry! {action=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }, key_down=KeyMinus, modifiers=[KeyControl]},
entry! {action=MovementMessage::SetCanvasZoom(1.), key_down=Key1, modifiers=[KeyControl]},
entry! {action=MovementMessage::SetCanvasZoom(2.), key_down=Key2, modifiers=[KeyControl]},
entry! {action=MovementMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
entry! {action=MovementMessage::WheelCanvasZoom, message=InputMapperMessage::MouseScroll, modifiers=[KeyControl]},
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }, message=InputMapperMessage::MouseScroll, modifiers=[KeyShift]},
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }, message=InputMapperMessage::MouseScroll},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/widgets/inputs/MenuBarInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
icon: "File",
action: (): void => {
editor.instance.new_document();
editor.instance.create_artboard(0, 0, 1920, 1080);
editor.instance.create_artboard_and_fit_to_viewport(0, 0, 1920, 1080);
},
},
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: (): void => editor.instance.open_document() },
Expand Down
6 changes: 4 additions & 2 deletions frontend/wasm/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,12 @@ impl JsEditorHandle {
self.dispatch(message);
}

// Creates an artboard at a specified point with a width and height
pub fn create_artboard(&self, top: f64, left: f64, height: f64, width: f64) {
/// Creates an artboard at a specified point with a width and height
pub fn create_artboard_and_fit_to_viewport(&self, top: f64, left: f64, height: f64, width: f64) {
let message = ArtboardMessage::AddArtboard { top, left, height, width };
self.dispatch(message);
let message = DocumentMessage::ZoomCanvasToFitAll;
self.dispatch(message);
}
}

Expand Down