Skip to content

Commit

Permalink
Vector editor widget (#8361)
Browse files Browse the repository at this point in the history
- Closes #8261
- Add vector editor widget
- Adding element to end
- Dragging elements
- Removing elements by dragging away

# Important Notes
- Both <kbd>Left Click</kbd> and <kbd>Ctrl</kbd> + <kbd>Left Click</kbd> are supported to begin a drag.
- Just <kbd>Left Click</kbd> alone is not sufficient as some widgets interact via left click
  • Loading branch information
somebody1234 authored Nov 30, 2023
1 parent 2d35be9 commit 2707262
Show file tree
Hide file tree
Showing 15 changed files with 810 additions and 37 deletions.
5 changes: 4 additions & 1 deletion app/gui2/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { provideAppClassSet } from '@/providers/appClass'
import { provideGuiConfig, type GuiConfig } from '@/providers/guiConfig'
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
import ProjectView from '@/views/ProjectView.vue'
Expand All @@ -9,14 +10,16 @@ const props = defineProps<{
metadata: object
}>()
const classSet = provideAppClassSet()
provideGuiConfig(toRef(props, 'config'))
// Initialize suggestion db immediately, so it will be ready when user needs it.
onMounted(() => useSuggestionDbStore())
</script>

<template>
<ProjectView class="App flex" />
<ProjectView class="App flex" :class="[...classSet.keys()]" />
</template>

<style scoped>
Expand Down
5 changes: 5 additions & 0 deletions app/gui2/src/assets/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 1 addition & 6 deletions app/gui2/src/components/GraphEditor/GraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,7 @@ function portGroupStyle(port: PortData) {
@update:id="emit('update:visualizationId', $event)"
@update:visible="emit('update:visualizationVisible', $event)"
/>
<div
class="node"
@pointerdown.capture="nodeEditHandler"
@keydown="nodeEditHandler"
v-on="dragPointer.events"
>
<div class="node" @keydown="nodeEditHandler">
<SvgIcon class="icon grab-handle" :name="icon"></SvgIcon>
<div ref="contentNode" class="widget-tree">
<NodeWidgetTree :ast="node.rootSpan" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const value = computed({
},
})
</script>

<script lang="ts">
function getRawBoolNode(ast: AstExtended) {
const candidate =
Expand Down Expand Up @@ -50,6 +51,7 @@ export const widgetDefinition = defineWidget(
},
)
</script>

<template>
<CheckboxWidget
v-model="value"
Expand All @@ -58,5 +60,3 @@ export const widgetDefinition = defineWidget(
@beforeinput.stop
/>
</template>

<style scoped></style>
2 changes: 2 additions & 0 deletions app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const value = computed({
},
})
</script>

<script lang="ts">
export const widgetDefinition = defineWidget(
AstExtended.isTree([Tree.Type.UnaryOprApp, Tree.Type.Number]),
Expand All @@ -39,6 +40,7 @@ export const widgetDefinition = defineWidget(
},
)
</script>

<template>
<SliderWidget v-model="value" class="WidgetNumber r-24" :min="-1000" :max="1000" />
</template>
Expand Down
74 changes: 74 additions & 0 deletions app/gui2/src/components/GraphEditor/widgets/WidgetVector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script setup lang="ts">
import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
import ListWidget from '@/components/widgets/ListWidget.vue'
import { Tree, type Token } from '@/generated/ast'
import { injectGraphNavigator } from '@/providers/graphNavigator'
import { Score, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { useGraphStore } from '@/stores/graph'
import { AstExtended } from '@/util/ast'
import { computed } from 'vue'
type Item = AstExtended<Tree.Tree | Token.Token, boolean>
const props = defineProps(widgetProps(widgetDefinition))
const graph = useGraphStore()
const value = computed({
get() {
return props.input.children().filter((child) => child.isTree())
},
set(value) {
const id = props.input.astId
if (!id) return
graph.replaceNodeSubexpression(
id,
undefined!,
`[${value.map((item) => item.repr()).join(', ')}]`,
)
},
})
function encodeAstPayload(ast: Item): string {
return ast.repr()
}
function decodeAstPayload(id: string): Item {
return AstExtended.parse(id)
}
const navigator = injectGraphNavigator(true)
</script>

<script lang="ts">
export const widgetDefinition = defineWidget(AstExtended.isTree([Tree.Type.Array]), {
priority: 1000,
score: () => Score.Perfect,
})
</script>

<template>
<ListWidget
v-model="value"
:default="() => AstExtended.parse('_')"
:getKey="(item: Item) => item.astId"
dragMimeType="application/x-enso-ast-node"
:toPlainText="(item: Item) => item.repr()"
:toDragPayload="encodeAstPayload"
:fromDragPayload="decodeAstPayload"
:toDragPosition="(p) => navigator?.clientToScenePos(p) ?? p"
class="WidgetVector"
contenteditable="false"
>
<template #default="{ item }">
<NodeWidget :input="item" />
</template>
</ListWidget>
</template>

<style scoped>
.drag-preview {
position: fixed;
background-color: var(--node-color-primary);
border-radius: var(--node-border-radius);
}
</style>
31 changes: 29 additions & 2 deletions app/gui2/src/components/GraphMouse.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
<script setup lang="ts">
import { injectGraphNavigator } from '@/providers/graphNavigator'
import { injectGraphSelection } from '@/providers/graphSelection'
import { computed } from 'vue'
import { useEvent } from '@/util/events'
import { computed, ref } from 'vue'
import SelectionBrush from './SelectionBrush.vue'
const navigator = injectGraphNavigator(true)
const nodeSelection = injectGraphSelection(true)
const scaledMousePos = computed(() => navigator?.sceneMousePos?.scale(navigator?.scale ?? 1))
const scaledSelectionAnchor = computed(() => nodeSelection?.anchor?.scale(navigator?.scale ?? 1))
const isNativeDragging = ref(0)
useEvent(
window,
'dragenter',
() => {
isNativeDragging.value += 1
},
{ capture: true },
)
useEvent(
window,
'dragleave',
() => {
isNativeDragging.value -= 1
},
{ capture: true },
)
useEvent(
window,
'drop',
() => {
isNativeDragging.value -= 1
},
{ capture: true },
)
</script>

<template>
<SelectionBrush
v-if="scaledMousePos"
v-if="scaledMousePos && !isNativeDragging"
:position="scaledMousePos"
:anchor="scaledSelectionAnchor"
:style="{ transform: navigator?.prescaledTransform }"
Expand Down
8 changes: 7 additions & 1 deletion app/gui2/src/components/widgets/CheckboxWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ const emit = defineEmits<{ 'update:modelValue': [modelValue: boolean] }>()
<template>
<div
class="Checkbox r-24"
@pointerdown.stop
@pointerdown="
!$event.ctrlKey &&
!$event.shiftKey &&
!$event.altKey &&
!$event.metaKey &&
$event.stopImmediatePropagation()
"
@click="emit('update:modelValue', !props.modelValue)"
>
<div :class="{ hidden: !props.modelValue }"></div>
Expand Down
Loading

0 comments on commit 2707262

Please sign in to comment.