Polish up the Layers panel design
This commit is contained in:
parent
5bab38e173
commit
938a688fa0
|
|
@ -13,6 +13,9 @@ pub struct IconButton {
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub icon: String,
|
pub icon: String,
|
||||||
|
|
||||||
|
#[serde(rename = "hoverIcon")]
|
||||||
|
pub hover_icon: Option<String>,
|
||||||
|
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub size: u32, // TODO: Convert to an `IconSize` enum
|
pub size: u32, // TODO: Convert to an `IconSize` enum
|
||||||
|
|
||||||
|
|
@ -95,6 +98,9 @@ pub struct TextButton {
|
||||||
|
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "hoverIcon")]
|
||||||
|
pub hover_icon: Option<String>,
|
||||||
|
|
||||||
pub flush: bool,
|
pub flush: bool,
|
||||||
|
|
||||||
pub emphasized: bool,
|
pub emphasized: bool,
|
||||||
|
|
|
||||||
|
|
@ -1296,7 +1296,8 @@ impl DocumentMessageHandler {
|
||||||
widgets.extend([
|
widgets.extend([
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
TextButton::new("Node Graph")
|
TextButton::new("Node Graph")
|
||||||
.icon(Some(if self.graph_view_overlay_open { "GraphViewOpen".into() } else { "GraphViewClosed".into() }))
|
.icon(Some((if self.graph_view_overlay_open { "GraphViewOpen" } else { "GraphViewClosed" }).into()))
|
||||||
|
.hover_icon(Some((if self.graph_view_overlay_open { "GraphViewClosed" } else { "GraphViewOpen" }).into()))
|
||||||
.tooltip(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
|
.tooltip(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
||||||
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
||||||
|
|
@ -1358,6 +1359,10 @@ impl DocumentMessageHandler {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let has_selection = self.selected_nodes.selected_layers(self.metadata()).next().is_some();
|
||||||
|
let selection_all_visible = self.selected_nodes.selected_layers(self.metadata()).all(|layer| self.metadata().node_is_visible(layer.to_node()));
|
||||||
|
let selection_all_locked = false; // TODO: Implement
|
||||||
|
|
||||||
let layers_panel_options_bar = WidgetLayout::new(vec![LayoutGroup::Row {
|
let layers_panel_options_bar = WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
DropdownInput::new(blend_mode_menu_entries)
|
DropdownInput::new(blend_mode_menu_entries)
|
||||||
|
|
@ -1384,16 +1389,42 @@ impl DocumentMessageHandler {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
|
//
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
IconButton::new("Folder", 24)
|
//
|
||||||
.tooltip("New Folder")
|
IconButton::new("NewLayer", 24)
|
||||||
|
.tooltip("New Folder/Layer")
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
|
||||||
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
|
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
|
IconButton::new("Folder", 24)
|
||||||
|
.tooltip("Group Selected")
|
||||||
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
|
||||||
|
.on_update(|_| DocumentMessage::GroupSelectedLayers.into())
|
||||||
|
.disabled(!has_selection)
|
||||||
|
.widget_holder(),
|
||||||
IconButton::new("Trash", 24)
|
IconButton::new("Trash", 24)
|
||||||
.tooltip("Delete Selected")
|
.tooltip("Delete Selected")
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
|
||||||
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
|
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
|
||||||
|
.disabled(!has_selection)
|
||||||
|
.widget_holder(),
|
||||||
|
//
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
//
|
||||||
|
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
||||||
|
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
|
||||||
|
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
||||||
|
.tooltip_shortcut(action_keys!(DialogMessageDiscriminant::RequestComingSoonDialog))
|
||||||
|
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1127) }.into())
|
||||||
|
.disabled(!has_selection)
|
||||||
|
.widget_holder(),
|
||||||
|
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
||||||
|
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
|
||||||
|
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
|
||||||
|
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
|
||||||
|
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())
|
||||||
|
.disabled(!has_selection)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
],
|
],
|
||||||
}]);
|
}]);
|
||||||
|
|
|
||||||
|
|
@ -544,14 +544,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
unimplemented!("Must use `actions_with_graph_open` instead (unless we change every implementation of the MessageHandler trait).")
|
unimplemented!("Must use `actions_with_node_graph_open` instead (unless we change every implementation of the MessageHandler trait).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeGraphMessageHandler {
|
impl NodeGraphMessageHandler {
|
||||||
pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList {
|
pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList {
|
||||||
if self.has_selection && graph_open {
|
if self.has_selection && graph_open {
|
||||||
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleSelectedVisibility)
|
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility, DuplicateSelectedNodes, DeleteSelectedNodes, Cut, Copy)
|
||||||
|
} else if self.has_selection {
|
||||||
|
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility)
|
||||||
} else {
|
} else {
|
||||||
actions!(NodeGraphMessageDiscriminant;)
|
actions!(NodeGraphMessageDiscriminant;)
|
||||||
}
|
}
|
||||||
|
|
@ -777,15 +779,24 @@ impl NodeGraphMessageHandler {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let parents_visible = layer
|
||||||
|
.ancestors(metadata)
|
||||||
|
.filter(|&ancestor| ancestor != layer)
|
||||||
|
.all(|layer| network.nodes.get(&layer.to_node()).map(|node| node.visible).unwrap_or_default());
|
||||||
|
|
||||||
let data = LayerPanelEntry {
|
let data = LayerPanelEntry {
|
||||||
id: node_id,
|
id: node_id,
|
||||||
layer_classification,
|
layer_classification,
|
||||||
expanded: layer.has_children(metadata) && !collapsed.0.contains(&layer),
|
expanded: layer.has_children(metadata) && !collapsed.0.contains(&layer),
|
||||||
|
has_children: layer.has_children(metadata),
|
||||||
depth: layer.ancestors(metadata).count() - 1,
|
depth: layer.ancestors(metadata).count() - 1,
|
||||||
parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
|
parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
|
||||||
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
|
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
|
||||||
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
|
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
|
||||||
visible: node.visible,
|
visible: node.visible,
|
||||||
|
parents_visible,
|
||||||
|
unlocked: true,
|
||||||
|
parents_unlocked: true,
|
||||||
};
|
};
|
||||||
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
|
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
|
||||||
}
|
}
|
||||||
|
|
@ -918,6 +929,7 @@ impl Default for NodeGraphMessageHandler {
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
TextButton::new("Node Graph")
|
TextButton::new("Node Graph")
|
||||||
.icon(Some("GraphViewOpen".into()))
|
.icon(Some("GraphViewOpen".into()))
|
||||||
|
.hover_icon(Some("GraphViewClosed".into()))
|
||||||
.tooltip("Hide Node Graph")
|
.tooltip("Hide Node Graph")
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
||||||
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,14 @@ pub struct LayerPanelEntry {
|
||||||
#[serde(rename = "layerClassification")]
|
#[serde(rename = "layerClassification")]
|
||||||
pub layer_classification: LayerClassification,
|
pub layer_classification: LayerClassification,
|
||||||
pub expanded: bool,
|
pub expanded: bool,
|
||||||
|
#[serde(rename = "hasChildren")]
|
||||||
|
pub has_children: bool,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
|
#[serde(rename = "parentsVisible")]
|
||||||
|
pub parents_visible: bool,
|
||||||
|
pub unlocked: bool,
|
||||||
|
#[serde(rename = "parentsUnlocked")]
|
||||||
|
pub parents_unlocked: bool,
|
||||||
#[serde(rename = "parentId")]
|
#[serde(rename = "parentId")]
|
||||||
pub parent_id: Option<NodeId>,
|
pub parent_id: Option<NodeId>,
|
||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path d="M5.7,11.3l3.1-3.1l1.4-1.4l1.1-1.1L16,1h-2l-2.7,2.7C10.3,3.3,9.2,3,8,3C3,3,0,8,0,8s1.2,2.1,3.5,3.5L0,15h2L5.7,11.3z M4,8c0-2.2,1.8-4,4-4c0.8,0,1.6,0.3,2.3,0.7L9.2,5.8c-0.1,0-0.1,0-0.2,0C8.3,5.8,7.8,6.3,7.8,7c0,0.1,0,0.1,0,0.2l-3.1,3.1C4.3,9.6,4,8.8,4,8z M16,8c0,0-3,5-8,5c-0.6,0-1.2-0.1-1.8-0.2l0.9-0.9C7.4,12,7.7,12,8,12c2.2,0,4-1.8,4-4c0-0.3,0-0.6-0.1-0.9l1.8-1.8C15.2,6.6,16,8,16,8z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 467 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path d="M8,3C3,3,0,8,0,8s3,5,8,5s8-5,8-5S13,3,8,3z M8,12c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S10.2,12,8,12z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 182 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10,8H8v2H7V8H5V7h2V5h1v2h2V8z M12,3H3v9h9V3 M12,2c0.6,0,1,0.4,1,1v9c0,0.6-0.4,1-1,1H3c-0.6,0-1-0.4-1-1V3c0-0.6,0.4-1,1-1H12L12,2z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 213 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path d="M9,11.3V13H8v-1.7c-0.3-0.2-0.5-0.5-0.5-0.8c0-0.6,0.4-1,1-1s1,0.4,1,1C9.5,10.9,9.3,11.2,9,11.3z M13,9v4c0,1.1-0.9,2-2,2H6c-1.1,0-2-0.9-2-2V9c0-0.7,0.4-1.4,1-1.7V5.5C5,3.6,6.6,2,8.5,2S12,3.6,12,5.5v1.8C12.6,7.6,13,8.3,13,9z M6,7h5V5.5C11,4.1,9.9,3,8.5,3S6,4.1,6,5.5V7z M12,9c0-0.6-0.4-1-1-1H6C5.4,8,5,8.4,5,9v4c0,0.6,0.4,1,1,1h5c0.6,0,1-0.4,1-1V9z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 428 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path d="M9,11.3V13H8v-1.7c-0.3-0.2-0.5-0.5-0.5-0.8c0-0.6,0.4-1,1-1s1,0.4,1,1C9.5,10.9,9.3,11.2,9,11.3z M13,9v4c0,1.1-0.9,2-2,2H6c-1.1,0-2-0.9-2-2V9c0-1.1,0.9-2,2-2h5V4.5C11,3.1,9.9,2,8.5,2C7.1,2,6,3.1,6,4.5H5C5,2.6,6.6,1,8.5,1S12,2.6,12,4.5v2.8C12.6,7.6,13,8.3,13,9z M12,9c0-0.6-0.4-1-1-1H6C5.4,8,5,8.4,5,9v4c0,0.6,0.4,1,1,1h5c0.6,0,1-0.4,1-1V9z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 420 B |
|
|
@ -69,6 +69,7 @@
|
||||||
:root {
|
:root {
|
||||||
// Replace usage of `-rgb` variants with CSS color() function to calculate alpha when browsers support it
|
// Replace usage of `-rgb` variants with CSS color() function to calculate alpha when browsers support it
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color() and https://caniuse.com/css-color-function
|
// See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color() and https://caniuse.com/css-color-function
|
||||||
|
// 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
|
||||||
--color-0-black: #000;
|
--color-0-black: #000;
|
||||||
--color-0-black-rgb: 0, 0, 0;
|
--color-0-black-rgb: 0, 0, 0;
|
||||||
--color-1-nearblack: #111;
|
--color-1-nearblack: #111;
|
||||||
|
|
@ -138,6 +139,16 @@
|
||||||
--color-transparent-checkered-background-size: 16px 16px;
|
--color-transparent-checkered-background-size: 16px 16px;
|
||||||
--color-transparent-checkered-background-position: 0 0, 8px 8px;
|
--color-transparent-checkered-background-position: 0 0, 8px 8px;
|
||||||
|
|
||||||
|
--background-inactive-stripes: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
transparent 0px,
|
||||||
|
transparent calc((3px * sqrt(2) / 2) - 0.5px),
|
||||||
|
var(--color-5-dullgray) calc((3px * sqrt(2) / 2) - 0.5px),
|
||||||
|
var(--color-5-dullgray) calc((3px * sqrt(2) / 2) + 0.5px),
|
||||||
|
transparent calc((3px * sqrt(2) / 2) + 0.5px),
|
||||||
|
transparent calc(6px * sqrt(2) / 2)
|
||||||
|
);
|
||||||
|
|
||||||
// Arrow triangle (#eee fill)
|
// Arrow triangle (#eee fill)
|
||||||
--icon-expand-collapse-arrow: url('data:image/svg+xml;utf8,\
|
--icon-expand-collapse-arrow: url('data:image/svg+xml;utf8,\
|
||||||
<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>\
|
<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>\
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
export { styleName as style };
|
export { styleName as style };
|
||||||
export let styles: Record<string, string | number | undefined> = {};
|
export let styles: Record<string, string | number | undefined> = {};
|
||||||
export let tooltip: string | undefined = undefined;
|
export let tooltip: string | undefined = undefined;
|
||||||
|
// TODO: Add middle-click drag scrolling
|
||||||
export let scrollableX = false;
|
export let scrollableX = false;
|
||||||
export let scrollableY = false;
|
export let scrollableY = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
export { styleName as style };
|
export { styleName as style };
|
||||||
export let styles: Record<string, string | number | undefined> = {};
|
export let styles: Record<string, string | number | undefined> = {};
|
||||||
export let tooltip: string | undefined = undefined;
|
export let tooltip: string | undefined = undefined;
|
||||||
|
// TODO: Add middle-click drag scrolling
|
||||||
export let scrollableX = false;
|
export let scrollableX = false;
|
||||||
export let scrollableY = false;
|
export let scrollableY = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -377,6 +377,7 @@
|
||||||
classes={{
|
classes={{
|
||||||
selected: fakeHighlight !== undefined ? fakeHighlight === listing.entry.id : $nodeGraph.selected.includes(listing.entry.id),
|
selected: fakeHighlight !== undefined ? fakeHighlight === listing.entry.id : $nodeGraph.selected.includes(listing.entry.id),
|
||||||
"insert-folder": (draggingData?.highlightFolder || false) && draggingData?.insertParentId === listing.entry.id,
|
"insert-folder": (draggingData?.highlightFolder || false) && draggingData?.insertParentId === listing.entry.id,
|
||||||
|
"nesting-layer": isNestingLayer(listing.entry.layerClassification),
|
||||||
}}
|
}}
|
||||||
styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }}
|
styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }}
|
||||||
data-layer
|
data-layer
|
||||||
|
|
@ -387,7 +388,13 @@
|
||||||
on:click={(e) => selectLayerWithModifiers(e, listing)}
|
on:click={(e) => selectLayerWithModifiers(e, listing)}
|
||||||
>
|
>
|
||||||
{#if isNestingLayer(listing.entry.layerClassification)}
|
{#if isNestingLayer(listing.entry.layerClassification)}
|
||||||
<button class="expand-arrow" class:expanded={listing.entry.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.id)} tabindex="0" />
|
<button
|
||||||
|
class="expand-arrow"
|
||||||
|
class:expanded={listing.entry.expanded}
|
||||||
|
disabled={!listing.entry.hasChildren}
|
||||||
|
on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.id)}
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
{#if listing.entry.layerClassification === "Artboard"}
|
{#if listing.entry.layerClassification === "Artboard"}
|
||||||
<IconLabel icon="Artboard" class={"layer-type-icon"} />
|
<IconLabel icon="Artboard" class={"layer-type-icon"} />
|
||||||
{:else if listing.entry.layerClassification === "Folder"}
|
{:else if listing.entry.layerClassification === "Folder"}
|
||||||
|
|
@ -413,12 +420,25 @@
|
||||||
on:change={(e) => onEditLayerNameChange(listing, e)}
|
on:change={(e) => onEditLayerNameChange(listing, e)}
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
{#if !listing.entry.unlocked || !listing.entry.parentsUnlocked}
|
||||||
|
<IconButton
|
||||||
|
class={"status-toggle"}
|
||||||
|
classes={{ inactive: !listing.entry.parentsUnlocked }}
|
||||||
|
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
|
||||||
|
size={24}
|
||||||
|
icon={listing.entry.parentsUnlocked ? "PadlockLocked" : "PadlockUnlocked"}
|
||||||
|
hoverIcon={listing.entry.parentsUnlocked ? "PadlockUnlocked" : "PadlockLocked"}
|
||||||
|
tooltip={listing.entry.parentsUnlocked ? "Unlock" : "Lock"}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<IconButton
|
<IconButton
|
||||||
class={"visibility"}
|
class={"status-toggle"}
|
||||||
|
classes={{ inactive: !listing.entry.parentsVisible }}
|
||||||
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
|
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
|
||||||
size={24}
|
size={24}
|
||||||
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
|
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
|
||||||
tooltip={listing.entry.visible ? "Visible" : "Hidden"}
|
hoverIcon={listing.entry.visible ? "EyeHide" : "EyeShow"}
|
||||||
|
tooltip={listing.entry.visible ? "Hide" : "Show"}
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -443,16 +463,27 @@
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blend mode selector
|
|
||||||
.dropdown-input {
|
|
||||||
max-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blend mode selector and opacity slider
|
// Blend mode selector and opacity slider
|
||||||
.dropdown-input,
|
.dropdown-input,
|
||||||
.number-input {
|
.number-input {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blend mode selector
|
||||||
|
.dropdown-input {
|
||||||
|
max-width: 120px;
|
||||||
|
flex-basis: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opacity slider
|
||||||
|
.number-input {
|
||||||
|
max-width: 180px;
|
||||||
|
flex-basis: 180px;
|
||||||
|
|
||||||
|
+ .separator ~ .separator {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layer hierarchy
|
// Layer hierarchy
|
||||||
|
|
@ -464,11 +495,15 @@
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-bottom: 1px solid var(--color-2-mildblack);
|
||||||
|
border-radius: 2px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
padding-left: calc(4px + var(--layer-indent-levels) * 16px);
|
padding-left: calc(4px + var(--layer-indent-levels) * 16px);
|
||||||
border-bottom: 1px solid var(--color-2-mildblack);
|
|
||||||
border-radius: 2px;
|
&.nesting-layer {
|
||||||
|
padding-left: calc(var(--layer-indent-levels) * 16px);
|
||||||
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background: var(--color-4-dimgray);
|
background: var(--color-4-dimgray);
|
||||||
|
|
@ -493,23 +528,28 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--color-5-dullgray);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 0;
|
width: 8px;
|
||||||
height: 0;
|
height: 8px;
|
||||||
border-style: solid;
|
background: var(--icon-expand-collapse-arrow);
|
||||||
border-width: 3px 0 3px 6px;
|
}
|
||||||
border-color: transparent transparent transparent var(--color-e-nearwhite);
|
|
||||||
|
&[disabled]::after {
|
||||||
|
background: var(--icon-expand-collapse-arrow-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not([disabled]) {
|
||||||
|
background: var(--color-5-dullgray);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background: var(--icon-expand-collapse-arrow-hover);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.expanded::after {
|
&.expanded::after {
|
||||||
border-width: 6px 3px 0 3px;
|
transform: rotate(90deg);
|
||||||
border-color: var(--color-e-nearwhite) transparent transparent transparent;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -573,11 +613,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.visibility {
|
.status-toggle {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
&.inactive {
|
||||||
|
background-image: var(--background-inactive-stripes);
|
||||||
|
}
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: calc(24px + 2 * 4px);
|
width: calc(24px + 2 * 4px);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
|
|
||||||
export let icon: IconName;
|
export let icon: IconName;
|
||||||
|
export let hoverIcon: IconName | undefined = undefined;
|
||||||
export let size: IconSize;
|
export let size: IconSize;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let active = false;
|
export let active = false;
|
||||||
|
|
@ -20,8 +21,21 @@
|
||||||
.join(" ");
|
.join(" ");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class={`icon-button size-${size} ${className} ${extraClasses}`.trim()} class:disabled class:active on:click={action} {disabled} title={tooltip} tabindex={active ? -1 : 0} {...$$restProps}>
|
<button
|
||||||
|
class={`icon-button size-${size} ${className} ${extraClasses}`.trim()}
|
||||||
|
class:hover-icon={hoverIcon && !disabled}
|
||||||
|
class:disabled
|
||||||
|
class:active
|
||||||
|
on:click={action}
|
||||||
|
{disabled}
|
||||||
|
title={tooltip}
|
||||||
|
tabindex={active ? -1 : 0}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
<IconLabel {icon} />
|
<IconLabel {icon} />
|
||||||
|
{#if hoverIcon && !disabled}
|
||||||
|
<IconLabel icon={hoverIcon} />
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
|
|
@ -49,6 +63,16 @@
|
||||||
background: var(--color-5-dullgray);
|
background: var(--color-5-dullgray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hover-icon {
|
||||||
|
&:not(:hover) .icon-label:nth-of-type(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .icon-label:nth-of-type(1) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
background: none;
|
background: none;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
// However, if multiple TextButton widgets are used in a group with only some having no label, this component is able to accommodate that.
|
// However, if multiple TextButton widgets are used in a group with only some having no label, this component is able to accommodate that.
|
||||||
export let label: string;
|
export let label: string;
|
||||||
export let icon: IconName | undefined = undefined;
|
export let icon: IconName | undefined = undefined;
|
||||||
|
export let hoverIcon: IconName | undefined = undefined;
|
||||||
export let emphasized = false;
|
export let emphasized = false;
|
||||||
export let flush = false;
|
export let flush = false;
|
||||||
export let minWidth = 0;
|
export let minWidth = 0;
|
||||||
|
|
@ -54,6 +55,7 @@
|
||||||
<button
|
<button
|
||||||
class="text-button"
|
class="text-button"
|
||||||
class:open={self?.open}
|
class:open={self?.open}
|
||||||
|
class:hover-icon={hoverIcon && !disabled}
|
||||||
class:emphasized
|
class:emphasized
|
||||||
class:disabled
|
class:disabled
|
||||||
class:flush
|
class:flush
|
||||||
|
|
@ -68,6 +70,9 @@
|
||||||
>
|
>
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<IconLabel {icon} />
|
<IconLabel {icon} />
|
||||||
|
{#if hoverIcon && !disabled}
|
||||||
|
<IconLabel icon={hoverIcon} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if icon && label}
|
{#if icon && label}
|
||||||
<Separator type={flush ? "Unrelated" : "Related"} />
|
<Separator type={flush ? "Unrelated" : "Related"} />
|
||||||
|
|
@ -118,6 +123,16 @@
|
||||||
--button-text-color: var(--color-f-white);
|
--button-text-color: var(--color-f-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hover-icon {
|
||||||
|
&:not(:hover) .icon-label:nth-of-type(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .icon-label:nth-of-type(1) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
--button-background-color: var(--color-4-dimgray);
|
--button-background-color: var(--color-4-dimgray);
|
||||||
--button-text-color: var(--color-8-uppergray);
|
--button-text-color: var(--color-8-uppergray);
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,8 @@ import Credits from "@graphite-frontend/assets/icon-16px-solid/credits.svg";
|
||||||
import CustomColor from "@graphite-frontend/assets/icon-16px-solid/custom-color.svg";
|
import CustomColor from "@graphite-frontend/assets/icon-16px-solid/custom-color.svg";
|
||||||
import Edit from "@graphite-frontend/assets/icon-16px-solid/edit.svg";
|
import Edit from "@graphite-frontend/assets/icon-16px-solid/edit.svg";
|
||||||
import EyeHidden from "@graphite-frontend/assets/icon-16px-solid/eye-hidden.svg";
|
import EyeHidden from "@graphite-frontend/assets/icon-16px-solid/eye-hidden.svg";
|
||||||
|
import EyeHide from "@graphite-frontend/assets/icon-16px-solid/eye-hide.svg";
|
||||||
|
import EyeShow from "@graphite-frontend/assets/icon-16px-solid/eye-show.svg";
|
||||||
import EyeVisible from "@graphite-frontend/assets/icon-16px-solid/eye-visible.svg";
|
import EyeVisible from "@graphite-frontend/assets/icon-16px-solid/eye-visible.svg";
|
||||||
import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg";
|
import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg";
|
||||||
import File from "@graphite-frontend/assets/icon-16px-solid/file.svg";
|
import File from "@graphite-frontend/assets/icon-16px-solid/file.svg";
|
||||||
|
|
@ -119,6 +121,7 @@ import IconsGrid from "@graphite-frontend/assets/icon-16px-solid/icons-grid.svg"
|
||||||
import Image from "@graphite-frontend/assets/icon-16px-solid/image.svg";
|
import Image from "@graphite-frontend/assets/icon-16px-solid/image.svg";
|
||||||
import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg";
|
import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg";
|
||||||
import License from "@graphite-frontend/assets/icon-16px-solid/license.svg";
|
import License from "@graphite-frontend/assets/icon-16px-solid/license.svg";
|
||||||
|
import NewLayer from "@graphite-frontend/assets/icon-16px-solid/new-layer.svg";
|
||||||
import NodeBlur from "@graphite-frontend/assets/icon-16px-solid/node-blur.svg";
|
import NodeBlur from "@graphite-frontend/assets/icon-16px-solid/node-blur.svg";
|
||||||
import NodeBrushwork from "@graphite-frontend/assets/icon-16px-solid/node-brushwork.svg";
|
import NodeBrushwork from "@graphite-frontend/assets/icon-16px-solid/node-brushwork.svg";
|
||||||
import NodeColorCorrection from "@graphite-frontend/assets/icon-16px-solid/node-color-correction.svg";
|
import NodeColorCorrection from "@graphite-frontend/assets/icon-16px-solid/node-color-correction.svg";
|
||||||
|
|
@ -132,6 +135,8 @@ import NodeOutput from "@graphite-frontend/assets/icon-16px-solid/node-output.sv
|
||||||
import NodeShape from "@graphite-frontend/assets/icon-16px-solid/node-shape.svg";
|
import NodeShape from "@graphite-frontend/assets/icon-16px-solid/node-shape.svg";
|
||||||
import NodeText from "@graphite-frontend/assets/icon-16px-solid/node-text.svg";
|
import NodeText from "@graphite-frontend/assets/icon-16px-solid/node-text.svg";
|
||||||
import NodeTransform from "@graphite-frontend/assets/icon-16px-solid/node-transform.svg";
|
import NodeTransform from "@graphite-frontend/assets/icon-16px-solid/node-transform.svg";
|
||||||
|
import PadlockLocked from "@graphite-frontend/assets/icon-16px-solid/padlock-locked.svg";
|
||||||
|
import PadlockUnlocked from "@graphite-frontend/assets/icon-16px-solid/padlock-unlocked.svg";
|
||||||
import Paste from "@graphite-frontend/assets/icon-16px-solid/paste.svg";
|
import Paste from "@graphite-frontend/assets/icon-16px-solid/paste.svg";
|
||||||
import Random from "@graphite-frontend/assets/icon-16px-solid/random.svg";
|
import Random from "@graphite-frontend/assets/icon-16px-solid/random.svg";
|
||||||
import Regenerate from "@graphite-frontend/assets/icon-16px-solid/regenerate.svg";
|
import Regenerate from "@graphite-frontend/assets/icon-16px-solid/regenerate.svg";
|
||||||
|
|
@ -175,6 +180,8 @@ const SOLID_16PX = {
|
||||||
Edit: { svg: Edit, size: 16 },
|
Edit: { svg: Edit, size: 16 },
|
||||||
Eyedropper: { svg: Eyedropper, size: 16 },
|
Eyedropper: { svg: Eyedropper, size: 16 },
|
||||||
EyeHidden: { svg: EyeHidden, size: 16 },
|
EyeHidden: { svg: EyeHidden, size: 16 },
|
||||||
|
EyeHide: { svg: EyeHide, size: 16 },
|
||||||
|
EyeShow: { svg: EyeShow, size: 16 },
|
||||||
EyeVisible: { svg: EyeVisible, size: 16 },
|
EyeVisible: { svg: EyeVisible, size: 16 },
|
||||||
File: { svg: File, size: 16 },
|
File: { svg: File, size: 16 },
|
||||||
FlipHorizontal: { svg: FlipHorizontal, size: 16 },
|
FlipHorizontal: { svg: FlipHorizontal, size: 16 },
|
||||||
|
|
@ -187,6 +194,7 @@ const SOLID_16PX = {
|
||||||
Image: { svg: Image, size: 16 },
|
Image: { svg: Image, size: 16 },
|
||||||
Layer: { svg: Layer, size: 16 },
|
Layer: { svg: Layer, size: 16 },
|
||||||
License: { svg: License, size: 16 },
|
License: { svg: License, size: 16 },
|
||||||
|
NewLayer: { svg: NewLayer, size: 16 },
|
||||||
NodeBlur: { svg: NodeBlur, size: 16 },
|
NodeBlur: { svg: NodeBlur, size: 16 },
|
||||||
NodeBrushwork: { svg: NodeBrushwork, size: 16 },
|
NodeBrushwork: { svg: NodeBrushwork, size: 16 },
|
||||||
NodeColorCorrection: { svg: NodeColorCorrection, size: 16 },
|
NodeColorCorrection: { svg: NodeColorCorrection, size: 16 },
|
||||||
|
|
@ -200,6 +208,8 @@ const SOLID_16PX = {
|
||||||
NodeShape: { svg: NodeShape, size: 16 },
|
NodeShape: { svg: NodeShape, size: 16 },
|
||||||
NodeText: { svg: NodeText, size: 16 },
|
NodeText: { svg: NodeText, size: 16 },
|
||||||
NodeTransform: { svg: NodeTransform, size: 16 },
|
NodeTransform: { svg: NodeTransform, size: 16 },
|
||||||
|
PadlockLocked: { svg: PadlockLocked, size: 16 },
|
||||||
|
PadlockUnlocked: { svg: PadlockUnlocked, size: 16 },
|
||||||
Paste: { svg: Paste, size: 16 },
|
Paste: { svg: Paste, size: 16 },
|
||||||
Random: { svg: Random, size: 16 },
|
Random: { svg: Random, size: 16 },
|
||||||
Regenerate: { svg: Regenerate, size: 16 },
|
Regenerate: { svg: Regenerate, size: 16 },
|
||||||
|
|
|
||||||
|
|
@ -613,8 +613,16 @@ export class LayerPanelEntry {
|
||||||
|
|
||||||
expanded!: boolean;
|
expanded!: boolean;
|
||||||
|
|
||||||
|
hasChildren!: boolean;
|
||||||
|
|
||||||
visible!: boolean;
|
visible!: boolean;
|
||||||
|
|
||||||
|
parentsVisible!: boolean;
|
||||||
|
|
||||||
|
unlocked!: boolean;
|
||||||
|
|
||||||
|
parentsUnlocked!: boolean;
|
||||||
|
|
||||||
parentId!: bigint | undefined;
|
parentId!: bigint | undefined;
|
||||||
|
|
||||||
id!: bigint;
|
id!: bigint;
|
||||||
|
|
@ -769,6 +777,8 @@ export class FontInput extends WidgetProps {
|
||||||
export class IconButton extends WidgetProps {
|
export class IconButton extends WidgetProps {
|
||||||
icon!: IconName;
|
icon!: IconName;
|
||||||
|
|
||||||
|
hoverIcon!: IconName | undefined;
|
||||||
|
|
||||||
size!: IconSize;
|
size!: IconSize;
|
||||||
|
|
||||||
disabled!: boolean;
|
disabled!: boolean;
|
||||||
|
|
@ -934,6 +944,8 @@ export class TextButton extends WidgetProps {
|
||||||
|
|
||||||
icon!: IconName | undefined;
|
icon!: IconName | undefined;
|
||||||
|
|
||||||
|
hoverIcon!: IconName | undefined;
|
||||||
|
|
||||||
emphasized!: boolean;
|
emphasized!: boolean;
|
||||||
|
|
||||||
flush!: boolean;
|
flush!: boolean;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue