Skip to content

Commit fc355cf

Browse files
committed
Polish up the Layers panel design
1 parent 5bab38e commit fc355cf

File tree

15 files changed

+205
-26
lines changed

15 files changed

+205
-26
lines changed

editor/src/messages/layout/utility_types/widgets/button_widgets.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ pub struct IconButton {
1313
#[widget_builder(constructor)]
1414
pub icon: String,
1515

16+
#[serde(rename = "hoverIcon")]
17+
pub hover_icon: Option<String>,
18+
1619
#[widget_builder(constructor)]
1720
pub size: u32, // TODO: Convert to an `IconSize` enum
1821

@@ -95,6 +98,9 @@ pub struct TextButton {
9598

9699
pub icon: Option<String>,
97100

101+
#[serde(rename = "hoverIcon")]
102+
pub hover_icon: Option<String>,
103+
98104
pub flush: bool,
99105

100106
pub emphasized: bool,

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,7 +1296,8 @@ impl DocumentMessageHandler {
12961296
widgets.extend([
12971297
Separator::new(SeparatorType::Unrelated).widget_holder(),
12981298
TextButton::new("Node Graph")
1299-
.icon(Some(if self.graph_view_overlay_open { "GraphViewOpen".into() } else { "GraphViewClosed".into() }))
1299+
.icon(Some((if self.graph_view_overlay_open { "GraphViewOpen" } else { "GraphViewClosed" }).into()))
1300+
.hover_icon(Some((if self.graph_view_overlay_open { "GraphViewClosed" } else { "GraphViewOpen" }).into()))
13001301
.tooltip(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
13011302
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
13021303
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
@@ -1358,6 +1359,10 @@ impl DocumentMessageHandler {
13581359
})
13591360
.collect();
13601361

1362+
let has_selection = self.selected_nodes.selected_layers(self.metadata()).next().is_some();
1363+
let selection_all_visible = self.selected_nodes.selected_layers(self.metadata()).all(|layer| self.metadata().node_is_visible(layer.to_node()));
1364+
let selection_all_locked = false; // TODO: Implement
1365+
13611366
let layers_panel_options_bar = WidgetLayout::new(vec![LayoutGroup::Row {
13621367
widgets: vec![
13631368
DropdownInput::new(blend_mode_menu_entries)
@@ -1384,16 +1389,42 @@ impl DocumentMessageHandler {
13841389
}
13851390
})
13861391
.widget_holder(),
1392+
//
13871393
Separator::new(SeparatorType::Unrelated).widget_holder(),
1388-
IconButton::new("Folder", 24)
1389-
.tooltip("New Folder")
1394+
//
1395+
IconButton::new("NewLayer", 24)
1396+
.tooltip("New Folder/Layer")
13901397
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
13911398
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
13921399
.widget_holder(),
1400+
IconButton::new("Folder", 24)
1401+
.tooltip("Group Selected")
1402+
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
1403+
.on_update(|_| DocumentMessage::GroupSelectedLayers.into())
1404+
.disabled(!has_selection)
1405+
.widget_holder(),
13931406
IconButton::new("Trash", 24)
13941407
.tooltip("Delete Selected")
13951408
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
13961409
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
1410+
.disabled(!has_selection)
1411+
.widget_holder(),
1412+
//
1413+
Separator::new(SeparatorType::Unrelated).widget_holder(),
1414+
//
1415+
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
1416+
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
1417+
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
1418+
.tooltip_shortcut(action_keys!(DialogMessageDiscriminant::RequestComingSoonDialog))
1419+
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1127) }.into())
1420+
.disabled(!has_selection)
1421+
.widget_holder(),
1422+
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
1423+
.hover_icon(Some((if selection_all_visible { "EyeHidden" } else { "EyeVisible" }).into()))
1424+
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
1425+
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
1426+
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())
1427+
.disabled(!has_selection)
13971428
.widget_holder(),
13981429
],
13991430
}]);

editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,14 +544,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
544544
}
545545

546546
fn actions(&self) -> ActionList {
547-
unimplemented!("Must use `actions_with_graph_open` instead (unless we change every implementation of the MessageHandler trait).")
547+
unimplemented!("Must use `actions_with_node_graph_open` instead (unless we change every implementation of the MessageHandler trait).")
548548
}
549549
}
550550

551551
impl NodeGraphMessageHandler {
552552
pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList {
553553
if self.has_selection && graph_open {
554-
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleSelectedVisibility)
554+
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility, DuplicateSelectedNodes, DeleteSelectedNodes, Cut, Copy)
555+
} else if self.has_selection {
556+
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility)
555557
} else {
556558
actions!(NodeGraphMessageDiscriminant;)
557559
}
@@ -777,15 +779,24 @@ impl NodeGraphMessageHandler {
777779
}
778780
};
779781

