Skip to content

Commit 9f54a37

Browse files
mfish33Keavon
andauthored
Fix bounds with artboards for zoom-to-fit and scrollbar scaling (#473)
* - document load keeps postition - zoom to fit - scrollbars use artboard dimensions * - review comments - svg export uses all artboard bounds Co-authored-by: Keavon Chambers <[email protected]>
1 parent 61432de commit 9f54a37

File tree

8 files changed

+96
-55
lines changed

8 files changed

+96
-55
lines changed

editor/src/consts.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@ pub const FILE_EXPORT_SUFFIX: &str = ".svg";
4545
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
4646

4747
// Document
48-
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.1";
48+
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.2";
49+
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;

editor/src/document/artboard_message_handler.rs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ pub struct ArtboardMessageHandler {
3030
pub artboard_ids: Vec<LayerId>,
3131
}
3232

33+
impl ArtboardMessageHandler {
34+
pub fn is_infinite_canvas(&self) -> bool {
35+
self.artboard_ids.is_empty()
36+
}
37+
}
38+
3339
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> for ArtboardMessageHandler {
3440
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque<Message>) {
3541
// let (layer_metadata, document, ipp) = data;
@@ -55,29 +61,31 @@ impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &In
5561
)
5662
.into(),
5763
);
58-
}
59-
RenderArtboards => {}
60-
}
6164

62-
// Render an infinite canvas if there are no artboards
63-
if self.artboard_ids.is_empty() {
64-
responses.push_back(
65-
FrontendMessage::UpdateArtboards {
66-
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
67-
}
68-
.into(),
69-
)
70-
} else {
71-
responses.push_back(
72-
FrontendMessage::UpdateArtboards {
73-
svg: self.artboards_graphene_document.render_root(ViewMode::Normal),
65+
responses.push_back(DocumentMessage::RenderDocument.into());
66+
}
67+
RenderArtboards => {
68+
// Render an infinite canvas if there are no artboards
69+
if self.artboard_ids.is_empty() {
70+
responses.push_back(
71+
FrontendMessage::UpdateArtboards {
72+
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
73+
}
74+
.into(),
75+
)
76+
} else {
77+
responses.push_back(
78+
FrontendMessage::UpdateArtboards {
79+
svg: self.artboards_graphene_document.render_root(ViewMode::Normal),
80+
}
81+
.into(),
82+
);
7483
}
75-
.into(),
76-
);
84+
}
7785
}
7886
}
7987

8088
fn actions(&self) -> ActionList {
81-
actions!(ArtBoardMessageDiscriminant;)
89+
actions!(ArtboardMessageDiscriminant;)
8290
}
8391
}

editor/src/document/document_file.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ use super::movement_handler::{MovementMessage, MovementMessageHandler};
88
use super::overlay_message_handler::OverlayMessageHandler;
99
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
1010
use super::vectorize_layer_metadata;
11-
12-
use crate::consts::DEFAULT_DOCUMENT_NAME;
13-
use crate::consts::GRAPHITE_DOCUMENT_VERSION;
14-
use crate::consts::{ASYMPTOTIC_EFFECT, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING};
11+
use crate::consts::{
12+
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,
13+
};
1514
use crate::document::Clipboard;
1615
use crate::input::InputPreprocessor;
1716
use crate::message_prelude::*;
@@ -82,7 +81,6 @@ pub struct DocumentMessageHandler {
8281
#[serde(with = "vectorize_layer_metadata")]
8382
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
8483
layer_range_selection_reference: Vec<LayerId>,
85-
#[serde(skip)]
8684
movement_handler: MovementMessageHandler,
8785
#[serde(skip)]
8886
overlay_message_handler: OverlayMessageHandler,
@@ -182,6 +180,7 @@ pub enum DocumentMessage {
182180
neighbor: Vec<LayerId>,
183181
},
184182
SetSnapping(bool),
183+
ZoomCanvasToFitAll,
185184
}
186185

