Add visibility and delete buttons to node sections in the Properties panel

This commit is contained in:
Keavon Chambers
2024-05-07 02:53:30 -07:00
parent 1ce3d59e0f
commit 07fd2c2782
10 changed files with 139 additions and 68 deletions
@@ -309,30 +309,35 @@ impl LayoutMessageHandler {
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>,
) { ) {
// We don't diff the menu bar layout yet. match new_layout {
if matches!(new_layout, Layout::MenuLayout(_)) { Layout::WidgetLayout(_) => {
// Skip update if the same let mut widget_diffs = Vec::new();
if self.layouts[layout_target as usize] == new_layout { self.layouts[layout_target as usize].diff(new_layout, &mut Vec::new(), &mut widget_diffs);
return;
// Skip sending if no diff.
if widget_diffs.is_empty() {
return;
}
self.send_diff(widget_diffs, layout_target, responses, action_input_mapping);
} }
// Update the backend storage // We don't diff the menu bar layout yet.
self.layouts[layout_target as usize] = new_layout; Layout::MenuLayout(_) => {
// Update the UI // Skip update if the same
responses.add(FrontendMessage::UpdateMenuBarLayout { if self.layouts[layout_target as usize] == new_layout {
layout_target, return;
layout: self.layouts[layout_target as usize].clone().unwrap_menu_layout(action_input_mapping).layout, }
});
return;
}
let mut widget_diffs = Vec::new(); // Update the backend storage
self.layouts[layout_target as usize].diff(new_layout, &mut Vec::new(), &mut widget_diffs); self.layouts[layout_target as usize] = new_layout;
// Skip sending if no diff.
if widget_diffs.is_empty() {
return;
}
self.send_diff(widget_diffs, layout_target, responses, action_input_mapping); // Update the UI
responses.add(FrontendMessage::UpdateMenuBarLayout {
layout_target,
layout: self.layouts[layout_target as usize].clone().unwrap_menu_layout(action_input_mapping).layout,
});
}
}
} }
/// Send a diff to the frontend based on the layout target. /// Send a diff to the frontend based on the layout target.
@@ -235,7 +235,7 @@ impl<'a> Iterator for WidgetIter<'a> {
self.current_slice = Some(widgets); self.current_slice = Some(widgets);
self.next() self.next()
} }
Some(LayoutGroup::Section { name: _, layout }) => { Some(LayoutGroup::Section { layout, .. }) => {
for layout_row in layout { for layout_row in layout {
self.stack.push(layout_row); self.stack.push(layout_row);
} }
@@ -276,7 +276,7 @@ impl<'a> Iterator for WidgetIterMut<'a> {
self.current_slice = Some(widgets); self.current_slice = Some(widgets);
self.next() self.next()
} }
Some(LayoutGroup::Section { name: _, layout }) => { Some(LayoutGroup::Section { layout, .. }) => {
for layout_row in layout { for layout_row in layout {
self.stack.push(layout_row); self.stack.push(layout_row);
} }
@@ -301,8 +301,9 @@ pub enum LayoutGroup {
#[serde(rename = "rowWidgets")] #[serde(rename = "rowWidgets")]
widgets: Vec<WidgetHolder>, widgets: Vec<WidgetHolder>,
}, },
// TODO: Move this from being a child of `enum LayoutGroup` to being a child of `enum Layout`
#[serde(rename = "section")] #[serde(rename = "section")]
Section { name: String, layout: SubLayout }, Section { name: String, visible: bool, id: u64, layout: SubLayout },
} }
impl Default for LayoutGroup { impl Default for LayoutGroup {
@@ -378,28 +379,43 @@ impl LayoutGroup {
( (
Self::Section { Self::Section {
name: current_name, name: current_name,
visible: current_visible,
id: current_id,
layout: current_layout, layout: current_layout,
}, },
Self::Section { name: new_name, layout: new_layout }, Self::Section {
name: new_name,
visible: new_visible,
id: new_id,
layout: new_layout,
},
) => { ) => {
// If the lengths are different then resend the entire panel // Resend the entire panel if the lengths, names, visibility, or node IDs are different
// TODO: Diff insersion and deletion of items // TODO: Diff insersion and deletion of items
if *current_name != new_name || current_layout.len() != new_layout.len() { if current_layout.len() != new_layout.len() || *current_name != new_name || *current_visible != new_visible || *current_id != new_id {
// Update self to reflect new changes // Update self to reflect new changes
*current_name = new_name.clone(); *current_name = new_name.clone();
*current_visible = new_visible;
*current_id = new_id;
*current_layout = new_layout.clone(); *current_layout = new_layout.clone();
// Push an update layout group to the diff // Push an update layout group to the diff
let new_value = DiffUpdate::LayoutGroup(Self::Section { name: new_name, layout: new_layout }); let new_value = DiffUpdate::LayoutGroup(Self::Section {
name: new_name,
visible: new_visible,
id: new_id,
layout: new_layout,
});
let widget_path = widget_path.to_vec(); let widget_path = widget_path.to_vec();
widget_diffs.push(WidgetDiff { widget_path, new_value }); widget_diffs.push(WidgetDiff { widget_path, new_value });
return;
} }
// Diff all of the children // Diff all of the children
for (index, (current_child, new_child)) in current_layout.iter_mut().zip(new_layout).enumerate() { else {
widget_path.push(index); for (index, (current_child, new_child)) in current_layout.iter_mut().zip(new_layout).enumerate() {
current_child.diff(new_child, widget_path, widget_diffs); widget_path.push(index);
widget_path.pop(); current_child.diff(new_child, widget_path, widget_diffs);
widget_path.pop();
}
} }
} }
(current, new) => { (current, new) => {
@@ -666,8 +666,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
})(); })();
document_metadata.load_structure(document_network, selected_nodes); document_metadata.load_structure(document_network, selected_nodes);
self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses); self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
responses.add(PropertiesPanelMessage::Refresh);
} }
NodeGraphMessage::ToggleSelectedLocked => { NodeGraphMessage::ToggleSelectedLocked => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
@@ -2252,7 +2252,12 @@ pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, c
Some(document_node_type) => (document_node_type.properties)(document_node, node_id, context), Some(document_node_type) => (document_node_type.properties)(document_node, node_id, context),
None => unknown_node_properties(document_node), None => unknown_node_properties(document_node),
}; };
LayoutGroup::Section { name, layout } LayoutGroup::Section {
name,
visible: document_node.visible,
id: node_id.0,
layout,
}
} }
pub fn stroke_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub fn stroke_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
@@ -43,7 +43,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let options_bar = vec![LayoutGroup::Row { let options_bar = vec![LayoutGroup::Row {
widgets: vec![ widgets: vec![
IconLabel::new("File").tooltip("Document").widget_holder(), IconLabel::new("File").tooltip("Document name").widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Related).widget_holder(),
TextInput::new(document_name) TextInput::new(document_name)
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into()) .on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
+3 -3
View File
@@ -129,8 +129,8 @@
return currentFolder; return currentFolder;
} }
function toggleLayerVisibility(id: bigint) { function toggleNodeVisibility(id: bigint) {
editor.handle.toggleLayerVisibility(id); editor.handle.toggleNodeVisibility(id);
} }
function toggleLayerLock(id: bigint) { function toggleLayerLock(id: bigint) {
@@ -430,7 +430,7 @@
<IconButton <IconButton
class={"status-toggle"} class={"status-toggle"}
classes={{ inactive: !listing.entry.parentsVisible }} classes={{ inactive: !listing.entry.parentsVisible }}
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())} action={(e) => (toggleNodeVisibility(listing.entry.id), e?.stopPropagation())}
size={24} size={24}
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"} icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
hoverIcon={listing.entry.visible ? "EyeHide" : "EyeShow"} hoverIcon={listing.entry.visible ? "EyeHide" : "EyeShow"}
+3 -3
View File
@@ -556,8 +556,8 @@
return selected.includes(node) || intersetNodeAABB(boxSelect, nodeIndex); return selected.includes(node) || intersetNodeAABB(boxSelect, nodeIndex);
} }
function toggleLayerVisibility(id: bigint) { function toggleNodeVisibility(id: bigint) {
editor.handle.toggleLayerVisibility(id); editor.handle.toggleNodeVisibility(id);
} }
function toggleLayerDisplay(displayAsLayer: boolean) { function toggleLayerDisplay(displayAsLayer: boolean) {
@@ -956,7 +956,7 @@
</div> </div>
<IconButton <IconButton
class={"visibility"} class={"visibility"}
action={(e) => (toggleLayerVisibility(node.id), e?.stopPropagation())} action={(e) => (toggleNodeVisibility(node.id), e?.stopPropagation())}
size={24} size={24}
icon={node.visible ? "EyeVisible" : "EyeHidden"} icon={node.visible ? "EyeVisible" : "EyeHidden"}
tooltip={node.visible ? "Visible" : "Hidden"} tooltip={node.visible ? "Visible" : "Hidden"}
@@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
import { isWidgetSpanRow, isWidgetSpanColumn, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages"; import { isWidgetSpanRow, isWidgetSpanColumn, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte"; import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
@@ -14,6 +18,8 @@
export let classes: Record<string, boolean> = {}; export let classes: Record<string, boolean> = {};
let expanded = true; let expanded = true;
const editor = getContext<Editor>("editor");
</script> </script>
<!-- TODO: Implement collapsable sections with properties system --> <!-- TODO: Implement collapsable sections with properties system -->
@@ -21,6 +27,25 @@
<button class="header" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex="0"> <button class="header" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex="0">
<div class="expand-arrow" /> <div class="expand-arrow" />
<TextLabel bold={true}>{widgetData.name}</TextLabel> <TextLabel bold={true}>{widgetData.name}</TextLabel>
<IconButton
icon={"Trash"}
size={24}
action={(e) => {
editor.handle.deleteNode(widgetData.id);
e?.stopPropagation();
}}
class={"show-only-on-hover"}
/>
<IconButton
icon={widgetData.visible ? "EyeVisible" : "EyeHidden"}
hoverIcon={widgetData.visible ? "EyeHide" : "EyeShow"}
size={24}
action={(e) => {
editor.handle.toggleNodeVisibility(widgetData.id);
e?.stopPropagation();
}}
class={widgetData.visible ? "show-only-on-hover" : ""}
/>
</button> </button>
{#if expanded} {#if expanded}
<LayoutCol class="body"> <LayoutCol class="body">
@@ -53,12 +78,34 @@
align-items: center; align-items: center;
display: flex; display: flex;
flex: 0 0 24px; flex: 0 0 24px;
padding: 0 8px; padding-left: 8px;
padding-right: 0;
margin-bottom: 4px; margin-bottom: 4px;
border: 0; border: 0;
border-radius: 4px; border-radius: 4px;
background: var(--color-2-mildblack); background: var(--color-2-mildblack);
&.expanded {
border-radius: 4px 4px 0 0;
margin-bottom: 0;
.expand-arrow::after {
transform: rotate(90deg);
}
}
&:hover {
background: var(--color-4-dimgray);
.expand-arrow::after {
background: var(--icon-expand-collapse-arrow-hover);
}
+ .body {
border: 1px solid var(--color-4-dimgray);
}
}
.expand-arrow { .expand-arrow {
width: 8px; width: 8px;
height: 8px; height: 8px;
@@ -79,32 +126,15 @@
} }
} }
&.expanded {
border-radius: 4px 4px 0 0;
margin-bottom: 0;
.expand-arrow::after {
transform: rotate(90deg);
}
}
.text-label { .text-label {
height: 18px; height: 18px;
margin-left: 8px; margin-left: 8px;
display: inline-block; flex: 1 1 100%;
} }
}
&:hover { &:not(:hover) .header .show-only-on-hover {
background: var(--color-4-dimgray); display: none;
.expand-arrow::after {
background: var(--icon-expand-collapse-arrow-hover);
}
+ .body {
border: 1px solid var(--color-4-dimgray);
}
}
} }
.body { .body {
+2 -2
View File
@@ -1176,7 +1176,7 @@ export function isWidgetSpanRow(layoutRow: LayoutGroup): layoutRow is WidgetSpan
return Boolean((layoutRow as WidgetSpanRow)?.rowWidgets); return Boolean((layoutRow as WidgetSpanRow)?.rowWidgets);
} }
export type WidgetSection = { name: string; layout: LayoutGroup[] }; export type WidgetSection = { name: string; visible: boolean; id: bigint; layout: LayoutGroup[] };
export function isWidgetSection(layoutRow: LayoutGroup): layoutRow is WidgetSection { export function isWidgetSection(layoutRow: LayoutGroup): layoutRow is WidgetSection {
return Boolean((layoutRow as WidgetSection)?.layout); return Boolean((layoutRow as WidgetSection)?.layout);
} }
@@ -1216,7 +1216,7 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
} }
if (layoutGroup.section) { if (layoutGroup.section) {
const result: WidgetSection = { name: layoutGroup.section.name, layout: layoutGroup.section.layout.map(createLayoutGroup) }; const result: WidgetSection = { name: layoutGroup.section.name, visible: layoutGroup.section.visible, id: layoutGroup.section.id, layout: layoutGroup.section.layout.map(createLayoutGroup) };
return result; return result;
} }
+14 -3
View File
@@ -683,14 +683,25 @@ impl EditorHandle {
self.dispatch(message); self.dispatch(message);
} }
/// Toggle visibility of a layer from the layer list /// Toggle visibility of a layer or node given its node ID
#[wasm_bindgen(js_name = toggleLayerVisibility)] #[wasm_bindgen(js_name = toggleNodeVisibility)]
pub fn toggle_layer_visibility(&self, id: u64) { pub fn toggle_node_visibility(&self, id: u64) {
let node_id = NodeId(id); let node_id = NodeId(id);
let message = NodeGraphMessage::ToggleVisibility { node_id }; let message = NodeGraphMessage::ToggleVisibility { node_id };
self.dispatch(message); self.dispatch(message);
} }
/// Delete a layer or node given its node ID
#[wasm_bindgen(js_name = deleteNode)]
pub fn delete_node(&self, id: u64) {
let message = DocumentMessage::StartTransaction;
self.dispatch(message);
let id = NodeId(id);
let message = DocumentMessage::DeleteLayer { id };
self.dispatch(message);
}
/// Toggle lock state of a layer from the layer list /// Toggle lock state of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerLock)] #[wasm_bindgen(js_name = toggleLayerLock)]
pub fn toggle_layer_lock(&self, id: u64) { pub fn toggle_layer_lock(&self, id: u64) {