Skip to content

Commit

Permalink
Move selections to a new layer (#9344)
Browse files Browse the repository at this point in the history
Move selections to a new Vue component so that they don't interfere with clicking on edges.

https://github.com/enso-org/enso/assets/1047859/190006df-1402-4a5f-9117-f43788fa4187

# Important Notes
- The new `GraphNodeSelection` components are drawn in a `GraphNodeSelections` container. They are created by the `GraphNode`s and teleported so that the node can set properties directly.
  • Loading branch information
kazcw authored Mar 11, 2024
1 parent b2215be commit 1f6db1e
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 54 deletions.
4 changes: 3 additions & 1 deletion app/gui2/e2e/selectingNodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ test('Selecting nodes by area drag', async ({ page }) => {
await expect(node1).not.toBeSelected()
await expect(node2).not.toBeSelected()

const node1BBox = await node1.locator('.selection').boundingBox()
const node1Id = await node1.getAttribute('data-node-id')
const node1Selection = page.locator(`.GraphNodeSelection[data-node-id="${node1Id}"]`)
const node1BBox = await node1Selection.boundingBox()
const node2BBox = await node2.boundingBox()
assert(node1BBox)
assert(node2BBox)
Expand Down
1 change: 1 addition & 0 deletions app/gui2/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
* A `border-radius` of 100% does not work because the element becomes an ellipse. */
--radius-full: 9999px;
--radius-default: 16px;
--node-border-radius: 16px;
--section-gap: 160px;
--selected-node-border-width: 20px;
--font-sans: 'M PLUS 1', /* System sans-serif font stack */ system-ui, -apple-system,
Expand Down
9 changes: 7 additions & 2 deletions app/gui2/src/components/GraphEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -608,13 +608,18 @@ function handleEdgeDrop(source: AstId, position: Vec2) {
@dragover.prevent
@drop.prevent="handleFileDrop($event)"
>
<div :style="{ transform: graphNavigator.transform }" class="htmlLayer">
<div class="layer" :style="{ transform: graphNavigator.transform }">
<GraphNodes
@nodeOutputPortDoubleClick="handleNodeOutputPortDoubleClick"
@nodeDoubleClick="(id) => stackNavigator.enterNode(id)"
@addNode="addNodeAt($event)"
/>
</div>
<div
id="graphNodeSelections"
class="layer"
:style="{ transform: graphNavigator.transform, 'z-index': -1 }"
/>
<GraphEdges :navigator="graphNavigator" @createNodeFromEdge="handleEdgeDrop" />

<ComponentBrowser
Expand Down Expand Up @@ -660,7 +665,7 @@ function handleEdgeDrop(source: AstId, position: Vec2) {
--node-color-no-type: #596b81;
}
.htmlLayer {
.layer {
position: absolute;
top: 0;
left: 0;
Expand Down
1 change: 1 addition & 0 deletions app/gui2/src/components/GraphEditor/GraphEdge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ const connected = computed(() => isConnected(props.edge))
.edge.io {
stroke-width: 14;
stroke: transparent;
pointer-events: stroke;
}
.edge.visible {
stroke-width: 4;
Expand Down
5 changes: 1 addition & 4 deletions app/gui2/src/components/GraphEditor/GraphEdges.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function createEdge(source: AstId, target: PortId) {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
.overlay.behindNodes {
Expand All @@ -131,8 +132,4 @@ function createEdge(source: AstId, target: PortId) {
.overlay.aboveNodes {
z-index: 20;
}
.nonInteractive {
pointer-events: none;
}
</style>
69 changes: 22 additions & 47 deletions app/gui2/src/components/GraphEditor/GraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { nodeEditBindings } from '@/bindings'
import CircularMenu from '@/components/CircularMenu.vue'
import GraphNodeComment from '@/components/GraphEditor/GraphNodeComment.vue'
import GraphNodeError from '@/components/GraphEditor/GraphNodeMessage.vue'
import GraphNodeSelection from '@/components/GraphEditor/GraphNodeSelection.vue'
import GraphVisualization from '@/components/GraphEditor/GraphVisualization.vue'
import NodeWidgetTree from '@/components/GraphEditor/NodeWidgetTree.vue'
import SvgIcon from '@/components/SvgIcon.vue'
Expand Down Expand Up @@ -339,6 +340,9 @@ const documentation = computed<string | undefined>({
})
},
})
const selected = computed(() => nodeSelection?.isSelected(nodeId.value) ?? false)
const selectionVisible = ref(false)
</script>

<template>
Expand All @@ -352,19 +356,28 @@ const documentation = computed<string | undefined>({
}"
:class="{
edited: props.edited,
dragging: dragPointer.dragging,
selected: nodeSelection?.isSelected(nodeId),
selected,
selectionVisible,
visualizationVisible: isVisualizationVisible,
['executionState-' + executionState]: true,
}"
:data-node-id="nodeId"
@pointerenter="nodeHovered = true"
@pointerleave="nodeHovered = false"
>
<div class="selection" v-on="dragPointer.events"></div>
<div class="binding" @pointerdown.stop>
{{ node.pattern?.code() ?? '' }}
</div>
<Teleport to="#graphNodeSelections">
<GraphNodeSelection
v-if="navigator"
:nodePosition="props.node.position"
:nodeSize="nodeSize"
:selected
:nodeId
:color
@visible="selectionVisible = $event"
v-on="dragPointer.events"
/>
</Teleport>
<div class="binding" @pointerdown.stop v-text="node.pattern?.code()" />
<button
v-if="!menuVisible && isRecordingOverridden"
class="overrideRecordButton"
Expand Down Expand Up @@ -544,7 +557,6 @@ const documentation = computed<string | undefined>({
.GraphNode {
--node-height: 32px;
--node-border-radius: 16px;
--node-color-primary: color-mix(
in oklab,
Expand Down Expand Up @@ -591,45 +603,10 @@ const documentation = computed<string | undefined>({
outline: 0px solid transparent;
}
.GraphNode .selection {
position: absolute;
inset: calc(0px - var(--selected-node-border-width));
--node-current-selection-width: 0px;
&:before {
content: '';
opacity: 0;
position: absolute;
border-radius: var(--node-border-radius);
display: block;
inset: var(--selected-node-border-width);
box-shadow: 0 0 0 var(--node-current-selection-width) var(--node-color-primary);
transition:
box-shadow 0.2s ease-in-out,
opacity 0.2s ease-in-out;
}
}
.GraphNode:is(:hover, .selected) .selection:before,
.GraphNode .selection:hover:before {
--node-current-selection-width: var(--selected-node-border-width);
}
.GraphNode .selection:hover:before {
opacity: 0.15;
}
.GraphNode.selected .selection:before {
opacity: 0.2;
}
.GraphNode.selected .selection:hover:before {
opacity: 0.3;
}
.binding {
font-family: var(--font-code);
user-select: none;
pointer-events: none;
margin-right: 10px;
color: black;
position: absolute;
Expand All @@ -641,8 +618,7 @@ const documentation = computed<string | undefined>({
white-space: nowrap;
}
.GraphNode .selection:hover + .binding,
.GraphNode.selected .binding {
.selectionVisible .binding {
opacity: 1;
}
Expand Down Expand Up @@ -695,8 +671,7 @@ const documentation = computed<string | undefined>({
transition: opacity 0.2s ease-in-out;
}
.GraphNode:is(:hover, .selected) .statuses,
.GraphNode:has(.selection:hover) .statuses {
.GraphNode.selectionVisible .statuses {
opacity: 0;
}
Expand Down
71 changes: 71 additions & 0 deletions app/gui2/src/components/GraphEditor/GraphNodeSelection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { Vec2 } from '@/util/data/vec2'
import type { AstId } from 'shared/ast'
import { computed, ref, watchEffect } from 'vue'
const props = defineProps<{
nodePosition: Vec2
nodeSize: Vec2
nodeId: AstId
selected: boolean
color: string
}>()
const emit = defineEmits<{
visible: [boolean]
}>()
const hovered = ref(false)
const visible = computed(() => props.selected || hovered.value)
watchEffect(() => emit('visible', visible.value))
const transform = computed(() => {
const { x, y } = props.nodePosition
return `translate(${x}px, ${y}px)`
})
const nodeWidthPx = computed(() => `${props.nodeSize.x}px`)
const nodeHeightPx = computed(() => `${props.nodeSize.y}px`)
</script>

<template>
<div
class="GraphNodeSelection"
:class="{ visible, selected: props.selected }"
:style="{ transform }"
:data-node-id="props.nodeId"
@pointerenter="hovered = true"
@pointerleave="hovered = false"
/>
</template>

<style scoped>
.GraphNodeSelection {
position: absolute;
inset: calc(0px - var(--selected-node-border-width));
width: calc(var(--selected-node-border-width) * 2 + v-bind('nodeWidthPx'));
height: calc(var(--selected-node-border-width) * 2 + v-bind('nodeHeightPx'));
&:before {
position: absolute;
content: '';
opacity: 0.2;
display: block;
inset: var(--selected-node-border-width);
box-shadow: 0 0 0 calc(0px - var(--node-border-radius)) v-bind('props.color');
border-radius: var(--node-border-radius);
transition:
box-shadow 0.2s ease-in-out,
opacity 0.2s ease-in-out;
}
}
.GraphNodeSelection.visible::before {
box-shadow: 0 0 0 var(--selected-node-border-width) v-bind('props.color');
}
.GraphNodeSelection:not(.selected):hover::before {
opacity: 0.3;
}
</style>

0 comments on commit 1f6db1e

Please sign in to comment.