187186
impl From<DocumentOperation> for DocumentMessage {
@@ -225,13 +224,10 @@ impl DocumentMessageHandler {
225224
document
226225
}
227226

228-
pub fn with_name_and_content(name: String, serialized_content: String, ipp: &InputPreprocessor) -> Result<Self, EditorError> {
227+
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
229228
match Self::deserialize_document(&serialized_content) {
230229
Ok(mut document) => {
231230
document.name = name;
232-
let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
233-
document.graphene_document.root.transform = starting_root_transform;
234-
document.artboard_message_handler.artboards_graphene_document.root.transform = starting_root_transform;
235231
Ok(document)
236232
}
237233
Err(DocumentError::InvalidFile(msg)) => Err(EditorError::Document(msg)),
@@ -540,6 +536,14 @@ impl DocumentMessageHandler {
540536

541537
Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec()))
542538
}
539+
540+
pub fn document_bounds(&self) -> Option<[DVec2; 2]> {
541+
if self.artboard_message_handler.is_infinite_canvas() {
542+
self.graphene_document.viewport_bounding_box(&[]).ok().flatten()
543+
} else {
544+
self.artboard_message_handler.artboards_graphene_document.viewport_bounding_box(&[]).ok().flatten()
545+
}
546+
}
543547
}
544548

545549
impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHandler {
@@ -577,7 +581,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
577581
);
578582
}
579583
ExportDocument => {
580-
let bbox = self.graphene_document.visible_layers_bounding_box().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
584+
// TODO(MFISH33): Add Dialog to select artboards
585+
let bbox = self.document_bounds().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
581586
let size = bbox[1] - bbox[0];
582587
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
583588
true => self.name.clone().replace(FILE_SAVE_SUFFIX, FILE_EXPORT_SUFFIX),
@@ -863,7 +868,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
863868
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
864869
let viewport_size = ipp.viewport_bounds.size();
865870
let viewport_mid = ipp.viewport_bounds.center();
866-
let [bounds1, bounds2] = self.graphene_document.visible_layers_bounding_box().unwrap_or([viewport_mid; 2]);
871+
let [bounds1, bounds2] = self.document_bounds().unwrap_or([viewport_mid; 2]);
867872
let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;
868873
let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;
869874
let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING);
@@ -1063,6 +1068,18 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
10631068
SetSnapping(new_status) => {
10641069
self.snapping_enabled = new_status;
10651070
}
1071+
ZoomCanvasToFitAll => {
1072+
if let Some(bounds) = self.document_bounds() {
1073+
responses.push_back(
1074+
MovementMessage::FitViewportToBounds {
1075+
bounds,
1076+
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
1077+
prevent_zoom_past_100: true,
1078+
}
1079+
.into(),
1080+
)
1081+
}
1082+
}
10661083
}
10671084
}
10681085

@@ -1078,6 +1095,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
10781095
SetSnapping,
10791096
DebugPrintDocument,
10801097
MoveLayerInTree,
1098+
ZoomCanvasToFitAll,
10811099
);
10821100

