mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
Add layer locked toggle icon and context menu entry to node graph (#3855)
* Add layer locked toggle icon and context menu entry to node graph * Simplify logic
This commit is contained in:
@@ -809,10 +809,27 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
let context_menu_data = if let Some(node_id) = clicked_id {
|
||||
let currently_is_node = !network_interface.is_layer(&node_id, breadcrumb_network_path);
|
||||
let can_be_layer = network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path);
|
||||
|
||||
// Determine which layers the Lock/Unlock action would affect:
|
||||
// - If the right-clicked node is in the selection, it affects all selected layers
|
||||
// - If the right-clicked node is not in the selection, it affects just the right-clicked node
|
||||
let selected_nodes = network_interface.selected_nodes_in_nested_network(selection_network_path);
|
||||
let is_clicked_selected = selected_nodes.as_ref().is_some_and(|selected| selected.selected_nodes().any(|id| *id == node_id));
|
||||
let affected_layer_ids = if is_clicked_selected {
|
||||
selected_nodes.map(|selected| selected.selected_nodes().copied().filter(|id| network_interface.is_layer(id, selection_network_path)).collect())
|
||||
} else {
|
||||
network_interface.is_layer(&node_id, selection_network_path).then(|| vec![node_id])
|
||||
}
|
||||
.unwrap_or_default();
|
||||
let has_selected_layers = !affected_layer_ids.is_empty();
|
||||
let all_selected_layers_locked = has_selected_layers && affected_layer_ids.iter().all(|id| network_interface.is_locked(id, selection_network_path));
|
||||
|
||||
ContextMenuData::ModifyNode {
|
||||
can_be_layer,
|
||||
currently_is_node,
|
||||
node_id,
|
||||
has_selected_layers,
|
||||
all_selected_layers_locked,
|
||||
}
|
||||
} else {
|
||||
ContextMenuData::CreateNode { compatible_type: None }
|
||||
@@ -896,6 +913,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle lock of clicked node and return
|
||||
if let Some(clicked_lock) = network_interface.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Lock, selection_network_path) {
|
||||
responses.add(NodeGraphMessage::ToggleLocked { node_id: clicked_lock });
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt-click sets the clicked node as previewed
|
||||
if alt_click && let Some(clicked_node) = clicked_id {
|
||||
self.preview_on_mouse_up = Some(clicked_node);
|
||||
@@ -1834,9 +1857,17 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedLocked");
|
||||
return;
|
||||
};
|
||||
let node_ids = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();
|
||||
let node_ids = selected_nodes
|
||||
.selected_nodes()
|
||||
.filter(|node_id| network_interface.is_layer(node_id, selection_network_path))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// If any of the selected layers are locked, show them all. Otherwise, hide them all.
|
||||
if node_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If any of the selected layers are unlocked, lock them all. Otherwise, unlock them all.
|
||||
let locked = !node_ids.iter().all(|node_id| network_interface.is_locked(node_id, selection_network_path));
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
@@ -147,6 +147,10 @@ pub enum ContextMenuData {
|
||||
can_be_layer: bool,
|
||||
#[serde(rename = "currentlyIsNode")]
|
||||
currently_is_node: bool,
|
||||
#[serde(rename = "hasSelectedLayers")]
|
||||
has_selected_layers: bool,
|
||||
#[serde(rename = "allSelectedLayersLocked")]
|
||||
all_selected_layers_locked: bool,
|
||||
},
|
||||
CreateNode {
|
||||
#[serde(rename = "compatibleType")]
|
||||
|
||||
@@ -2108,9 +2108,10 @@ impl NodeNetworkInterface {
|
||||
|
||||
let grip_padding = 4.;
|
||||
let grip_width = 8.;
|
||||
let lock_icon_width = if self.is_locked(node_id, network_path) { GRID_SIZE as f64 } else { 0. };
|
||||
let icon_overhang_width = GRID_SIZE as f64 / 2.;
|
||||
|
||||
let layer_width_pixels = left_thumbnail_padding + thumbnail_width + GAP_WIDTH + text_width + grip_padding + grip_width + icon_overhang_width;
|
||||
let layer_width_pixels = left_thumbnail_padding + thumbnail_width + GAP_WIDTH + text_width + grip_padding + grip_width + lock_icon_width + icon_overhang_width;
|
||||
let layer_width = ((layer_width_pixels / 24.).ceil() as u32).max(8);
|
||||
|
||||
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
|
||||
@@ -2527,14 +2528,25 @@ impl NodeNetworkInterface {
|
||||
});
|
||||
let width = layer_width_cells * crate::consts::GRID_SIZE;
|
||||
let height = 2 * crate::consts::GRID_SIZE;
|
||||
let locked = self.is_locked(node_id, network_path);
|
||||
|
||||
// Update visibility button click target
|
||||
let visibility_offset = node_top_left + DVec2::new(width as f64, 24.);
|
||||
let subpath = Subpath::new_rounded_rectangle(DVec2::new(-12., -12.) + visibility_offset, DVec2::new(12., 12.) + visibility_offset, [3.; 4]);
|
||||
let visibility_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||
|
||||
// Update grip button click target, which is positioned to the left of the left most icon
|
||||
let grip_offset_right_edge = node_top_left + DVec2::new(width as f64 - (GRID_SIZE as f64) / 2., 24.);
|
||||
// Update lock button click target, positioned one grid unit to the left of the visibility button (only when locked)
|
||||
let lock_click_target = if locked {
|
||||
let lock_offset = node_top_left + DVec2::new(width as f64 - GRID_SIZE as f64, 24.);
|
||||
let subpath = Subpath::new_rounded_rectangle(DVec2::new(-12., -12.) + lock_offset, DVec2::new(12., 12.) + lock_offset, [3.; 4]);
|
||||
Some(ClickTarget::new_with_subpath(subpath, 0.))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Update grip button click target, which is positioned to the left of the leftmost icon
|
||||
let icons_width = if locked { GRID_SIZE as f64 } else { 0. };
|
||||
let grip_offset_right_edge = node_top_left + DVec2::new(width as f64 - (GRID_SIZE as f64) / 2. - icons_width, 24.);
|
||||
let subpath = Subpath::new_rounded_rectangle(DVec2::new(-8., -12.) + grip_offset_right_edge, DVec2::new(0., 12.) + grip_offset_right_edge, [0.; 4]);
|
||||
let grip_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||
|
||||
@@ -2552,6 +2564,7 @@ impl NodeNetworkInterface {
|
||||
port_click_targets,
|
||||
node_type_metadata: NodeTypeClickTargets::Layer(LayerClickTargets {
|
||||
visibility_click_target,
|
||||
lock_click_target,
|
||||
grip_click_target,
|
||||
}),
|
||||
}
|
||||
@@ -2749,10 +2762,17 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
}
|
||||
if let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {
|
||||
// Visibility button (eye icon)
|
||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.visibility_click_target.target_type() {
|
||||
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
|
||||
// Lock button (padlock icon), only when the layer is locked
|
||||
if let Some(lock_click_target) = &layer_metadata.lock_click_target
|
||||
&& let ClickTargetType::Subpath(subpath) = lock_click_target.target_type()
|
||||
{
|
||||
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
// Drag grip (dotted symbol)
|
||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.grip_click_target.target_type() {
|
||||
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
@@ -2882,6 +2902,7 @@ impl NodeNetworkInterface {
|
||||
if let NodeTypeClickTargets::Layer(layer) = &transient_node_metadata.node_type_metadata {
|
||||
match click_target_type {
|
||||
LayerClickTargetTypes::Visibility => layer.visibility_click_target.intersect_point_no_stroke(point).then_some(*node_id),
|
||||
LayerClickTargetTypes::Lock => layer.lock_click_target.as_ref().and_then(|target| target.intersect_point_no_stroke(point).then_some(*node_id)),
|
||||
LayerClickTargetTypes::Grip => layer.grip_click_target.intersect_point_no_stroke(point).then_some(*node_id),
|
||||
}
|
||||
} else {
|
||||
@@ -4508,6 +4529,8 @@ impl NodeNetworkInterface {
|
||||
|
||||
node_metadata.persistent_metadata.locked = locked;
|
||||
self.transaction_modified();
|
||||
self.try_unload_layer_width(node_id, network_path);
|
||||
self.unload_node_click_targets(node_id, network_path);
|
||||
}
|
||||
|
||||
pub fn set_to_node_or_layer(&mut self, node_id: &NodeId, network_path: &[NodeId], is_layer: bool) {
|
||||
@@ -6419,6 +6442,8 @@ pub enum NodeTypeClickTargets {
|
||||
pub struct LayerClickTargets {
|
||||
/// Cache for all visibility buttons. Should be automatically updated when update_click_target is called
|
||||
pub visibility_click_target: ClickTarget,
|
||||
/// Cache for the lock icon button, only present when the layer is locked.
|
||||
pub lock_click_target: Option<ClickTarget>,
|
||||
/// Cache for the grip icon, which is next to the visibility button.
|
||||
pub grip_click_target: ClickTarget,
|
||||
// TODO: Store click target for the preview button, which will appear when the node is a selected/(hovered?) layer node
|
||||
@@ -6427,6 +6452,7 @@ pub struct LayerClickTargets {
|
||||
|
||||
pub enum LayerClickTargetTypes {
|
||||
Visibility,
|
||||
Lock,
|
||||
Grip,
|
||||
// Preview,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user