Add a 'Preserve Aspect Ratio' checkbox to Properties panel (#923)

* Add the link button

* Transform around pivot

* Remove log

* Fix tests

* Add a hacky two-line layout for the checkbox

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-12-28 15:21:03 -08:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent df74f6f562
commit 904f38fa1b
8 changed files with 85 additions and 9 deletions
@@ -21,6 +21,7 @@ pub enum PropertiesPanelMessage {
ModifyFill { fill: Fill },
ModifyFont { font_family: String, font_style: String, size: f64 },
ModifyName { name: String },
ModifyPreserveAspect { preserve_aspect: bool },
ModifyStroke { stroke: Stroke },
ModifyText { new_text: String },
ModifyTransform { value: f64, transform_op: TransformOp },
@@ -107,6 +107,10 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name }))
}
ModifyPreserveAspect { preserve_aspect } => {
let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerPreserveAspect { layer_path, preserve_aspect }))
}
ModifyFill { fill } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill }));
@@ -4,7 +4,7 @@ use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup,
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::assist_widgets::PivotAssist;
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton, TextButton};
use crate::messages::layout::utility_types::widgets::input_widgets::{ColorInput, FontInput, NumberInput, NumberInputMode, RadioEntryData, RadioInput, TextAreaInput, TextInput};
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, ColorInput, FontInput, NumberInput, NumberInputMode, RadioEntryData, RadioInput, TextAreaInput, TextInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, TextLabel};
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
@@ -20,6 +20,8 @@ use std::f64::consts::PI;
use std::sync::Arc;
pub fn apply_transform_operation(layer: &Layer, transform_op: TransformOp, value: f64, font_cache: &FontCache) -> [f64; 6] {
let pivot = DAffine2::from_translation(layer.transform.transform_point2(layer.layerspace_pivot(font_cache)));
let transformation = match transform_op {
TransformOp::X => DAffine2::update_x,
TransformOp::Y => DAffine2::update_y,
@@ -34,7 +36,24 @@ pub fn apply_transform_operation(layer: &Layer, transform_op: TransformOp, value
_ => 1.,
};
transformation(layer.transform, value / scale).to_cols_array()
// Apply the operation and find the delta transform
let mut delta = layer.transform.inverse() * transformation(layer.transform, value / scale);
// Preserve aspect ratio
if matches!(transform_op, TransformOp::ScaleX | TransformOp::Width) && layer.preserve_aspect {
let scale_x = layer.transform.scale_x();
if scale_x != 0. {
delta = DAffine2::from_scale((1., (value / scale) / scale_x).into()) * delta;
}
} else if matches!(transform_op, TransformOp::ScaleY | TransformOp::Height) && layer.preserve_aspect {
let scale_y = layer.transform.scale_y();
if scale_y != 0. {
delta = DAffine2::from_scale(((value / scale) / scale_y, 1.).into()) * delta;
}
}
// Transform around pivot
((pivot * delta * pivot.inverse()) * layer.transform).to_cols_array()
}
pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, persistent_data: &PersistentData) {
@@ -128,9 +147,15 @@ pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDequ
..TextLabel::default()
})),
WidgetHolder::unrelated_separator(),
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
WidgetHolder::related_separator(),
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: layer.preserve_aspect,
icon: "Link".into(),
tooltip: "Preserve Aspect Ratio".into(),
on_update: WidgetCallback::new(|input: &CheckboxInput| PropertiesPanelMessage::ModifyPreserveAspect { preserve_aspect: input.checked }.into()),
..Default::default()
})),
WidgetHolder::related_separator(),
WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some(layer.bounding_transform(&persistent_data.font_cache).scale_x()),
@@ -407,9 +432,15 @@ fn node_section_transform(layer: &Layer, persistent_data: &PersistentData) -> La
..TextLabel::default()
})),
WidgetHolder::unrelated_separator(),
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
WidgetHolder::related_separator(),
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: layer.preserve_aspect,
icon: "Link".into(),
tooltip: "Preserve Aspect Ratio".into(),
on_update: WidgetCallback::new(|input: &CheckboxInput| PropertiesPanelMessage::ModifyPreserveAspect { preserve_aspect: input.checked }.into()),
..Default::default()
})),
WidgetHolder::related_separator(),
WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some(layer.transform.scale_x()),