10831101
if self.layer_metadata.values().any(|data| data.selected) {

editor/src/document/document_message_handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
294294
document,
295295
document_is_saved,
296296
} => {
297-
let document = DocumentMessageHandler::with_name_and_content(document_name, document, ipp);
297+
let document = DocumentMessageHandler::with_name_and_content(document_name, document);
298298
match document {
299299
Ok(mut document) => {
300300
document.set_save_state(document_is_saved);

editor/src/document/movement_handler.rs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ pub enum MovementMessage {
3939
center_on_mouse: bool,
4040
},
4141
WheelCanvasZoom,
42-
ZoomCanvasToFitAll,
42+
FitViewportToBounds {
43+
bounds: [DVec2; 2],
44+
padding_scale_factor: Option<f32>,
45+
prevent_zoom_past_100: bool,
46+
},
4347
TranslateCanvas(DVec2),
4448
TranslateCanvasByViewportFraction(DVec2),
4549
}
@@ -273,25 +277,34 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
273277
responses.push_back(ToolMessage::DocumentIsDirty.into());
274278
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: self.snapped_angle() }.into());
275279
}
276-
ZoomCanvasToFitAll => {
277-
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
278-
let pos1 = document.root.transform.inverse().transform_point2(pos1);
279-
let pos2 = document.root.transform.inverse().transform_point2(pos2);
280-
let v1 = document.root.transform.inverse().transform_point2(DVec2::ZERO);
281-
let v2 = document.root.transform.inverse().transform_point2(ipp.viewport_bounds.size());
282-
283-
let center = v1.lerp(v2, 0.5) - pos1.lerp(pos2, 0.5);
284-
let size = (pos2 - pos1) / (v2 - v1);
285-
let size = 1. / size;
286-
let new_scale = size.min_element();
287-
288-
self.pan += center;
289-
self.zoom *= new_scale;
290-
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.zoom }.into());
291-
responses.push_back(ToolMessage::DocumentIsDirty.into());
292-
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
293-
self.create_document_transform(&ipp.viewport_bounds, responses);
280+
FitViewportToBounds {
281+
bounds: [bounds_corner_a, bounds_corner_b],
282+
padding_scale_factor,
283+
prevent_zoom_past_100,
284+
} => {
285+
let pos1 = document.root.transform.inverse().transform_point2(bounds_corner_a);
286+
let pos2 = document.root.transform.inverse().transform_point2(bounds_corner_b);
287+
let v1 = document.root.transform.inverse().transform_point2(DVec2::ZERO);
288+
let v2 = document.root.transform.inverse().transform_point2(ipp.viewport_bounds.size());
289+
290+
let center = v1.lerp(v2, 0.5) - pos1.lerp(pos2, 0.5);
291+
let size = (pos2 - pos1) / (v2 - v1);
292+
let size = 1. / size;
293+
let new_scale = size.min_element();
294+
295+
self.pan += center;
296+
self.zoom *= new_scale;
297+
298+
self.zoom /= padding_scale_factor.unwrap_or(1.) as f64;
299+
300+
if self.zoom > 1. && prevent_zoom_past_100 {
301+
self.zoom = 1.
294302
}
303+
304+
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.zoom }.into());
305+
responses.push_back(ToolMessage::DocumentIsDirty.into());
306+
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
307+
self.create_document_transform(&ipp.viewport_bounds, responses);
295308
}
296309
TranslateCanvas(delta) => {
297310
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
@@ -321,7 +334,6 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
321334
IncreaseCanvasZoom,
322335
DecreaseCanvasZoom,
323336
WheelCanvasTranslate,
324-
ZoomCanvasToFitAll,
325337
TranslateCanvas,
326338
TranslateCanvasByViewportFraction,
327339
);

editor/src/input/input_mapper.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ impl Default for Mapping {
234234
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl]},
235235
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]},
236236
entry! {action=DocumentMessage::DebugPrintDocument, key_down=Key9},
237+
entry! {action=DocumentMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
237238
// Initiate Transform Layers
238239
entry! {action=TransformLayerMessage::BeginGrab, key_down=KeyG},
239240
entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR},
@@ -251,7 +252,6 @@ impl Default for Mapping {
251252
entry! {action=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }, key_down=KeyMinus, modifiers=[KeyControl]},
252253
entry! {action=MovementMessage::SetCanvasZoom(1.), key_down=Key1, modifiers=[KeyControl]},
253254
entry! {action=MovementMessage::SetCanvasZoom(2.), key_down=Key2, modifiers=[KeyControl]},
254-
entry! {action=MovementMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
255255
entry! {action=MovementMessage::WheelCanvasZoom, message=InputMapperMessage::MouseScroll, modifiers=[KeyControl]},
256256
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }, message=InputMapperMessage::MouseScroll, modifiers=[KeyShift]},
257257
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }, message=InputMapperMessage::MouseScroll},

frontend/src/components/widgets/inputs/MenuBarInput.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
7171
icon: "File",
7272
action: (): void => {
7373
editor.instance.new_document();
74-
editor.instance.create_artboard(0, 0, 1920, 1080);
74+
editor.instance.create_artboard_and_fit_to_viewport(0, 0, 1920, 1080);
7575
},
7676
},
7777
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: (): void => editor.instance.open_document() },

frontend/wasm/src/api.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,10 +504,12 @@ impl JsEditorHandle {
504504
self.dispatch(message);
505505
}
506506

507-
// Creates an artboard at a specified point with a width and height
508-
pub fn create_artboard(&self, top: f64, left: f64, height: f64, width: f64) {
507+
/// Creates an artboard at a specified point with a width and height
508+
pub fn create_artboard_and_fit_to_viewport(&self, top: f64, left: f64, height: f64, width: f64) {
509509
let message = ArtboardMessage::AddArtboard { top, left, height, width };
510510
self.dispatch(message);
511+
let message = DocumentMessage::ZoomCanvasToFitAll;
512+
self.dispatch(message);
511513
}
512514
}
513515

0 commit comments

Comments
 (0)