Skip to content

Commit

Permalink
Implement output port double click.
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMauderer committed Nov 20, 2023
1 parent 8f576e3 commit 705c06b
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 19 deletions.
61 changes: 45 additions & 16 deletions app/gui2/src/components/GraphEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const EXECUTION_MODES = ['design', 'live']
const COMPONENT_BROWSER_TO_NODE_OFFSET = new Vec2(20, 35)
// Assumed size of a newly created node. This is used to place the component browser.
const DEFAULT_NODE_SIZE = new Vec2(0, 24)
const gapBetweenNodes = 48.0
const viewportNode = ref<HTMLElement>()
const graphNavigator = provideGraphNavigator(viewportNode)
Expand All @@ -58,6 +59,20 @@ const interactionBindingsHandler = interactionBindings.handler({
click: (e) => (e instanceof MouseEvent ? interaction.handleClick(e, graphNavigator) : false),
})
function environmentForNodes(nodeIds: IterableIterator<ExprId>): Environment {
const nodeRects = [...graphStore.nodeRects.values()]
const selectedNodeRects: Iterable<Rect> = [...nodeIds]
.map((id) => graphStore.nodeRects.get(id))
.filter((item): item is Rect => item !== undefined)
const screenBounds = graphNavigator.viewport
const mousePosition = graphNavigator.sceneMousePos
return { nodeRects, selectedNodeRects, screenBounds, mousePosition } as Environment
}
const placementEnvironment = computed(() => {
return environmentForNodes(nodeSelection.selected.values())
})
// Return the position for a new node, assuming there are currently nodes selected. If there are no nodes
// selected, return undefined.
function placementPositionForSelection() {
Expand Down Expand Up @@ -90,10 +105,12 @@ function targetComponentBrowserPosition() {
// This is the current position of the component browser.
const componentBrowserPosition = ref<Vec2>(Vec2.Zero)
const graphEditorSourceNode = computed(() => {
function sourceNodeForSelection() {
if (graphStore.editedNodeInfo != null) return undefined
return nodeSelection.selected.values().next().value
})
}
const componentBrowserSourceNode = ref<ExprId | undefined>(sourceNodeForSelection())
useEvent(window, 'keydown', (event) => {
interactionBindingsHandler(event) || graphBindingsHandler(event) || codeEditorHandler(event)
Expand Down Expand Up @@ -206,21 +223,10 @@ const editingNode: Interaction = {
const nodeIsBeingEdited = computed(() => graphStore.editedNodeInfo != null)
interaction.setWhen(nodeIsBeingEdited, editingNode)
const placementEnvironment = computed(() => {
const mousePosition = graphNavigator.sceneMousePos ?? Vec2.Zero
const nodeRects = [...graphStore.nodeRects.values()]
const selectedNodesIter = nodeSelection.selected.values()
const selectedNodeRects: Iterable<Rect> = [...selectedNodesIter]
.map((id) => graphStore.nodeRects.get(id))
.filter((item): item is Rect => item !== undefined)
const screenBounds = graphNavigator.viewport
const environment: Environment = { mousePosition, nodeRects, selectedNodeRects, screenBounds }
return environment
})
const creatingNode: Interaction = {
init: () => {
componentBrowserInputContent.value = ''
componentBrowserSourceNode.value = sourceNodeForSelection()
componentBrowserPosition.value = targetComponentBrowserPosition()
componentBrowserVisible.value = true
},
Expand All @@ -244,6 +250,16 @@ const creatingNodeFromButton: Interaction = {
},
}
const creatingNodeFromPortDoubleClick: Interaction = {
init: () => {
componentBrowserInputContent.value = ''
componentBrowserVisible.value = true
},
cancel: () => {
// Nothing to do here. We just don't create a node and the component browser will close itself.
},
}
async function handleFileDrop(event: DragEvent) {
// A vertical gap between created nodes when multiple files were dropped together.
const MULTIPLE_FILES_GAP = 50
Expand Down Expand Up @@ -403,6 +419,19 @@ async function readNodeFromClipboard() {
console.warn('No valid expression in clipboard.')
}
}
function handleNodeOutputPortDoubleClick(id: ExprId) {
componentBrowserSourceNode.value = id
const placementEnvironment = environmentForNodes([id].values())
componentBrowserPosition.value = previousNodeDictatedPlacement(
DEFAULT_NODE_SIZE,
placementEnvironment,
{
gap: gapBetweenNodes,
},
).position
interaction.setCurrent(creatingNodeFromPortDoubleClick)
}
</script>

<template>
Expand All @@ -422,7 +451,7 @@ async function readNodeFromClipboard() {
<GraphEdges />
</svg>
<div :style="{ transform: graphNavigator.transform }" class="htmlLayer">
<GraphNodes />
<GraphNodes @nodeOutputPortDoubleClick="handleNodeOutputPortDoubleClick" />
</div>
<ComponentBrowser
v-if="componentBrowserVisible"
Expand All @@ -434,7 +463,7 @@ async function readNodeFromClipboard() {
@canceled="onComponentBrowserCancel"
:initialContent="componentBrowserInputContent"
:initialCaretPosition="graphStore.editedNodeInfo?.range ?? [0, 0]"
:sourceNode="graphEditorSourceNode"
:sourceNode="componentBrowserSourceNode"
/>
<TopBar
v-model:mode="projectStore.executionMode"
Expand Down
27 changes: 25 additions & 2 deletions app/gui2/src/components/GraphEditor/GraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ const emit = defineEmits<{
delete: []
replaceSelection: []
'update:selected': [selected: boolean]
outputPortAction: []
outputPortClick: []
outputPortDoubleClick: []
'update:edited': [cursorPosition: number]
}>()
Expand Down Expand Up @@ -191,6 +192,28 @@ function getRelatedSpanOffset(domNode: globalThis.Node, domOffset: number): numb
}
return 0
}
const timeBetweenClicks = 200
let lastClickTime = 0
let clickCount = 0
let singleClickTimer: ReturnType<typeof setTimeout>
function handlePortClick() {
clickCount++
if (clickCount === 1) {
singleClickTimer = setTimeout(function () {
clickCount = 0
// If within proper time range, consider it as fast clicks
if (Date.now() - lastClickTime >= timeBetweenClicks) {
emit('outputPortClick')
}
lastClickTime = Date.now()
}, timeBetweenClicks)
} else if (clickCount === 2) {
clearTimeout(singleClickTimer)
clickCount = 0
emit('outputPortDoubleClick')
}
}
</script>

<template>
Expand Down Expand Up @@ -245,7 +268,7 @@ function getRelatedSpanOffset(domNode: globalThis.Node, domOffset: number): numb
class="outputPortHoverArea"
@pointerenter="outputHovered = true"
@pointerleave="outputHovered = false"
@pointerdown.stop.prevent="emit('outputPortAction')"
@pointerdown="handlePortClick"
/>
<rect class="outputPort" />
<text class="outputTypeName">{{ outputTypeName }}</text>
Expand Down
7 changes: 6 additions & 1 deletion app/gui2/src/components/GraphEditor/GraphNodes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const dragging = useDragging()
const selection = injectGraphSelection(true)
const navigator = injectGraphNavigator(true)
const emit = defineEmits<{
nodeOutputPortDoubleClick: [nodeId: ExprId]
}>()
function updateNodeContent(id: ExprId, updates: [ContentRange, string][]) {
graphStore.transact(() => {
for (const [range, content] of updates) {
Expand Down Expand Up @@ -59,7 +63,8 @@ const uploadingFiles = computed<[FileName, File][]>(() => {
@setVisualizationVisible="graphStore.setNodeVisualizationVisible(id, $event)"
@dragging="nodeIsDragged(id, $event)"
@draggingCommited="dragging.finishDrag()"
@outputPortAction="graphStore.createEdgeFromOutput(id)"
@outputPortClick="graphStore.createEdgeFromOutput(id)"
@outputPortDoubleClick="emit('nodeOutputPortDoubleClick', id)"
/>
<UploadingFile
v-for="(nameAndFile, index) in uploadingFiles"
Expand Down

0 comments on commit 705c06b

Please sign in to comment.