Add a selected_layers() function and refactor code to use it (#314)
* Add a selected_layers() function * Refactor AlignSelectedLayers Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
parent
11d8b80698
commit
7afb28b995
|
|
@ -136,6 +136,9 @@ impl DocumentMessageHandler {
|
||||||
// TODO: Add deduplication
|
// TODO: Add deduplication
|
||||||
(!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten()
|
(!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten()
|
||||||
}
|
}
|
||||||
|
fn selected_layers(&self) -> impl Iterator<Item = &Vec<LayerId>> {
|
||||||
|
self.active_document().layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path))
|
||||||
|
}
|
||||||
fn layerdata(&self, path: &[LayerId]) -> &LayerData {
|
fn layerdata(&self, path: &[LayerId]) -> &LayerData {
|
||||||
self.active_document().layer_data.get(path).expect("Layerdata does not exist")
|
self.active_document().layer_data.get(path).expect("Layerdata does not exist")
|
||||||
}
|
}
|
||||||
|
|
@ -155,8 +158,7 @@ impl DocumentMessageHandler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the paths to all layers in order, optionally including only selected or non
|
/// Returns the paths to all layers in order, optionally including only selected or non-selected layers.
|
||||||
/// selected layers.
|
|
||||||
fn layers_sorted(&self, selected: Option<bool>) -> Vec<Vec<LayerId>> {
|
fn layers_sorted(&self, selected: Option<bool>) -> Vec<Vec<LayerId>> {
|
||||||
// Compute the indices for each layer to be able to sort them
|
// Compute the indices for each layer to be able to sort them
|
||||||
let mut layers_with_indices: Vec<(Vec<LayerId>, Vec<usize>)> = self
|
let mut layers_with_indices: Vec<(Vec<LayerId>, Vec<usize>)> = self
|
||||||
|
|
@ -366,17 +368,14 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
SetBlendModeForSelectedLayers(blend_mode) => {
|
SetBlendModeForSelectedLayers(blend_mode) => {
|
||||||
let active_document = self.active_document();
|
for path in self.selected_layers().cloned() {
|
||||||
|
|
||||||
for path in active_document.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
|
|
||||||
responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into());
|
responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetOpacityForSelectedLayers(opacity) => {
|
SetOpacityForSelectedLayers(opacity) => {
|
||||||
let opacity = opacity.clamp(0., 1.);
|
let opacity = opacity.clamp(0., 1.);
|
||||||
let active_document = self.active_document();
|
|
||||||
|
|
||||||
for path in active_document.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
|
for path in self.selected_layers().cloned() {
|
||||||
responses.push_back(DocumentOperation::SetLayerOpacity { path, opacity }.into());
|
responses.push_back(DocumentOperation::SetLayerOpacity { path, opacity }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -388,8 +387,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
responses.extend(self.handle_folder_changed(path));
|
responses.extend(self.handle_folder_changed(path));
|
||||||
}
|
}
|
||||||
DeleteSelectedLayers => {
|
DeleteSelectedLayers => {
|
||||||
let paths = self.selected_layers_sorted();
|
for path in self.selected_layers().cloned() {
|
||||||
for path in paths {
|
|
||||||
responses.push_back(DocumentOperation::DeleteLayer { path }.into())
|
responses.push_back(DocumentOperation::DeleteLayer { path }.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -608,14 +606,12 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
|
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
|
||||||
}
|
}
|
||||||
NudgeSelectedLayers(x, y) => {
|
NudgeSelectedLayers(x, y) => {
|
||||||
let paths = self.selected_layers_sorted();
|
|
||||||
|
|
||||||
let delta = {
|
let delta = {
|
||||||
let root_layer_rotation = self.layerdata_mut(&[]).rotation;
|
let root_layer_rotation = self.layerdata_mut(&[]).rotation;
|
||||||
let rotate_to_viewport_space = DAffine2::from_angle(root_layer_rotation).inverse();
|
let rotate_to_viewport_space = DAffine2::from_angle(root_layer_rotation).inverse();
|
||||||
rotate_to_viewport_space.transform_point2((x, y).into())
|
rotate_to_viewport_space.transform_point2((x, y).into())
|
||||||
};
|
};
|
||||||
for path in paths {
|
for path in self.selected_layers().cloned() {
|
||||||
let operation = DocumentOperation::TransformLayer {
|
let operation = DocumentOperation::TransformLayer {
|
||||||
path,
|
path,
|
||||||
transform: DAffine2::from_translation(delta).to_cols_array(),
|
transform: DAffine2::from_translation(delta).to_cols_array(),
|
||||||
|
|
@ -711,13 +707,12 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
AlignSelectedLayers(axis, aggregate) => {
|
AlignSelectedLayers(axis, aggregate) => {
|
||||||
// TODO: Handle folder nested transforms with the transforms API
|
// TODO: Handle folder nested transforms with the transforms API
|
||||||
let selected_paths = self.selected_layers_sorted();
|
if self.selected_layers().next().is_none() {
|
||||||
if selected_paths.is_empty() {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected_layers = selected_paths.iter().filter_map(|path| {
|
let selected_layers = self.selected_layers().cloned().filter_map(|path| {
|
||||||
let layer = self.active_document().document.layer(path).unwrap();
|
let layer = self.active_document().document.layer(&path).ok()?;
|
||||||
let point = {
|
let point = {
|
||||||
let bounding_box = layer.bounding_box(layer.transform, layer.style)?;
|
let bounding_box = layer.bounding_box(layer.transform, layer.style)?;
|
||||||
match aggregate {
|
match aggregate {
|
||||||
|
|
@ -731,44 +726,33 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
AlignAxis::X => (point.x, layer.transform.translation.x),
|
AlignAxis::X => (point.x, layer.transform.translation.x),
|
||||||
AlignAxis::Y => (point.y, layer.transform.translation.y),
|
AlignAxis::Y => (point.y, layer.transform.translation.y),
|
||||||
};
|
};
|
||||||
Some((path.clone(), bounding_box_coord, translation_coord))
|
Some((path, bounding_box_coord, translation_coord))
|
||||||
});
|
});
|
||||||
|
let selected_layers: Vec<_> = selected_layers.collect();
|
||||||
|
|
||||||
let bounding_box_coords = selected_layers.clone().map(|(_, bounding_box_coord, _)| bounding_box_coord);
|
let bounding_box_coords = selected_layers.iter().map(|(_, bounding_box_coord, _)| bounding_box_coord).cloned();
|
||||||
let aggregated_coord = match aggregate {
|
if let Some(aggregated_coord) = match aggregate {
|
||||||
AlignAggregate::Min => bounding_box_coords.reduce(|a, b| a.min(b)).unwrap(),
|
AlignAggregate::Min => bounding_box_coords.reduce(|a, b| a.min(b)),
|
||||||
AlignAggregate::Max => bounding_box_coords.reduce(|a, b| a.max(b)).unwrap(),
|
AlignAggregate::Max => bounding_box_coords.reduce(|a, b| a.max(b)),
|
||||||
AlignAggregate::Center => {
|
AlignAggregate::Center => {
|
||||||
// TODO: Refactor with `reduce` and `merge_bounding_boxes` once the latter is added
|
// TODO: Refactor with `reduce` and `merge_bounding_boxes` once the latter is added
|
||||||
let bounding_boxes = selected_paths.iter().filter_map(|path| {
|
self.selected_layers()
|
||||||
let layer = self.active_document().document.layer(path).unwrap();
|
.filter_map(|path| self.active_document().document.layer(path).ok().map(|layer| layer.bounding_box(layer.transform, layer.style)).flatten())
|
||||||
layer.bounding_box(layer.transform, layer.style)
|
|
||||||
});
|
|
||||||
let min = bounding_boxes
|
|
||||||
.clone()
|
|
||||||
.map(|bbox| match axis {
|
.map(|bbox| match axis {
|
||||||
AlignAxis::X => bbox[0].x,
|
AlignAxis::X => (bbox[0].x, bbox[1].x),
|
||||||
AlignAxis::Y => bbox[0].y,
|
AlignAxis::Y => (bbox[0].y, bbox[1].y),
|
||||||
})
|
})
|
||||||
.reduce(|a, b| a.min(b))
|
.reduce(|(a, b), (c, d)| (a.min(c), b.max(d)))
|
||||||
.unwrap();
|
.map(|(min, max)| (min + max) / 2.)
|
||||||
let max = bounding_boxes
|
|
||||||
.clone()
|
|
||||||
.map(|bbox| match axis {
|
|
||||||
AlignAxis::X => bbox[1].x,
|
|
||||||
AlignAxis::Y => bbox[1].y,
|
|
||||||
})
|
|
||||||
.reduce(|a, b| a.max(b))
|
|
||||||
.unwrap();
|
|
||||||
(min + max) / 2.
|
|
||||||
}
|
}
|
||||||
AlignAggregate::Average => bounding_box_coords.sum::<f64>() / selected_paths.len() as f64,
|
AlignAggregate::Average => Some(bounding_box_coords.sum::<f64>() / selected_layers.len() as f64),
|
||||||
};
|
} {
|
||||||
for (path, bounding_box_coord, translation_coord) in selected_layers {
|
for (path, bounding_box_coord, translation_coord) in selected_layers {
|
||||||
let new_coord = aggregated_coord - (bounding_box_coord - translation_coord);
|
let new_coord = aggregated_coord - (bounding_box_coord - translation_coord);
|
||||||
match axis {
|
match axis {
|
||||||
AlignAxis::X => responses.push_back(DocumentMessage::SetLayerTranslation(path, Some(new_coord), None).into()),
|
AlignAxis::X => responses.push_back(DocumentMessage::SetLayerTranslation(path, Some(new_coord), None).into()),
|
||||||
AlignAxis::Y => responses.push_back(DocumentMessage::SetLayerTranslation(path, None, Some(new_coord)).into()),
|
AlignAxis::Y => responses.push_back(DocumentMessage::SetLayerTranslation(path, None, Some(new_coord)).into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue