dynamic cursor depending on mouse scene state (#1995)

* dynamic cursor depending on mouse scene state

* hover stuff

* bump min length

* clean up

* sketch on face failing randomly

* more time out for extrude snapshots

* Update src/clientSideScene/sceneEntities.ts

Co-authored-by: Frank Noirot <frank@zoo.dev>

* move for profileStart handle, and select when no tool equiped

---------

Co-authored-by: Frank Noirot <frank@zoo.dev>
This commit is contained in:
Kurt Hutten
2024-04-04 11:07:51 +11:00
committed by GitHub
parent 108bb4ee90
commit f03f34d8be
8 changed files with 237 additions and 93 deletions

View File

@ -1533,6 +1533,7 @@ test('Sketch on face', async ({ page, context }) => {
await page.getByRole('button', { name: 'Extrude' }).click() await page.getByRole('button', { name: 'Extrude' }).click()
await expect(page.getByTestId('command-bar')).toBeVisible() await expect(page.getByTestId('command-bar')).toBeVisible()
await page.waitForTimeout(100)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await expect(page.getByText('Confirm Extrude')).toBeVisible() await expect(page.getByText('Confirm Extrude')).toBeVisible()

View File

@ -363,14 +363,14 @@ test('extrude on each default plane should be stable', async ({
// clear code // clear code
await u.removeCurrentCode() await u.removeCurrentCode()
// add makeCode('XZ') // add makeCode('XZ')
await u.openAndClearDebugPanel()
await page.locator('.cm-content').fill(makeCode(plane)) await page.locator('.cm-content').fill(makeCode(plane))
// wait for execution done // wait for execution done
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
await page.getByText('Code').click() await page.getByText('Code').click()
await page.waitForTimeout(80) await page.waitForTimeout(150)
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })

View File

@ -4,10 +4,15 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls' import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useStore } from 'useStore' import { useStore } from 'useStore'
import { DEBUG_SHOW_BOTH_SCENES } from './sceneInfra' import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
import { ReactCameraProperties } from './CameraControls' import { ReactCameraProperties } from './CameraControls'
import { throttle } from 'lib/utils' import { throttle } from 'lib/utils'
import { sceneInfra } from 'lib/singletons' import { sceneInfra } from 'lib/singletons'
import {
EXTRA_SEGMENT_HANDLE,
PROFILE_START,
getParentGroup,
} from './sceneEntities'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false) const [isCamMoving, setIsCamMoving] = useState(false)
@ -40,7 +45,7 @@ export const ClientSideScene = ({
>['settings']['context']['modeling']['mouseControls']['current'] >['settings']['context']['modeling']['mouseControls']['current']
}) => { }) => {
const canvasRef = useRef<HTMLDivElement>(null) const canvasRef = useRef<HTMLDivElement>(null)
const { state, send } = useModelingContext() const { state, send, context } = useModelingContext()
const { hideClient, hideServer } = useShouldHideScene() const { hideClient, hideServer } = useShouldHideScene()
const { setHighlightRange } = useStore((s) => ({ const { setHighlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange, setHighlightRange: s.setHighlightRange,
@ -76,9 +81,33 @@ export const ClientSideScene = ({
} }
}, []) }, [])
let cursor = 'default'
if (state.matches('Sketch')) {
if (
context.mouseState.type === 'isHovering' &&
getParentGroup(context.mouseState.on, [
ARROWHEAD,
EXTRA_SEGMENT_HANDLE,
PROFILE_START,
])
) {
cursor = 'move'
} else if (context.mouseState.type === 'isDragging') {
cursor = 'grabbing'
} else if (
state.matches('Sketch.Line tool') ||
state.matches('Sketch.Tangential arc to')
) {
cursor = 'crosshair'
} else {
cursor = 'default'
}
}
return ( return (
<div <div
ref={canvasRef} ref={canvasRef}
style={{ cursor: cursor }}
className={`absolute inset-0 h-full w-full transition-all duration-300 ${ className={`absolute inset-0 h-full w-full transition-all duration-300 ${
hideClient ? 'opacity-0' : 'opacity-100' hideClient ? 'opacity-0' : 'opacity-100'
} ${hideServer ? 'bg-black' : ''} ${ } ${hideServer ? 'bg-black' : ''} ${

View File

@ -98,8 +98,9 @@ export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
'tangential-arc-to-segment-body-dashed' 'tangential-arc-to-segment-body-dashed'
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment' export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body' export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const MIN_SEGMENT_LENGTH = 60 // in pixels
export const SEGMENT_WIDTH_PX = 1.6 export const SEGMENT_WIDTH_PX = 1.6
export const HIDE_SEGMENT_LENGTH = 75 // in pixels
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
// This singleton Class is responsible for all of the things the user sees and interacts with. // This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements. // That mostly mean sketch elements.
@ -563,7 +564,7 @@ export class SceneEntities {
}, },
}) })
}, },
...mouseEnterLeaveCallbacks(), ...this.mouseEnterLeaveCallbacks(),
}) })
} }
setupSketchIdleCallbacks = ({ setupSketchIdleCallbacks = ({
@ -685,7 +686,7 @@ export class SceneEntities {
if (!event) return if (!event) return
sceneInfra.modelingSend(event) sceneInfra.modelingSend(event)
}, },
...mouseEnterLeaveCallbacks(), ...this.mouseEnterLeaveCallbacks(),
}) })
} }
prepareTruncatedMemoryAndAst = ( prepareTruncatedMemoryAndAst = (
@ -882,7 +883,16 @@ export class SceneEntities {
}) })
const pxLength = arcInfo.arcLength / scale const pxLength = arcInfo.arcLength / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [TANGENTIAL_ARC_TO_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) { if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0) arrowGroup.position.set(to[0], to[1], 0)
@ -894,7 +904,7 @@ export class SceneEntities {
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
) )
arrowGroup.scale.set(scale, scale, scale) arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = !shouldHide arrowGroup.visible = isHandlesVisible
} }
if (extraSegmentGroup) { if (extraSegmentGroup) {
@ -913,7 +923,7 @@ export class SceneEntities {
0 0
) )
extraSegmentGroup.scale.set(scale, scale, scale) extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = !shouldHide extraSegmentGroup.visible = isHandlesVisible
} }
const tangentialArcToSegmentBody = group.children.find( const tangentialArcToSegmentBody = group.children.find(
@ -970,7 +980,16 @@ export class SceneEntities {
) )
const pxLength = length / scale const pxLength = length / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [STRAIGHT_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) { if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0) arrowGroup.position.set(to[0], to[1], 0)
@ -983,7 +1002,7 @@ export class SceneEntities {
.normalize() .normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir) arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.scale.set(scale, scale, scale) arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = !shouldHide arrowGroup.visible = isHandlesVisible
} }
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE) const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
@ -997,7 +1016,7 @@ export class SceneEntities {
0 0
) )
extraSegmentGroup.scale.set(scale, scale, scale) extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = !shouldHide extraSegmentGroup.visible = isHandlesVisible
} }
const straightSegmentBody = group.children.find( const straightSegmentBody = group.children.find(
@ -1175,6 +1194,119 @@ export class SceneEntities {
}, },
}) })
} }
mouseEnterLeaveCallbacks() {
return {
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5)
}
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
const node = getNodeFromPath<CallExpression>(
updatedAst,
parent.userData.pathToNode,
'CallExpression'
).node
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
extraSegmentGroup.traverse((child) => {
if (child instanceof Points || child instanceof Mesh) {
child.material.opacity = dragSelected ? 0 : 1
}
})
}
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier
if (parent.name === STRAIGHT_SEGMENT) {
this.updateStraightSegment({
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
}
return
}
sceneInfra.highlightCallback([0, 0])
},
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (parent) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier
if (parent.name === STRAIGHT_SEGMENT) {
this.updateStraightSegment({
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
}
}
const isSelected = parent?.userData?.isSelected
colorSegment(
selected,
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
)
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
extraSegmentGroup.traverse((child) => {
if (child instanceof Points || child instanceof Mesh) {
child.material.opacity = 0
}
})
}
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
}
},
}
}
} }
export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ' export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ'
@ -1417,69 +1549,3 @@ function massageFormats(a: any): Vector3 {
? new Vector3(a[0], a[1], a[2]) ? new Vector3(a[0], a[1], a[2])
: new Vector3(a.x, a.y, a.z) : new Vector3(a.x, a.y, a.z)
} }
function mouseEnterLeaveCallbacks() {
return {
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5)
}
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
const node = getNodeFromPath<CallExpression>(
updatedAst,
parent.userData.pathToNode,
'CallExpression'
).node
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
extraSegmentGroup.traverse((child) => {
if (child instanceof Points || child instanceof Mesh) {
child.material.opacity = dragSelected ? 0 : 1
}
})
}
return
}
sceneInfra.highlightCallback([0, 0])
},
onMouseLeave: ({ selected }: OnMouseEnterLeaveArgs) => {
sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
const isSelected = parent?.userData?.isSelected
colorSegment(
selected,
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
)
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
extraSegmentGroup.traverse((child) => {
if (child instanceof Points || child instanceof Mesh) {
child.material.opacity = 0
}
})
}
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
}
},
}
}

View File

@ -30,6 +30,7 @@ import { type BaseUnit } from 'lib/settings/settingsTypes'
import { CameraControls } from './CameraControls' import { CameraControls } from './CameraControls'
import { EngineCommandManager } from 'lang/std/engineConnection' import { EngineCommandManager } from 'lang/std/engineConnection'
import { settings } from 'lib/settings/initialSettings' import { settings } from 'lib/settings/initialSettings'
import { MouseState } from 'machines/modelingMachine'
type SendType = ReturnType<typeof useModelingContext>['send'] type SendType = ReturnType<typeof useModelingContext>['send']
@ -102,6 +103,7 @@ export class SceneInfra {
_baseUnit: BaseUnit = 'mm' _baseUnit: BaseUnit = 'mm'
_baseUnitMultiplier = 1 _baseUnitMultiplier = 1
extraSegmentTexture: Texture extraSegmentTexture: Texture
lastMouseState: MouseState = { type: 'idle' }
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
onDragEndCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragEndCallback: (arg: OnDragCallbackArgs) => void = () => {}
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
@ -338,8 +340,6 @@ export class SceneInfra {
planeIntersectPoint.twoD && planeIntersectPoint.twoD &&
planeIntersectPoint.threeD planeIntersectPoint.threeD
) { ) {
// // console.log('onDrag', this.selected)
this.onDragCallback({ this.onDragCallback({
mouseEvent, mouseEvent,
intersectionPoint: { intersectionPoint: {
@ -349,6 +349,10 @@ export class SceneInfra {
intersects, intersects,
selected: this.selected.object, selected: this.selected.object,
}) })
this.updateMouseState({
type: 'isDragging',
on: this.selected.object,
})
} }
} else if ( } else if (
planeIntersectPoint && planeIntersectPoint &&
@ -368,27 +372,34 @@ export class SceneInfra {
if (intersects[0]) { if (intersects[0]) {
const firstIntersectObject = intersects[0].object const firstIntersectObject = intersects[0].object
if (this.hoveredObject !== firstIntersectObject) { if (this.hoveredObject !== firstIntersectObject) {
if (this.hoveredObject) { const hoveredObj = this.hoveredObject
this.onMouseLeave({ this.hoveredObject = null
selected: this.hoveredObject, this.onMouseLeave({
mouseEvent: mouseEvent, selected: hoveredObj,
}) mouseEvent: mouseEvent,
} })
this.hoveredObject = firstIntersectObject this.hoveredObject = firstIntersectObject
this.onMouseEnter({ this.onMouseEnter({
selected: this.hoveredObject, selected: this.hoveredObject,
dragSelected: this.selected?.object, dragSelected: this.selected?.object,
mouseEvent: mouseEvent, mouseEvent: mouseEvent,
}) })
if (!this.selected)
this.updateMouseState({
type: 'isHovering',
on: this.hoveredObject,
})
} }
} else { } else {
if (this.hoveredObject) { if (this.hoveredObject) {
const hoveredObj = this.hoveredObject
this.hoveredObject = null
this.onMouseLeave({ this.onMouseLeave({
selected: this.hoveredObject, selected: hoveredObj,
dragSelected: this.selected?.object, dragSelected: this.selected?.object,
mouseEvent: mouseEvent, mouseEvent: mouseEvent,
}) })
this.hoveredObject = null if (!this.selected) this.updateMouseState({ type: 'idle' })
} }
} }
} }
@ -445,6 +456,11 @@ export class SceneInfra {
(a, b) => a.distance - b.distance (a, b) => a.distance - b.distance
) )
} }
updateMouseState(mouseState: MouseState) {
if (this.lastMouseState.type === mouseState.type) return
this.lastMouseState = mouseState
this.modelingSend({ type: 'Set mouse state', data: mouseState })
}
onMouseDown = (event: MouseEvent) => { onMouseDown = (event: MouseEvent) => {
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1 this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1
@ -484,6 +500,16 @@ export class SceneInfra {
mouseEvent, mouseEvent,
selected: this.selected as any, selected: this.selected as any,
}) })
if (intersects.length) {
this.updateMouseState({
type: 'isHovering',
on: intersects[0].object,
})
} else {
this.updateMouseState({
type: 'idle',
})
}
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) { } else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
// fire onClick event as there was no drags // fire onClick event as there was no drags
this.onClickCallback({ this.onClickCallback({

View File

@ -25,7 +25,7 @@ import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import { import {
EXTRA_SEGMENT_HANDLE, EXTRA_SEGMENT_HANDLE,
EXTRA_SEGMENT_OFFSET_PX, EXTRA_SEGMENT_OFFSET_PX,
MIN_SEGMENT_LENGTH, HIDE_SEGMENT_LENGTH,
PROFILE_START, PROFILE_START,
SEGMENT_WIDTH_PX, SEGMENT_WIDTH_PX,
STRAIGHT_SEGMENT, STRAIGHT_SEGMENT,
@ -141,7 +141,7 @@ export function straightSegment({
.normalize() .normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir) arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
const pxLength = length / scale const pxLength = length / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
arrowGroup.visible = !shouldHide arrowGroup.visible = !shouldHide
group.add(mesh) group.add(mesh)
@ -282,7 +282,7 @@ export function tangentialArcToSegment({
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
) )
const pxLength = arcLength / scale const pxLength = arcLength / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
arrowGroup.visible = !shouldHide arrowGroup.visible = !shouldHide
const extraSegmentGroup = createExtraSegmentHandle(scale, texture) const extraSegmentGroup = createExtraSegmentHandle(scale, texture)

View File

@ -112,6 +112,9 @@ export const ModelingMachineProvider = ({
kclManager.executeAst() kclManager.executeAst()
} }
}, },
'Set mouse state': assign({
mouseState: (_, event) => event.data,
}),
'Set selection': assign(({ selectionRanges }, event) => { 'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data const setSelections = event.data

View File

@ -64,6 +64,19 @@ export type SetSelections =
selection: Selections selection: Selections
} }
export type MouseState =
| {
type: 'idle'
}
| {
type: 'isHovering'
on: any
}
| {
type: 'isDragging'
on: any
}
export interface SketchDetails { export interface SketchDetails {
sketchPathToNode: PathToNode sketchPathToNode: PathToNode
zAxis: [number, number, number] zAxis: [number, number, number]
@ -128,12 +141,13 @@ export type ModelingMachineEvent =
type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch' type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch'
data: SketchDetails data: SketchDetails
} }
| { type: 'Set mouse state'; data: MouseState }
export type MoveDesc = { line: number; snippet: string } export type MoveDesc = { line: number; snippet: string }
export const modelingMachine = createMachine( export const modelingMachine = createMachine(
{ {
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANgCcARgB0NcQHYAHKIBMw8QGY1ogKxKANCACeiWUvmT5SraNE15a4QBZxW2QF9X+tBhz4CAZTB2AAJYLDByTm5aBiQQFjZInliBBEFtWUlxGlEnWWVZNQc1fSMEDSl1NXlVewcirWE1d090LDwoSWwITDACAFFcdjAAJxCAa0DyAAto3niOLiTQFMEVDPEVUUd5DfFxYVkHEsQlHMkioqUnNWzq0WaQLzb8Tu7egaHR2An2aapxGLMVgLbi8FZXGjnfYyLS2G7yGiyY4IBw0JSSfLCLQbYQ7Kr7B5PHwdLo9fp8djDACuGFmsXmiTBQi0LkkpxoDW0Di0O2Re1hbOqim0NAcOSchNaxNeZL6fBYw3YdKBCUWTNSpjMpghSiU4jF3PkyNksikBy0hSxCKUhy0ku87Ukfh+02IZEomGdkxm9DmwMZyUQe1EZgcKgOGxoaj1uL5ShkkgtciFzgOOXtzw6Xt+UydLqmAEk3gRkCQJiEwFAALZgQZBABuI045BImGVcX9asDCHUUcTaMRthouNhWj5RTUklNNxkouEwlF9w8jyljuz0zz3qLZNL5dC1drwSmqGG2AAXtx2K32wyu8sg6ZhBibRYLGoWTYjYYg+UMTyDqadTyLIBwZtK665hB269EQ3CwJSJB4EEx6nhegytkEEDYPB7pgDenagt2OJPvYzjRukhxXMiVz7OcuIOLIVgmKIhQOGBa75puObQcQcEIUhjaKtgLaYJh2FXhQeG+vSBFLPwD47GyJiIgxjQ2Ao1GnJOLGmgupxZMB7EvBBXHTDxsG4PBwyIbgQQAIIAEJ+EEAAa+GqoR949qcGSMQcVQck4ziiNRoiMZI9hhSyFphdoRlZpxUHFhZVk2fZTlBAAmu5IJySkGxhYmNTyA0VQMSGyJqFkoiZFUOimrqDRaGxy5Ehx3qmYWyV8dZSFkFAPQ5QGXnqCyU5RhycizmiwjUfVkgRiy0ayMOerxZ1nXmT1aU9Pg7A+oCHYeXlP5igt8gOHYjXYlUs3fggKiaJkb4sYaVrrSZSVkil-G2UwIz-bgWHkFSmAkKMWE4ZJQ13vJ3mouY4gle+OgOPRej3aY761aYBRKFUmwfYl+ZbZZv3ISe56Xq2mAGEErbYFAuAw55cMbOFcgmrYaMWKcIX3WRT4rfIb40WV8hEx1X0wdtAlNsJNN0wzTMsyd3kSFOOhZI9yhI0iAuOE+NBoiaThbAiIuSzmm3dWTvW2bAuAkEwQTsKgrmq+q6iWImdhRnqs7CCo1GrQtt1hsGYgtS0DrGcTW626lSGO87rvu9l0kqrlXvRloEXaM1thWPk+ulDGGRbFN+pVaYLhWxu0u8XbaVgAAjlSGG7VA+2e0RNx53YqJoq+iImHyjGQhdy0sitmgcvXkEk4n5NMODNPUJnR3Z33T2FEjzW6TRfLpOcaPqBP3JNK1q5x1LS-fbLtnDGAVaoI2QTkI-7CwL3I0qJCLF9h5FRGIHY-NSj7ykGGEM2gchiFHgvG2ZIABKYBBBgD4OEKkQxf5s3sHnbkOh3wHBKnkO6pQQwZFnKab2-klCIMbn0du2AXYABk8BgDTqgNsm9bys3yp+cwOhgI3BAlVOQyISp52UGiTm9ETDCAYffd4zCXYSRgIMbAGFwbkDTrglIeoETjUugfBEWJwFBg2OiJGzgdg+RUBLa+scEodTsgAd0QkeSmaEryiUhhJSgQQ8AADNUAEAgNwMAnRcD1lQBMSQMB2CCBQlTdCmBBAhNQPok4VUhZYkaI+UwMhqJ1EhCoFwIZcSiMcTHTMG13GeIpqhamfjxK4SCbgUJBARjDBPJIJgYN2ChOGFWBJgRkneJaRkzpWTeGyXVPjOQ5wNCNDqLiPYGgSlonOBaFw-4rSWEQQ0jgDZ5YiTElDQJmTwmROibE+JiTBCCWbK2aZoTskPWjA4HZs9Cg2BkI0aiNxvmal1CBNG1h7BHI8Sc55CtWmXM4dcnpfSBkkCGSeUZjy4UiTebMw6fC1b4x9hsMUvNKmOE0kHRMCgXB5FkeIaFjTHLORch0rpETcBRLwPcqJjySAACNYCCD4Hij5xKaq2CsRacFUYjiYxDOiJamhxBwKAjUlczj6kwuCCy1y7KwkouGP0wZwysXjMFcK0VmTxXkU1lVYc041CAVCvYKch9qj7xWnaJxdSTLHN1RlTKBqblcruXEvlFqhWCAMGKuZx0FnRjMAcMU9KtYVXuvCCuwZ-7m0Yj62p4FOIBvSs5YNyLhi9ONWijFIyxlJMtTGuNBL5ndglRFHmC4qgSCxJVJG3zLqPRNFiVVhQmUnP6j0ENnLuUxIjfWwQk60E2vjdvLy7ag5Rh5ATU25CTh0o7YUEC85zZLkLe1a2Jal0hqNSa9FZqF1LubX6BNbbfxog0NiK4zqyJzWdYmcp3JwWFA1W1W+l6dVBC7vtadtyeXzsedBqYz6ZKvvXctTW+w5A+RmsUTG3rzggVOLjZVBbNV+uLZBpDN7K2otNZihdSGUNZ2GnDYloZVU8gKgVPdD15yTkRM1RwMCmLjuCP9YYgNgag3BhcgJSKZmhtnbyhdEmpPCRk8MQQ-jcLMa3qxgxNo842kYgxW0sJeOmG5FOC6dQ9SVAYmJoIanazSbBhDNpkkaNVrvbW81SSXNAw0+57TnnKB6cJQsozSlTOmxZMOaieRfK2Yjg5twvqi0dXYVyrhmB+iqNdv1Q8WjRI6L0augzJwFBPgYjXVE7NVXyooYVC6BwCn4wjmRsDLjrbZc4W7bhkgCy4A4AQcVVwarOFNrsYWNpjQJhHRIQhWGqiIL67lobI32BjYBC+tdbGrPmF1FGbQIHlC8byDVYCI6zOIl1GtjhG2ABy7sAAKqA8DfwIHZCAEAQhXkVM5j7gxxUATZFcEWlhYQixNMiXEV36omCsOU9MGWL0bnWwNzAkgXtBHe592AbpJI8JbWhtmnqIrYgXFrECFgmvGD1BibI+xIfzguul894GNzDdGx87WZgERijWEUzZAtLCThFsOGEhwi6KLR1z3MPPtv-FJ-t-Kw4QVZHZqZiwY4BY10yCGaM+xURVS6zfHrG4AAqRXNHaOGLot2+WqQsKCJj1A3DQcNE1sbbIiJWKFGRFsdYJV7N6jWRYRBVIgbuxwoqCCAB5XASnw3xLsn4K3gho8RMEHH9gifmYVdhvlDQZh6oqv1PUWwyIeQANUJ+WrdhnULyCLgd2wSSCUH8GECImEwAd9BuJsGXKPmrD2M9HIzrDjVEONoOHKhEYdeddYcFZ7yPSjINgKs6L2hp2c8P3oM7U9RM39voYgg3aCA75QD5LIKgIgUKKY2x3S6ICDmYGuGhh5hUuqBi3khT8d98A98MFxJd9vhvQU94N4lADz9L8ICcwPkg484ZobQQxnAigx5M0kYFp-JFBcRFAGJHFlw28MB4BYhus9tKtUgChJVkxztKhtAMYIERZzBZ8NBTRB4EF5cSQ3gqDi8hBtgioksyoeQGJJFFILQpUkx8YZA19utOp+D+FEAQxJ5FxhcERRcy4QIIpZ8kYDgjMZUlEE4eglC1Z9RnAFogE0RUQZdA8xd9RExmp4xVAGJUY-8tV-VIMUkfEMIdMvNMkzCvZJcpw9IZA7APwqgSkNhNYLAQIkYbh8gnMcU-CwsFNQkgiiJCDCNlB7BzE9g0ZNIVpllTRORNCkcnM9U2VAjUM1cgwbACEP13Chc6hQpsRzAxB4idBKEwpKig0DVMiRobBvl2csQhcVp8Y9dSh4Q85GhkZxFykownNr0aiWMBCex1IMQhd5wtZ3w8My5ui2R7A7A9hw9uQnNqNVj9N1jVVtkTAXxCC9QbBmCTgsQMh4wxRGhR0TMPCKNXFINAs3NZN-CrkZlBjycxBMgpszcfjX8HprQcZB4T1VACgHscssdwSDF+R85-5qgYwe04dtl3wbQGIgIqpUdOdLdcx3dBsldMSTh9hJxNBcSg59gCT7oGoaUWJ4xnBHwLo0T+sPdsc28RlWx6SHpYQaoyEWRjsqpuR6cEATApAbAe1Wd5EBTns3tgdv5xSrgsQFoP0Gp-4LsLAFochbBgIZAIU5dKSNo6TajqCshTQMRDYGIsM9QpiHwEwkxqsdgGhgFEEbd8Bit7dHdUBdSmJpAJASpzS8SqIBY7tzoTdUQp4x0eCNps9Y8Ad898wk9xSq4hEGJbBGJqgGgFSGgnxgIxRfcVoFwOd190cphW929O8wBxTVgEYrpzRDhhZuRkR5T+wrBjYnS8hUZ1pYDd83Z98yA2yHSbjGgMQQJhj0hIxeMg4jYqhrtmo-ZDJ0yJzgCpzQD4JwD8x8zCo-I3jZEihVUXj4TIR5xbBniUz393B3AgA */ /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANgCcARgB0NcQHYAHKIBMw8QGY1ogKxKANCACeiWUvmT5SraNE15a4QBZxW2QF9X+tBhz4CAZTB2AAJYLDByTm5aBiQQFjZInliBBEFtWUlxGlEnWWVZNQc1fSMEDSl1NXlVewcirWE1d090LDxCAODYdgAnMBIAWyDyAFce2FQe6N54ji4k0BSaEsQaZpAvNvxJbAhMMAIAUVx2MB6QgGtA8gALadjZxN4UwRUM8RVRR3kP8XFhWQOFYIJQ5SRFIpKJxqbLVUTrTY+KA7PYHY6nc6wK7sW5UcQxZisObcZ5CKE0cH-GRaWww+Q0WTAhw0JSSfLCLQfYQ-Kr-BGtJEo-ZHPi9EYYe6EhLzUmpLQuSSgmgNbQOLQ-YF-GmK6qKbQ0Bw5Jz87ztIVovgsHrsSVxIlPZJk+RmUzkpRKcSGtXyYGyWRSAFaQoc+lKQFaE1bZF+bG3YhkSiYGPXO70Gb2mWOhB-DTg5TCDm-bnqTXiGySTkOeQKDSKaoOSOC5M4m6SZu3ACSqIIyBIVxCYCgAzAJyCADczpxyCRMLbHpnFogPlUK9yaI4TEHnJqHO7JGJhNlLGIJOJ5I2ze3W1eu8Le-3QkOR8EbpNsAAvbjsGdzjMkrMfB6bKiFU7rOMy8pAoYS5qEoFI5HI3IFj8djCBe2xXm2sY3LeBxENw3Q9CQeBBK+PQfl+M5BBA2DdAmYC-tK-6LtmcFSLusg6NysjCHBjLQSCEHgpyDSAl8ULqOh0bYVhKa4cQBG9MRuDjpO2DTpg1G0d+FAMWmDx-gs-BLh6wiSLW666koagmMUAnuhYbJwZYZ6epoDYeBsAqXjJN7dvhuCEcpQQAIIAEJ+EEAAajHEkZKTLqyFhhmoZabvSUGlKCjjmTo8gOLI66pTI56eYiPkprJLbyQFQUkeFkUAJqxQ6LGJfuKV5PKYh1HZpQwqIZhFmejjqrZUlVbck04f5ilESRZBQPsLULsZrHljxNhqqoOQAsCLlmRyDiOAVIHKKVLSmhhvnYTVc3Bfs+DsKmBJ2kx8UmflkicbYKhyPYNCwftvFDUd+WDWWHITZhfnCrVSkkUwZxI7gNGjJgJDnDRdG6StzFrX8nHmPlOhGqlVb8VlFgOJkzhfAVqguGhZXeddlWw3h90kWRFEnDOmAGEEM7YFAuB4x9rFZIqRSFdZ1Q9ZlS46Fo+4SBIDPHRIF1eVd0ns7ds2BQjKkTta6n84Lwui+LsqAWZKHZByTN2HoAn-IUbLyOqHImDIzhuCzuvTdNd1G-NKmwLgJBMEE7CoNFNsASoFKODYydljo-r7cqAYegUtQwjI0M3XJht1RHUcx3HQTNfpUpxbbyfgozhR+nYyiu1lzhmUaeSgloar5MX+ul3DXMqWAACOIxUY9UDPYnbXkuY1jWACx0D2emoFKI5gqPYhUgWIJjDy2Idl8bQRMJj-PUHXb0N0ngLggy+XVK3ypaJqu3gqC7ppzWbkp8pocwUmHYKfQBioAnMMce7BYCLwJhnYCPF86WDqI4TU+UKROFBNWAqLJPQeUulGYOoCABKYBBBgD4OEEYpxEEJULhWD0-8rCFSKKIYEogibWCqCyL2NBAbVmAdeA2wpDjT2wDHAAMngMAsdUCoFnPfec+MEr5TUGyM88trAFR4sCfKNMbAaCPBIP0ORRHnwkVIqui1nzYCopjcgijGGIA9M4b6hU34nkSqWP4mRHAAldMqQEAcSFNhkiFAA7sRF8b5Px800tjHSlAgh4AAGaoAIBAbgYAdi4DHKgK4kgYDsEEDzRJ35MCCEyagNxIIXLmFSrxAEPwVBf3sruXeUJnRpyKl7KxMS4mkQSZRZJ2l6LpNwFkggZweiTEkEwDG7Ask9AGKUwIFSxlJNqTM+pqjDKyndDoPM4k7DVBcAY+ymh2L2ALAUOsihiE61IZhYZHBVJmw0lpHGaS6k5LyQUopJSymCFNlOGceyskNJObvHQMIwzMjfuufaoItFhnUGWOQgMhHMwiRVM+HzggQvNhMv5CiAXzMWcskgqzJgbLBaSjS0KDmvTURLd0h5Mg2B4bxOQHwfT2SaQ0f4PV0H-G1uVNmRLYmfIatFaZszcm4HyXgEF+SwUkAAEawEEHwVlsKVA009CYMsoIqwAkpu4nhUggzfGDDw+U4TXmRMqsS0KEVFVUp6AsnoSyVlrMZVsnVeqDV1KNTlH4Eg8WpTsC4faygVacQKmxJw1ZARDLlcEBVjUlXZJVWqwpxTNUht1YIAwhrDnvWOfvb6zIeGgkKgWKEwIYQ8RXjSRCbFqZZpGbm-NczfU0sDQyzZ5TQ0Vqreyo5WYuU01wV7IoDJeLaDbf9ZuLg-7OhpAaPtnzFr7EHYW4FJbx2CEPVQiN1bH4sXnfud+CKZDWS4fZBoZlYIcmLMoNe+7giXsHdS-1tL6XrPPZe6d6Ya1zpUGZWwIE1T-Dzl8faXEKyOxsqYHi8o-1BDns9Y9QL1VnrBfhm4kGDLQbvSoJKiE-gegHjYYQwMwSDyqJw50EFcNkcA8O4Do6wOkZHPPcj16Z1UbWlyrRVZnU6GyNcrKHJWTKi9g0TkSoXnSr1rKkZSMegozRiMDGWNJm6UI6q09oKtl6YM+pIzmNBApPohR+urVJMegpIUNQm8CyaA4ftOwNNUFBnsPcoMUrWbaamh6mzI5DPGd+akyl+yh1+oDXSoN57Yuozs8ZxzpnKAuYfm5lIYFPP1CcL5nhBp9oKDMsFjQjQCzhasXI1VijlFHFsbHexJxHGaWca4m9JX3FmorLcuQIE-h2GtQgQa9s8ED0BMEiwrX5EdcwJIDsuAOAEFhWeGm7obLefUJBWbYgU7ebxQyQ0cEIyBzeTJNrCi47KK2zt9ge38RQdve5ssxN6QaDVBoBNAkFBmC9l8dcFqBEuq08HZ7G3JAADl44AAVUB4HgQQEKEAIAhG-NaK+mOTiwtUKyJU1YgkgR0MCNpzTvZODpi2tb7XXubdR0EDHWPYDxl0io8Tv2Ep5DMrkKwEh7Cmt9FLRQXLAaGmsJ6Kx23dsNK+Fo6sR51zHUNICTUUIkpNZAvJzQKhlcfa+4Lkbc3rCKh4kWAopgciahhBUH40J3a8SUFYgAKr1zgTieguLjl1kY0igiI-Z7CqwUgfiHhZIrmwQrSjaDMpKxFBQ-RiSsSMVG8c6LWivAAeVwICizxGSkhT8D7wQufcmCAL+wYvYthurSYSuTOE3eo0mT4gdU8Edq2AKlUDQcPIvTSCLgeOGSSCUH8GECI1EwAz6M8EWlqqGmvACWeHINlATVDEp00orTzDLhltYHihoJpkGwAMOl7RFFXwxqqsvRaNWSBv3f04gg46CBn5QBpeUN3V+A0IRd0BkOnUwcyZcRFRQffCLIOT-e-fAR-GhbSB-LEFMV-SzfJJA7-X-TAlsBpXiFWFkXiPIH4AeTDddMwB5KoL4PpAqUqTyKfDAeAWILTH7a3QQHeaQOQWXGoTQWnN2TkcwfvHFKEKwGQTTcfXYfYLgtvIQb4VcPIR5b0AqQxH4cbWwZwVKayGQeEB7N1FsBQ9RRACQFWTXZQbXVOPXG5XebzekSbdUduTiKxDmUwiWU7FOCwA8WCb2XvViQGcbEwPlbxONXDSpcZRLKZOpTw22SgveHaFwAeFTPqdxZkKQQEdQK1IqX9IwwlaLbNL5SFclJLfNeIgCCweFNUfQq5FwV9LKSwXeXcFkGTQGAESxAomVIo-tL1KKCoyjIXJcbBZuQLRwQGegxNZkMQ0wIMZUONAEXDAdOIoY63M8TQbRWoTjAeZ0NtG7DqJDY6bzdIb3boqLVsD1ADVY1zRQ7MTRPgriA0TQH4OwrKTkF0MsDuE6TiM4glHoy44onjG44rO4jYjIcHBQUwQ8I6WbY1NPZ0ZUAqf4dUAqXDbLeLTGGIszEEjlBIg0TIV49BY6FyRWEEPICoOmP6H9A3VnF7JRTASou9ZUHBb2FkVI7uYEGPcyRwxoGQGyOkpHFXdgJkyTWEYSaodkzIhoX0GQVcCSf4e3TicQQU9nSQKfdZGcUU0rOoGmaodBcnKkHhaXCkWXZOQoVeJXc4hHdbNUznbnE4dg24swwSIRfcbkewSsWo46X0f0fcP0M8FkKEP0Roc3DgbUpcIRDIXFRwLIEXPIMkv4csSsasOweg+sX3f3frIWIPRRCMkEIMVkf2L2DQMMWwPaN2dcAMDcbzQ8S-FU60zCOvfPQnJvbCEvfMz3JyBkMggMviLk+UMQvICws6dQBAx7FMSfafWfMAfM14GYl2QMMJIfI-RANUGmGkKQmQBkLqKEa-HbL-B-audfWctYsExoNkTaA0dID4VFASE-YRKnXYukMfRAg85AqAVAvgdAlAwg24Tsnhb6d9FwQhIoc1ALFOQ8RQNo8s0wdwdwIAA */
id: 'Modeling', id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0, tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
@ -157,6 +171,7 @@ export const modelingMachine = createMachine(
sketchPlaneId: '' as string, sketchPlaneId: '' as string,
sketchEnginePathId: '' as string, sketchEnginePathId: '' as string,
moveDescs: [] as MoveDesc[], moveDescs: [] as MoveDesc[],
mouseState: { type: 'idle' } as MouseState,
}, },
schema: { schema: {
@ -535,6 +550,10 @@ export const modelingMachine = createMachine(
internal: true, internal: true,
actions: 'Set selection', actions: 'Set selection',
}, },
'Set mouse state': {
internal: true,
actions: 'Set mouse state',
},
}, },
}, },
{ {