782+
let parents_visible = layer
783+
.ancestors(metadata)
784+
.filter(|&ancestor| ancestor != layer)
785+
.all(|layer| network.nodes.get(&layer.to_node()).map(|node| node.visible).unwrap_or_default());
786+
780787
let data = LayerPanelEntry {
781788
id: node_id,
782789
layer_classification,
783790
expanded: layer.has_children(metadata) && !collapsed.0.contains(&layer),
791+
has_children: layer.has_children(metadata),
784792
depth: layer.ancestors(metadata).count() - 1,
785793
parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
786794
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
787795
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
788796
visible: node.visible,
797+
parents_visible,
798+
unlocked: true,
799+
parents_unlocked: true,
789800
};
790801
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
791802
}
@@ -918,6 +929,7 @@ impl Default for NodeGraphMessageHandler {
918929
Separator::new(SeparatorType::Unrelated).widget_holder(),
919930
TextButton::new("Node Graph")
920931
.icon(Some("GraphViewOpen".into()))
932+
.hover_icon(Some("GraphViewClosed".into()))
921933
.tooltip("Hide Node Graph")
922934
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
923935
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())

editor/src/messages/portfolio/document/utility_types/nodes.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,14 @@ pub struct LayerPanelEntry {
4646
#[serde(rename = "layerClassification")]
4747
pub layer_classification: LayerClassification,
4848
pub expanded: bool,
49+
#[serde(rename = "hasChildren")]
50+
pub has_children: bool,
4951
pub visible: bool,
52+
#[serde(rename = "parentsVisible")]
53+
pub parents_visible: bool,
54+
pub unlocked: bool,
55+
#[serde(rename = "parentsUnlocked")]
56+
pub parents_unlocked: bool,
5057
#[serde(rename = "parentId")]
5158
pub parent_id: Option<NodeId>,
5259
pub depth: usize,
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

frontend/src/components/Editor.svelte

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
:root {
7070
// Replace usage of `-rgb` variants with CSS color() function to calculate alpha when browsers support it
7171
// See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color() and https://caniuse.com/css-color-function
72+
// Specifically, support for the relative syntax is needed: `color(from var(--color-0-black) srgb r g b / 0.5)` to convert black to 50% alpha
7273
--color-0-black: #000;
7374
--color-0-black-rgb: 0, 0, 0;
7475
--color-1-nearblack: #111;
@@ -138,6 +139,16 @@
138139
--color-transparent-checkered-background-size: 16px 16px;
139140
--color-transparent-checkered-background-position: 0 0, 8px 8px;
140141
142+
--background-inactive-stripes: repeating-linear-gradient(
143+
-45deg,
144+
transparent 0px,
145+
transparent calc((3px * sqrt(2) / 2) - 0.5px),
146+
var(--color-5-dullgray) calc((3px * sqrt(2) / 2) - 0.5px),
147+
var(--color-5-dullgray) calc((3px * sqrt(2) / 2) + 0.5px),
148+
transparent calc((3px * sqrt(2) / 2) + 0.5px),
149+
transparent calc(6px * sqrt(2) / 2)
150+
);
151+
141152
// Arrow triangle (#eee fill)
142153
--icon-expand-collapse-arrow: url('data:image/svg+xml;utf8,\
143154
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23eee" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>\

frontend/src/components/layout/LayoutCol.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
export { styleName as style };
77
export let styles: Record<string, string | number | undefined> = {};
88
export let tooltip: string | undefined = undefined;
9+
// TODO: Add middle-click drag scrolling
910
export let scrollableX = false;
1011
export let scrollableY = false;
1112

frontend/src/components/layout/LayoutRow.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
export { styleName as style };
77
export let styles: Record<string, string | number | undefined> = {};
88
export let tooltip: string | undefined = undefined;
9+
// TODO: Add middle-click drag scrolling
910
export let scrollableX = false;
1011
export let scrollableY = false;
1112

frontend/src/components/panels/Layers.svelte

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@
377377
classes={{
378378
selected: fakeHighlight !== undefined ? fakeHighlight === listing.entry.id : $nodeGraph.selected.includes(listing.entry.id),
379379
"insert-folder": (draggingData?.highlightFolder || false) && draggingData?.insertParentId === listing.entry.id,
380+
"nesting-layer": isNestingLayer(listing.entry.layerClassification),
380381
}}
381382
styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }}
382383
data-layer
@@ -387,7 +388,13 @@
387388
on:click={(e) => selectLayerWithModifiers(e, listing)}
388389
>
389390
{#if isNestingLayer(listing.entry.layerClassification)}
390-
<button class="expand-arrow" class:expanded={listing.entry.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.id)} tabindex="0" />
391+
<button
392+
class="expand-arrow"
393+
class:expanded={listing.entry.expanded}
394+
disabled={!listing.entry.hasChildren}
395+
on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.id)}
396+
tabindex="0"
397+
/>
391398
{#if listing.entry.layerClassification === "Artboard"}
392399
<IconLabel icon="Artboard" class={"layer-type-icon"} />
393400
{:else if listing.entry.layerClassification === "Folder"}
@@ -413,12 +420,25 @@
413420
on:change={(e) => onEditLayerNameChange(listing, e)}
414421
/>
415422
</LayoutRow>
423+
{#if !listing.entry.unlocked || !listing.entry.parentsUnlocked}
424+
<IconButton
425+
class={"status-toggle"}
426+
classes={{ inactive: !listing.entry.parentsUnlocked }}
427+
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
428+
size={24}
429+
icon={listing.entry.parentsUnlocked ? "PadlockLocked" : "PadlockUnlocked"}
430+
hoverIcon={listing.entry.parentsUnlocked ? "PadlockUnlocked" : "PadlockLocked"}
431+
tooltip={listing.entry.parentsUnlocked ? "Unlock" : "Lock"}
432+
/>
433+
{/if}
416434
<IconButton
417-
class={"visibility"}
435+
class={"status-toggle"}
436+
classes={{ inactive: !listing.entry.parentsVisible }}
418437
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
419438
size={24}
420439
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
421-
tooltip={listing.entry.visible ? "Visible" : "Hidden"}
440+
hoverIcon={listing.entry.visible ? "EyeHidden" : "EyeVisible"}
441+
tooltip={listing.entry.visible ? "Hide" : "Show"}
422442
/>
423443
</LayoutRow>
424444
{/each}
@@ -443,15 +463,26 @@
443463
min-width: 300px;
444464
}
445465
466+
// Blend mode selector and opacity slider
467+
.dropdown-input,
468+
.number-input {
469+
flex: 1 1 auto;
470+
}
471+
446472
// Blend mode selector
447473
.dropdown-input {
448474
max-width: 120px;
475+
flex-basis: 120px;
449476
}
450477
451-
// Blend mode selector and opacity slider
452-
.dropdown-input,
478+
// Opacity slider
453479
.number-input {
454-
flex: 1 1 auto;
480+
max-width: 180px;
481+
flex-basis: 180px;
482+
483+
+ .separator ~ .separator {
484+
flex-grow: 1;
485+
}
455486
}
456487
}
457488
@@ -464,11 +495,15 @@
464495
flex: 0 0 auto;
465496
align-items: center;
466497
position: relative;
498+
border-bottom: 1px solid var(--color-2-mildblack);
499+
border-radius: 2px;
467500
height: 32px;
468501
margin: 0 4px;
469502
padding-left: calc(4px + var(--layer-indent-levels) * 16px);
470-
border-bottom: 1px solid var(--color-2-mildblack);
471-
border-radius: 2px;
503+
504+
&.nesting-layer {
505+
padding-left: calc(var(--layer-indent-levels) * 16px);
506+
}
472507
473508
&.selected {
474509
background: var(--color-4-dimgray);
@@ -493,23 +528,28 @@
493528
justify-content: center;
494529
border-radius: 2px;
495530
496-
&:hover {
497-
background: var(--color-5-dullgray);
498-
}
499-
500531
&::after {
501532
content: "";
502533
position: absolute;
503-
width: 0;
504-
height: 0;
505-
border-style: solid;
506-
border-width: 3px 0 3px 6px;
507-
border-color: transparent transparent transparent var(--color-e-nearwhite);
534+
width: 8px;
535+
height: 8px;
536+
background: var(--icon-expand-collapse-arrow);
537+
}
538+
539+
&[disabled]::after {
540+
background: var(--icon-expand-collapse-arrow-disabled);
541+
}
542+
543+
&:hover:not([disabled]) {
544+
background: var(--color-5-dullgray);
545+
546+
&::after {
547+
background: var(--icon-expand-collapse-arrow-hover);
548+
}
508549
}
509550
510551
&.expanded::after {
511-
border-width: 6px 3px 0 3px;
512-
border-color: var(--color-e-nearwhite) transparent transparent transparent;
552+
transform: rotate(90deg);
513553
}
514554
}
515555
@@ -573,11 +613,15 @@
573613
}
574614
}
575615
576-
.visibility {
616+
.status-toggle {
577617
flex: 0 0 auto;
578618
align-items: center;
579619
height: 100%;
580620
621+
&.inactive {
622+
background-image: var(--background-inactive-stripes);
623+
}
624+
581625
.icon-button {
582626
height: 100%;
583627
width: calc(24px + 2 * 4px);

0 commit comments

Comments
 (0)