Redesign the Layer Tree UI (#468)

This commit is contained in:
Keavon Chambers
2022-01-07 23:13:33 -08:00
parent 8e35424c43
commit f6bfe595f4
+85 -86
View File
@@ -29,9 +29,9 @@
</PopoverButton> </PopoverButton>
</LayoutRow> </LayoutRow>
<LayoutRow :class="'layer-tree scrollable-y'"> <LayoutRow :class="'layer-tree scrollable-y'">
<LayoutCol :class="'list'" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="updateLine($event)" @dragend="drop()"> <LayoutCol :class="'list'" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="updateInsertLine($event)" @dragend="drop()">
<div class="layer-row" v-for="(layer, index) in layers" :key="String(layer.path.slice(-1))"> <div class="layer-row" v-for="(layer, index) in layers" :key="String(layer.path.slice(-1))">
<div class="layer-visibility"> <div class="visibility">
<IconButton <IconButton
:action="(e) => (toggleLayerVisibility(layer.path), e && e.stopPropagation())" :action="(e) => (toggleLayerVisibility(layer.path), e && e.stopPropagation())"
:icon="layer.visible ? 'EyeVisible' : 'EyeHidden'" :icon="layer.visible ? 'EyeVisible' : 'EyeHidden'"
@@ -39,17 +39,11 @@
:title="layer.visible ? 'Visible' : 'Hidden'" :title="layer.visible ? 'Visible' : 'Hidden'"
/> />
</div> </div>
<button <div class="indent" :style="{ marginLeft: layerIndent(layer) }"></div>
v-if="layer.layer_type === 'Folder'" <button v-if="layer.layer_type === 'Folder'" class="expand-arrow" :class="{ expanded: layer.layer_metadata.expanded }" @click.stop="handleExpandArrowClick(layer.path)"></button>
class="node-connector"
:class="{ expanded: layer.layer_metadata.expanded }"
@click.stop="handleNodeConnectorClick(layer.path)"
></button>
<div v-else class="node-connector-missing"></div>
<div <div
class="layer" class="layer"
:class="{ selected: layer.layer_metadata.selected }" :class="{ selected: layer.layer_metadata.selected }"
:style="{ marginLeft: layerIndent(layer) }"
@click.shift.exact.stop="selectLayer(layer, false, true)" @click.shift.exact.stop="selectLayer(layer, false, true)"
@click.shift.ctrl.exact.stop="selectLayer(layer, true, true)" @click.shift.ctrl.exact.stop="selectLayer(layer, true, true)"
@click.ctrl.exact.stop="selectLayer(layer, true, false)" @click.ctrl.exact.stop="selectLayer(layer, true, false)"
@@ -59,7 +53,6 @@
@dragstart="dragStart($event, layer)" @dragstart="dragStart($event, layer)"
:title="layer.path" :title="layer.path"
> >
<div class="layer-thumbnail" v-html="layer.thumbnail"></div>
<div class="layer-type-icon"> <div class="layer-type-icon">
<IconLabel v-if="layer.layer_type === 'Folder'" :icon="'NodeTypeFolder'" title="Folder" /> <IconLabel v-if="layer.layer_type === 'Folder'" :icon="'NodeTypeFolder'" title="Folder" />
<IconLabel v-else :icon="'NodeTypePath'" title="Path" /> <IconLabel v-else :icon="'NodeTypePath'" title="Path" />
@@ -67,8 +60,8 @@
<div class="layer-name"> <div class="layer-name">
<span>{{ layer.name }}</span> <span>{{ layer.name }}</span>
</div> </div>
<div class="thumbnail" v-html="layer.thumbnail"></div>
</div> </div>
<!-- <div class="glue" :style="{ marginLeft: layerIndent(layer) }"></div> -->
</div> </div>
</LayoutCol> </LayoutCol>
</LayoutRow> </LayoutRow>
@@ -96,78 +89,112 @@
} }
.layer-tree { .layer-tree {
// Crop away the 1px border below the bottom layer entry when it uses the full space of this panel
margin-bottom: -1px;
.layer-row { .layer-row {
display: flex;
height: 36px;
align-items: center;
flex: 0 0 auto; flex: 0 0 auto;
display: flex;
align-items: center;
position: relative; position: relative;
height: 36px;
margin: 0 4px;
border-bottom: 1px solid var(--color-4-dimgray);
& + .layer-row, .visibility {
& + .insert-mark + .layer-row { height: 100%;
margin-top: 2px; flex: 0 0 auto;
display: flex;
align-items: center;
.icon-button {
height: 100%;
width: calc(24px + 2 * 4px);
}
} }
.layer-visibility { .expand-arrow {
flex: 0 0 auto; margin-left: -16px;
margin-left: 4px; width: 16px;
} height: 100%;
padding: 0;
.node-connector {
flex: 0 0 auto;
width: 12px;
height: 12px;
margin: 0 2px;
border-radius: 50%;
background: var(--color-data-raster);
outline: none; outline: none;
border: none; border: none;
position: relative; position: relative;
background: none;
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
&:hover {
background: var(--color-6-lowergray);
}
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
width: 0; width: 0;
height: 0; height: 0;
top: 3px;
left: 4px;
border-style: solid; border-style: solid;
border-width: 3px 0 3px 6px; border-width: 3px 0 3px 6px;
border-color: transparent transparent transparent var(--color-2-mildblack); border-color: transparent transparent transparent var(--color-e-nearwhite);
&:hover {
color: var(--color-f-white);
}
} }
&.expanded::after { &.expanded::after {
top: 4px;
left: 3px;
border-width: 6px 3px 0 3px; border-width: 6px 3px 0 3px;
border-color: var(--color-2-mildblack) transparent transparent transparent; border-color: var(--color-e-nearwhite) transparent transparent transparent;
}
}
.node-connector-missing { &:hover {
width: 16px; color: var(--color-f-white);
flex: 0 0 auto; }
}
} }
.layer { .layer {
display: flex; display: flex;
min-width: 0;
align-items: center; align-items: center;
border-radius: 2px; z-index: 1;
background: var(--color-5-dullgray); min-width: 0;
margin-right: 16px;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 1; border-radius: 2px;
padding: 0 4px;
margin-right: 8px;
&.selected { &.selected {
background: var(--color-7-middlegray); background: var(--color-5-dullgray);
color: var(--color-f-white); color: var(--color-f-white);
} }
.layer-thumbnail { .layer-type-icon {
flex: 0 0 auto;
margin: 0 4px;
}
.layer-name {
flex: 1 1 100%;
display: flex;
min-width: 0;
margin: 0 4px;
span {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
.thumbnail {
height: calc(100% - 4px);
margin: 2px 0;
margin-left: 4px;
width: 64px; width: 64px;
height: 100%;
background: white; background: white;
border-radius: 2px; border-radius: 2px;
flex: 0 0 auto; flex: 0 0 auto;
@@ -178,35 +205,6 @@
margin: 2px; margin: 2px;
} }
} }
.layer-type-icon {
margin-left: 8px;
margin-right: 4px;
flex: 0 0 auto;
}
.layer-name {
display: flex;
min-width: 0;
flex: 1 1 100%;
margin-right: 8px;
span {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
}
.glue {
position: absolute;
background: var(--color-data-raster);
height: 6px;
bottom: -4px;
left: 44px;
right: 16px;
z-index: 0;
} }
} }
@@ -221,11 +219,11 @@
position: absolute; position: absolute;
background: var(--color-accent-hover); background: var(--color-accent-hover);
width: 100%; width: 100%;
height: 6px; height: 5px;
} }
&:not(:first-child, :last-child) { &:not(:first-child, :last-child) {
top: -2px; top: -3px;
} }
&:first-child::after { &:first-child::after {
@@ -233,7 +231,8 @@
} }
&:last-child::after { &:last-child::after {
bottom: 0; // Shifted up 1px to account for the shifting down of the entire `.layer-tree` panel
bottom: 1px;
} }
} }
} }
@@ -318,12 +317,12 @@ export default defineComponent({
}, },
methods: { methods: {
layerIndent(layer: LayerPanelEntry) { layerIndent(layer: LayerPanelEntry) {
return `${(layer.path.length - 1) * 16}px`; return `${layer.path.length * 16}px`;
}, },
async toggleLayerVisibility(path: BigUint64Array) { async toggleLayerVisibility(path: BigUint64Array) {
this.editor.instance.toggle_layer_visibility(path); this.editor.instance.toggle_layer_visibility(path);
}, },
async handleNodeConnectorClick(path: BigUint64Array) { async handleExpandArrowClick(path: BigUint64Array) {
this.editor.instance.toggle_layer_expansion(path); this.editor.instance.toggle_layer_expansion(path);
}, },
async setLayerBlendMode(newSelectedIndex: number) { async setLayerBlendMode(newSelectedIndex: number) {
@@ -416,15 +415,15 @@ export default defineComponent({
const [nearestPath, above, nearestElement] = this.closest(tree, event.clientY); const [nearestPath, above, nearestElement] = this.closest(tree, event.clientY);
// Set the initial state of the line // Set the initial state of the insert line
if (nearestElement.parentNode) { if (nearestElement.parentNode) {
insertLine.style.marginLeft = `${LAYER_LEFT_MARGIN_OFFSET + LAYER_LEFT_INDENT_OFFSET * nearestPath.length}px`; insertLine.style.marginLeft = `${LAYER_LEFT_MARGIN_OFFSET + LAYER_LEFT_INDENT_OFFSET * nearestPath.length}px`; // TODO: use layerIndent function to calculate this
tree.insertBefore(insertLine, nearestElement); tree.insertBefore(insertLine, nearestElement);
} }
this.draggingData = { path: layer.path, above, nearestPath, insertLine }; this.draggingData = { path: layer.path, above, nearestPath, insertLine };
}, },
updateLine(event: DragEvent) { updateInsertLine(event: DragEvent) {
// Stop the drag from being shown as cancelled // Stop the drag from being shown as cancelled
event.preventDefault(); event.preventDefault();