mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 12:08:11 +08:00
Add visibility and delete buttons to node sections in the Properties panel
This commit is contained in:
@@ -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> {
|
||||||
|
|||||||
+1
-1
@@ -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())
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user