Implement selection shortcut keys

This commit is contained in:
Nettika 2026-01-29 01:29:25 -08:00
parent aa7e588980
commit 3b42ce9f88
No known key found for this signature in database
4 changed files with 116 additions and 5 deletions

View file

@ -90,9 +90,9 @@ A step-by-step checklist for porting MatterControl's design features to a Vue +
- [x] Implement click-to-select single object
- [x] Implement click-on-empty to deselect
- [x] Add `selectedObjects` array to Pinia store
- [ ] Implement Shift+click for multi-select
- [ ] Implement Ctrl+click to toggle selection
- [ ] Add "Select All" (Ctrl+A) shortcut
- [x] Implement Shift+click for multi-select
- [x] Implement Ctrl+click to toggle selection
- [x] Add "Select All" (Ctrl+A) shortcut
### Selection Visualization
- [ ] Add selection outline effect (OutlinePass or custom)

View file

@ -1,17 +1,39 @@
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import AppLayout from './components/AppLayout.vue'
import Viewport from './components/Viewport.vue'
import { useSceneStore, type SceneObject } from './stores/scene'
const sceneStore = useSceneStore()
function handlePick(object: SceneObject | null) {
function handlePick(object: SceneObject | null, event: MouseEvent) {
if (object) {
sceneStore.select(object.id)
if (event.shiftKey) {
sceneStore.addToSelection(object.id)
} else if (event.ctrlKey || event.metaKey) {
sceneStore.toggleSelection(object.id)
} else {
sceneStore.select(object.id)
}
} else {
sceneStore.clearSelection()
}
}
function handleKeyDown(event: KeyboardEvent) {
if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
event.preventDefault()
sceneStore.selectAll()
}
}
onMounted(() => {
window.addEventListener('keydown', handleKeyDown)
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown)
})
</script>
<template>

View file

@ -248,5 +248,68 @@ describe('useSceneStore', () => {
expect(store.selectedObjects).toHaveLength(1)
expect(store.selectedObjects[0]?.name).toBe('A')
})
it('addToSelection() adds to existing selection', () => {
const obj1 = store.addObject(createTestMesh(), 'A')
const obj2 = store.addObject(createTestMesh(), 'B')
store.select(obj1.id)
store.addToSelection(obj2.id)
expect(store.selectedObjects).toHaveLength(2)
})
it('addToSelection() ignores locked objects', () => {
const obj1 = store.addObject(createTestMesh(), 'A')
const obj2 = store.addObject(createTestMesh(), 'B')
store.setObjectLocked(obj2.id, true)
store.select(obj1.id)
store.addToSelection(obj2.id)
expect(store.selectedObjects).toHaveLength(1)
})
it('toggleSelection() adds unselected object', () => {
const obj1 = store.addObject(createTestMesh(), 'A')
const obj2 = store.addObject(createTestMesh(), 'B')
store.select(obj1.id)
store.toggleSelection(obj2.id)
expect(store.selectedObjects).toHaveLength(2)
})
it('toggleSelection() removes selected object', () => {
const obj1 = store.addObject(createTestMesh(), 'A')
const obj2 = store.addObject(createTestMesh(), 'B')
store.select(obj1.id)
store.addToSelection(obj2.id)
store.toggleSelection(obj1.id)
expect(store.selectedObjects).toHaveLength(1)
expect(store.selectedObjects[0]?.id).toBe(obj2.id)
})
it('toggleSelection() ignores locked objects', () => {
const obj = store.addObject(createTestMesh(), 'A')
store.setObjectLocked(obj.id, true)
store.toggleSelection(obj.id)
expect(store.selectedObjects).toHaveLength(0)
})
it('selectAll() selects all unlocked objects', () => {
store.addObject(createTestMesh(), 'A')
store.addObject(createTestMesh(), 'B')
const obj3 = store.addObject(createTestMesh(), 'C')
store.setObjectLocked(obj3.id, true)
store.selectAll()
expect(store.selectedObjects).toHaveLength(2)
})
})
})

View file

@ -115,6 +115,29 @@ export const useSceneStore = defineStore('scene', () => {
selectedIds.value = new Set([id])
}
function addToSelection(id: string): void {
const obj = objects.value.find((o) => o.id === id)
if (!obj || obj.locked) return
selectedIds.value = new Set([...selectedIds.value, id])
}
function toggleSelection(id: string): void {
const obj = objects.value.find((o) => o.id === id)
if (!obj || obj.locked) return
const newSet = new Set(selectedIds.value)
if (newSet.has(id)) {
newSet.delete(id)
} else {
newSet.add(id)
}
selectedIds.value = newSet
}
function selectAll(): void {
const unlocked = objects.value.filter((o) => !o.locked).map((o) => o.id)
selectedIds.value = new Set(unlocked)
}
function clearSelection(): void {
selectedIds.value = new Set()
}
@ -143,6 +166,9 @@ export const useSceneStore = defineStore('scene', () => {
setObjectLocked,
renameObject,
select,
addToSelection,
toggleSelection,
selectAll,
clearSelection,
}
})