Compare commits
5 Commits
nested_dir
...
jtran/appl
Author | SHA1 | Date | |
---|---|---|---|
01cbd9533b | |||
a8d12a35cd | |||
24c2fe996f | |||
4da6298e2a | |||
7021be8360 |
1
interface.d.ts
vendored
1
interface.d.ts
vendored
@ -69,6 +69,7 @@ export interface IElectronAPI {
|
|||||||
kittycad: (access: string, args: any) => any
|
kittycad: (access: string, args: any) => any
|
||||||
listMachines: () => Promise<MachinesListing>
|
listMachines: () => Promise<MachinesListing>
|
||||||
getMachineApiIp: () => Promise<string | null>
|
getMachineApiIp: () => Promise<string | null>
|
||||||
|
readNaturalScrollDirection: () => Promise<boolean>
|
||||||
onUpdateDownloaded: (
|
onUpdateDownloaded: (
|
||||||
callback: (value: string) => void
|
callback: (value: string) => void
|
||||||
) => Electron.IpcRenderer
|
) => Electron.IpcRenderer
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
UnreliableSubscription,
|
UnreliableSubscription,
|
||||||
} from 'lang/std/engineConnection'
|
} from 'lang/std/engineConnection'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import { toSync, uuidv4 } from 'lib/utils'
|
import { cachedNaturalScrollDirection, toSync, uuidv4 } from 'lib/utils'
|
||||||
import { deg2Rad } from 'lib/utils2d'
|
import { deg2Rad } from 'lib/utils2d'
|
||||||
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
||||||
import * as TWEEN from '@tweenjs/tween.js'
|
import * as TWEEN from '@tweenjs/tween.js'
|
||||||
@ -78,8 +78,13 @@ export class CameraControls {
|
|||||||
enablePan = true
|
enablePan = true
|
||||||
enableZoom = true
|
enableZoom = true
|
||||||
zoomDataFromLastFrame?: number = undefined
|
zoomDataFromLastFrame?: number = undefined
|
||||||
// holds coordinates, and interaction
|
// Holds event type, coordinates (for wheel, it's delta), and interaction
|
||||||
moveDataFromLastFrame?: [number, number, string] = undefined
|
moveDataFromLastFrame?: [
|
||||||
|
'pointer' | 'wheel',
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
interactionType
|
||||||
|
] = undefined
|
||||||
lastPerspectiveFov: number = 45
|
lastPerspectiveFov: number = 45
|
||||||
pendingZoom: number | null = null
|
pendingZoom: number | null = null
|
||||||
pendingRotation: Vector2 | null = null
|
pendingRotation: Vector2 | null = null
|
||||||
@ -283,19 +288,75 @@ export class CameraControls {
|
|||||||
|
|
||||||
const doMove = () => {
|
const doMove = () => {
|
||||||
if (this.moveDataFromLastFrame !== undefined) {
|
if (this.moveDataFromLastFrame !== undefined) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
const interaction = this.moveDataFromLastFrame[3]
|
||||||
this.engineCommandManager.sendSceneCommand({
|
if (this.moveDataFromLastFrame[0] === 'pointer') {
|
||||||
type: 'modeling_cmd_req',
|
this.engineCommandManager
|
||||||
cmd: {
|
.sendSceneCommand({
|
||||||
type: 'camera_drag_move',
|
type: 'modeling_cmd_req',
|
||||||
interaction: this.moveDataFromLastFrame[2] as any,
|
cmd: {
|
||||||
window: {
|
type: 'camera_drag_move',
|
||||||
x: this.moveDataFromLastFrame[0],
|
interaction,
|
||||||
y: this.moveDataFromLastFrame[1],
|
window: {
|
||||||
},
|
x: this.moveDataFromLastFrame[1],
|
||||||
},
|
y: this.moveDataFromLastFrame[2],
|
||||||
cmd_id: uuidv4(),
|
},
|
||||||
})
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
|
} else if (this.moveDataFromLastFrame[0] === 'wheel') {
|
||||||
|
const deltaX = this.moveDataFromLastFrame[1]
|
||||||
|
const deltaY = this.moveDataFromLastFrame[2]
|
||||||
|
this.isDragging = true
|
||||||
|
this.handleStart()
|
||||||
|
|
||||||
|
this.engineCommandManager
|
||||||
|
.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_batch_req',
|
||||||
|
batch_id: uuidv4(),
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
cmd: {
|
||||||
|
type: 'camera_drag_start',
|
||||||
|
interaction,
|
||||||
|
window: { x: 0, y: 0 },
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: {
|
||||||
|
type: 'camera_drag_move',
|
||||||
|
interaction,
|
||||||
|
window: {
|
||||||
|
x: -deltaX,
|
||||||
|
y: -deltaY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: {
|
||||||
|
type: 'camera_drag_end',
|
||||||
|
interaction,
|
||||||
|
window: {
|
||||||
|
x: -deltaX,
|
||||||
|
y: -deltaY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: false,
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
|
|
||||||
|
this.isDragging = false
|
||||||
|
this.handleEnd()
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`Unknown moveDataFromLastFrame event type: ${this.moveDataFromLastFrame[0]}`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.moveDataFromLastFrame = undefined
|
this.moveDataFromLastFrame = undefined
|
||||||
}
|
}
|
||||||
@ -386,32 +447,16 @@ export class CameraControls {
|
|||||||
if (interaction === 'none') return
|
if (interaction === 'none') return
|
||||||
|
|
||||||
if (this.syncDirection === 'engineToClient') {
|
if (this.syncDirection === 'engineToClient') {
|
||||||
this.moveDataFromLastFrame = [event.clientX, event.clientY, interaction]
|
this.moveDataFromLastFrame = [
|
||||||
|
'pointer',
|
||||||
|
event.clientX,
|
||||||
|
event.clientY,
|
||||||
|
interaction,
|
||||||
|
]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement camera movement logic here based on deltaMove
|
this.moveCamera(interaction, deltaMove)
|
||||||
// For example, for rotating the camera around the target:
|
|
||||||
if (interaction === 'rotate') {
|
|
||||||
this.pendingRotation = this.pendingRotation
|
|
||||||
? this.pendingRotation
|
|
||||||
: new Vector2()
|
|
||||||
this.pendingRotation.x += deltaMove.x
|
|
||||||
this.pendingRotation.y += deltaMove.y
|
|
||||||
} else if (interaction === 'zoom') {
|
|
||||||
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
|
||||||
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
|
||||||
} else if (interaction === 'pan') {
|
|
||||||
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
|
||||||
let distance = this.camera.position.distanceTo(this.target)
|
|
||||||
if (this.camera instanceof OrthographicCamera) {
|
|
||||||
const zoomFudgeFactor = 2280
|
|
||||||
distance = zoomFudgeFactor / (this.camera.zoom * 45)
|
|
||||||
}
|
|
||||||
const panSpeed = (distance / 1000 / 45) * this.perspectiveFovBeforeOrtho
|
|
||||||
this.pendingPan.x += -deltaMove.x * panSpeed
|
|
||||||
this.pendingPan.y += deltaMove.y * panSpeed
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* If we're not in sketch mode and not dragging, we can highlight entities
|
* If we're not in sketch mode and not dragging, we can highlight entities
|
||||||
@ -433,6 +478,31 @@ export class CameraControls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveCamera(interaction: interactionType, deltaMove: Vector2) {
|
||||||
|
// Implement camera movement logic here based on deltaMove
|
||||||
|
// For example, for rotating the camera around the target:
|
||||||
|
if (interaction === 'rotate') {
|
||||||
|
this.pendingRotation = this.pendingRotation
|
||||||
|
? this.pendingRotation
|
||||||
|
: new Vector2()
|
||||||
|
this.pendingRotation.x += deltaMove.x
|
||||||
|
this.pendingRotation.y += deltaMove.y
|
||||||
|
} else if (interaction === 'zoom') {
|
||||||
|
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||||
|
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
||||||
|
} else if (interaction === 'pan') {
|
||||||
|
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
||||||
|
let distance = this.camera.position.distanceTo(this.target)
|
||||||
|
if (this.camera instanceof OrthographicCamera) {
|
||||||
|
const zoomFudgeFactor = 2280
|
||||||
|
distance = zoomFudgeFactor / (this.camera.zoom * 45)
|
||||||
|
}
|
||||||
|
const panSpeed = (distance / 1000 / 45) * this.perspectiveFovBeforeOrtho
|
||||||
|
this.pendingPan.x += -deltaMove.x * panSpeed
|
||||||
|
this.pendingPan.y += deltaMove.y * panSpeed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMouseUp = (event: PointerEvent) => {
|
onMouseUp = (event: PointerEvent) => {
|
||||||
this.domElement.releasePointerCapture(event.pointerId)
|
this.domElement.releasePointerCapture(event.pointerId)
|
||||||
this.isDragging = false
|
this.isDragging = false
|
||||||
@ -452,6 +522,20 @@ export class CameraControls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomDirection = (event: WheelEvent): 1 | -1 => {
|
||||||
|
if (this.interactionGuards.zoom.pinchToZoom && isPinchToZoom(event)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.interactionGuards.zoom.scrollAllowInvertY) return 1
|
||||||
|
// Safari provides the updated user setting on every event, so it's more
|
||||||
|
// accurate than our cached value.
|
||||||
|
if ('webkitDirectionInvertedFromDevice' in event) {
|
||||||
|
return event.webkitDirectionInvertedFromDevice ? -1 : 1
|
||||||
|
}
|
||||||
|
return cachedNaturalScrollDirection ? -1 : 1
|
||||||
|
}
|
||||||
|
|
||||||
onMouseWheel = (event: WheelEvent) => {
|
onMouseWheel = (event: WheelEvent) => {
|
||||||
const interaction = this.getInteractionType(event)
|
const interaction = this.getInteractionType(event)
|
||||||
if (interaction === 'none') return
|
if (interaction === 'none') return
|
||||||
@ -459,12 +543,15 @@ export class CameraControls {
|
|||||||
|
|
||||||
if (this.syncDirection === 'engineToClient') {
|
if (this.syncDirection === 'engineToClient') {
|
||||||
if (interaction === 'zoom') {
|
if (interaction === 'zoom') {
|
||||||
this.zoomDataFromLastFrame = event.deltaY
|
const zoomDir = this.zoomDirection(event)
|
||||||
|
this.zoomDataFromLastFrame = event.deltaY * zoomDir
|
||||||
} else {
|
} else {
|
||||||
// This case will get handled when we add pan and rotate using Apple trackpad.
|
this.moveDataFromLastFrame = [
|
||||||
console.error(
|
'wheel',
|
||||||
`Unexpected interaction type for engineToClient wheel event: ${interaction}`
|
event.deltaX,
|
||||||
)
|
event.deltaY,
|
||||||
|
interaction,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -478,12 +565,20 @@ export class CameraControls {
|
|||||||
|
|
||||||
this.handleStart()
|
this.handleStart()
|
||||||
if (interaction === 'zoom') {
|
if (interaction === 'zoom') {
|
||||||
this.pendingZoom = 1 + (event.deltaY / window.devicePixelRatio) * 0.001
|
const zoomDir = this.zoomDirection(event)
|
||||||
|
this.pendingZoom =
|
||||||
|
1 + (event.deltaY / window.devicePixelRatio) * 0.001 * zoomDir
|
||||||
} else {
|
} else {
|
||||||
// This case will get handled when we add pan and rotate using Apple trackpad.
|
this.isDragging = true
|
||||||
console.error(
|
this.mouseDownPosition.set(event.clientX, event.clientY)
|
||||||
`Unexpected interaction type for wheel event: ${interaction}`
|
|
||||||
|
this.moveCamera(interaction, new Vector2(-event.deltaX, -event.deltaY))
|
||||||
|
|
||||||
|
this.mouseDownPosition.set(
|
||||||
|
event.clientX + event.deltaX,
|
||||||
|
event.clientY + event.deltaY
|
||||||
)
|
)
|
||||||
|
this.isDragging = false
|
||||||
}
|
}
|
||||||
this.handleEnd()
|
this.handleEnd()
|
||||||
}
|
}
|
||||||
@ -1266,8 +1361,17 @@ function _getInteractionType(
|
|||||||
enableZoom: boolean
|
enableZoom: boolean
|
||||||
): interactionType | 'none' {
|
): interactionType | 'none' {
|
||||||
if (event instanceof WheelEvent) {
|
if (event instanceof WheelEvent) {
|
||||||
if (enableZoom && interactionGuards.zoom.scrollCallback(event))
|
// If the control scheme accepts pinch-to-zoom, and the event is
|
||||||
return 'zoom'
|
// pinch-to-zoom, never consider other interaction types.
|
||||||
|
if (interactionGuards.zoom.pinchToZoom && isPinchToZoom(event)) {
|
||||||
|
if (enableZoom) return 'zoom'
|
||||||
|
} else {
|
||||||
|
if (enablePan && interactionGuards.pan.scrollCallback(event)) return 'pan'
|
||||||
|
if (enableRotate && interactionGuards.rotate.scrollCallback(event))
|
||||||
|
return 'rotate'
|
||||||
|
if (enableZoom && interactionGuards.zoom.scrollCallback(event))
|
||||||
|
return 'zoom'
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
|
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
|
||||||
if (enableRotate && interactionGuards.rotate.callback(event))
|
if (enableRotate && interactionGuards.rotate.callback(event))
|
||||||
@ -1277,6 +1381,18 @@ function _getInteractionType(
|
|||||||
return 'none'
|
return 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPinchToZoom(event: WheelEvent): boolean {
|
||||||
|
// Browsers do this hack. A couple issues:
|
||||||
|
//
|
||||||
|
// - According to MDN, it doesn't work on iOS.
|
||||||
|
// - It doesn't differentiate with a user actually holding Control and
|
||||||
|
// scrolling normally. It's possible to detect this by using onKeyDown and
|
||||||
|
// onKeyUp to track the state of the Control key. But we currently don't
|
||||||
|
// care about this since only the Apple Trackpad scheme looks for
|
||||||
|
// pinch-to-zoom events using interactionGuards.zoom.pinchToZoom.
|
||||||
|
return event.ctrlKey
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells the engine to fire it's animation waits for it to finish and then requests camera settings
|
* Tells the engine to fire it's animation waits for it to finish and then requests camera settings
|
||||||
* to ensure the client-side camera is synchronized with the engine's camera state.
|
* to ensure the client-side camera is synchronized with the engine's camera state.
|
||||||
|
@ -13,6 +13,7 @@ export type CameraSystem =
|
|||||||
| 'KittyCAD'
|
| 'KittyCAD'
|
||||||
| 'OnShape'
|
| 'OnShape'
|
||||||
| 'Trackpad Friendly'
|
| 'Trackpad Friendly'
|
||||||
|
| 'Apple Trackpad'
|
||||||
| 'Solidworks'
|
| 'Solidworks'
|
||||||
| 'NX'
|
| 'NX'
|
||||||
| 'Creo'
|
| 'Creo'
|
||||||
@ -22,6 +23,7 @@ export const cameraSystems: CameraSystem[] = [
|
|||||||
'KittyCAD',
|
'KittyCAD',
|
||||||
'OnShape',
|
'OnShape',
|
||||||
'Trackpad Friendly',
|
'Trackpad Friendly',
|
||||||
|
'Apple Trackpad',
|
||||||
'Solidworks',
|
'Solidworks',
|
||||||
'NX',
|
'NX',
|
||||||
'Creo',
|
'Creo',
|
||||||
@ -38,6 +40,8 @@ export function mouseControlsToCameraSystem(
|
|||||||
return 'OnShape'
|
return 'OnShape'
|
||||||
case 'trackpad_friendly':
|
case 'trackpad_friendly':
|
||||||
return 'Trackpad Friendly'
|
return 'Trackpad Friendly'
|
||||||
|
case 'apple_trackpad':
|
||||||
|
return 'Apple Trackpad'
|
||||||
case 'solidworks':
|
case 'solidworks':
|
||||||
return 'Solidworks'
|
return 'Solidworks'
|
||||||
case 'nx':
|
case 'nx':
|
||||||
@ -54,6 +58,7 @@ export function mouseControlsToCameraSystem(
|
|||||||
interface MouseGuardHandler {
|
interface MouseGuardHandler {
|
||||||
description: string
|
description: string
|
||||||
callback: (e: MouseEvent) => boolean
|
callback: (e: MouseEvent) => boolean
|
||||||
|
scrollCallback: (e: WheelEvent) => boolean
|
||||||
lenientDragStartButton?: number
|
lenientDragStartButton?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +66,8 @@ interface MouseGuardZoomHandler {
|
|||||||
description: string
|
description: string
|
||||||
dragCallback: (e: MouseEvent) => boolean
|
dragCallback: (e: MouseEvent) => boolean
|
||||||
scrollCallback: (e: WheelEvent) => boolean
|
scrollCallback: (e: WheelEvent) => boolean
|
||||||
|
scrollAllowInvertY?: boolean
|
||||||
|
pinchToZoom?: boolean
|
||||||
lenientDragStartButton?: number
|
lenientDragStartButton?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +90,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(btnName(e).middle && noModifiersPressed(e)) ||
|
(btnName(e).middle && noModifiersPressed(e)) ||
|
||||||
(btnName(e).right && e.shiftKey),
|
(btnName(e).right && e.shiftKey),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll or Ctrl + Right click drag',
|
description: 'Scroll or Ctrl + Right click drag',
|
||||||
@ -92,6 +100,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
rotate: {
|
rotate: {
|
||||||
description: 'Right click drag',
|
description: 'Right click drag',
|
||||||
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OnShape: {
|
OnShape: {
|
||||||
@ -100,6 +109,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(btnName(e).right && e.ctrlKey) ||
|
(btnName(e).right && e.ctrlKey) ||
|
||||||
(btnName(e).middle && noModifiersPressed(e)),
|
(btnName(e).middle && noModifiersPressed(e)),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll',
|
description: 'Scroll',
|
||||||
@ -109,6 +119,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
rotate: {
|
rotate: {
|
||||||
description: 'Right click drag',
|
description: 'Right click drag',
|
||||||
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'Trackpad Friendly': {
|
'Trackpad Friendly': {
|
||||||
@ -117,6 +128,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(btnName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
|
(btnName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
|
||||||
(btnName(e).middle && noModifiersPressed(e)),
|
(btnName(e).middle && noModifiersPressed(e)),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: `Scroll or ${ALT} + ${META} + Left click drag`,
|
description: `Scroll or ${ALT} + ${META} + Left click drag`,
|
||||||
@ -126,13 +138,45 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
rotate: {
|
rotate: {
|
||||||
description: `${ALT} + Left click drag`,
|
description: `${ALT} + Left click drag`,
|
||||||
callback: (e) => btnName(e).left && e.altKey && !e.shiftKey && !e.metaKey,
|
callback: (e) => btnName(e).left && e.altKey && !e.shiftKey && !e.metaKey,
|
||||||
|
scrollCallback: () => false,
|
||||||
lenientDragStartButton: 0,
|
lenientDragStartButton: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'Apple Trackpad': {
|
||||||
|
pan: {
|
||||||
|
description: `Scroll or one finger drag`,
|
||||||
|
callback: (e) => btnName(e).left && noModifiersPressed(e),
|
||||||
|
scrollCallback: (e) => e.deltaMode === 0 && noModifiersPressed(e),
|
||||||
|
lenientDragStartButton: 0,
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
description: `Shift + Scroll`,
|
||||||
|
dragCallback: (e) => false,
|
||||||
|
scrollCallback: (e) =>
|
||||||
|
e.deltaMode === 0 &&
|
||||||
|
e.shiftKey &&
|
||||||
|
!e.ctrlKey &&
|
||||||
|
!e.altKey &&
|
||||||
|
!e.metaKey,
|
||||||
|
scrollAllowInvertY: true,
|
||||||
|
pinchToZoom: true,
|
||||||
|
},
|
||||||
|
rotate: {
|
||||||
|
description: `${ALT} + Scroll`,
|
||||||
|
callback: (e) => false,
|
||||||
|
scrollCallback: (e) =>
|
||||||
|
e.deltaMode === 0 &&
|
||||||
|
e.altKey &&
|
||||||
|
!e.ctrlKey &&
|
||||||
|
!e.shiftKey &&
|
||||||
|
!e.metaKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
Solidworks: {
|
Solidworks: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Ctrl + Right click drag',
|
description: 'Ctrl + Right click drag',
|
||||||
callback: (e) => btnName(e).right && e.ctrlKey,
|
callback: (e) => btnName(e).right && e.ctrlKey,
|
||||||
|
scrollCallback: () => false,
|
||||||
lenientDragStartButton: 2,
|
lenientDragStartButton: 2,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
@ -143,12 +187,14 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click drag',
|
description: 'Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NX: {
|
NX: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Shift + Middle click drag',
|
description: 'Shift + Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && e.shiftKey,
|
callback: (e) => btnName(e).middle && e.shiftKey,
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll or Ctrl + Middle click drag',
|
description: 'Scroll or Ctrl + Middle click drag',
|
||||||
@ -158,12 +204,14 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click drag',
|
description: 'Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Creo: {
|
Creo: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Ctrl + Left click drag',
|
description: 'Ctrl + Left click drag',
|
||||||
callback: (e) => btnName(e).left && !btnName(e).right && e.ctrlKey,
|
callback: (e) => btnName(e).left && !btnName(e).right && e.ctrlKey,
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll or Ctrl + Right click drag',
|
description: 'Scroll or Ctrl + Right click drag',
|
||||||
@ -176,12 +224,14 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
const b = btnName(e)
|
const b = btnName(e)
|
||||||
return (b.middle || (b.left && b.right)) && e.ctrlKey
|
return (b.middle || (b.left && b.right)) && e.ctrlKey
|
||||||
},
|
},
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AutoCAD: {
|
AutoCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click drag',
|
description: 'Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll',
|
description: 'Scroll',
|
||||||
@ -191,6 +241,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
rotate: {
|
rotate: {
|
||||||
description: 'Shift + Middle click drag',
|
description: 'Shift + Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && e.shiftKey,
|
callback: (e) => btnName(e).middle && e.shiftKey,
|
||||||
|
scrollCallback: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -565,3 +565,7 @@ export const getUser = async (
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error('unreachable'))
|
return Promise.reject(new Error('unreachable'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function readNaturalScrollDirection() {
|
||||||
|
return window.electron.readNaturalScrollDirection()
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { v4 } from 'uuid'
|
|||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
import { AnyMachineSnapshot } from 'xstate'
|
import { AnyMachineSnapshot } from 'xstate'
|
||||||
import { AsyncFn } from './types'
|
import { AsyncFn } from './types'
|
||||||
|
import { readNaturalScrollDirection } from './desktop'
|
||||||
|
|
||||||
export const uuidv4 = v4
|
export const uuidv4 = v4
|
||||||
|
|
||||||
@ -262,6 +263,19 @@ export function isReducedMotion(): boolean {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if Apple Trackpad scroll should move the content. I.e. if this is true,
|
||||||
|
* and the user scrolls down, the viewport moves up relative to the content.
|
||||||
|
*/
|
||||||
|
export let cachedNaturalScrollDirection = platform() === 'macos'
|
||||||
|
|
||||||
|
export async function refreshNaturalScrollDirection() {
|
||||||
|
if (!isDesktop()) return cachedNaturalScrollDirection
|
||||||
|
const isNatural = await readNaturalScrollDirection()
|
||||||
|
cachedNaturalScrollDirection = isNatural
|
||||||
|
return isNatural
|
||||||
|
}
|
||||||
|
|
||||||
export function XOR(bool1: boolean, bool2: boolean): boolean {
|
export function XOR(bool1: boolean, bool2: boolean): boolean {
|
||||||
return (bool1 || bool2) && !(bool1 && bool2)
|
return (bool1 || bool2) && !(bool1 && bool2)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import os from 'node:os'
|
|||||||
import fsSync from 'node:fs'
|
import fsSync from 'node:fs'
|
||||||
import packageJson from '../package.json'
|
import packageJson from '../package.json'
|
||||||
import { MachinesListing } from 'lib/machineManager'
|
import { MachinesListing } from 'lib/machineManager'
|
||||||
|
import { exec } from 'child_process'
|
||||||
import chokidar from 'chokidar'
|
import chokidar from 'chokidar'
|
||||||
|
|
||||||
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
||||||
@ -81,6 +82,25 @@ const listMachines = async (): Promise<MachinesListing> => {
|
|||||||
const getMachineApiIp = async (): Promise<String | null> =>
|
const getMachineApiIp = async (): Promise<String | null> =>
|
||||||
ipcRenderer.invoke('find_machine_api')
|
ipcRenderer.invoke('find_machine_api')
|
||||||
|
|
||||||
|
async function readNaturalScrollDirection(): Promise<boolean> {
|
||||||
|
if (os.platform() !== 'darwin') {
|
||||||
|
// TODO: Detect this on other OS's.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(
|
||||||
|
'defaults read -globalDomain com.apple.swipescrolldirection',
|
||||||
|
(err, stdout) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve(stdout.trim() === '1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
startDeviceFlow,
|
startDeviceFlow,
|
||||||
loginWithDeviceFlow,
|
loginWithDeviceFlow,
|
||||||
@ -144,6 +164,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
kittycad,
|
kittycad,
|
||||||
listMachines,
|
listMachines,
|
||||||
getMachineApiIp,
|
getMachineApiIp,
|
||||||
|
readNaturalScrollDirection,
|
||||||
onUpdateDownloaded,
|
onUpdateDownloaded,
|
||||||
appRestart,
|
appRestart,
|
||||||
})
|
})
|
||||||
|
@ -37,6 +37,8 @@ import {
|
|||||||
} from 'lib/desktop'
|
} from 'lib/desktop'
|
||||||
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
|
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
|
||||||
import { Project } from 'lib/project'
|
import { Project } from 'lib/project'
|
||||||
|
import { refreshNaturalScrollDirection } from 'lib/utils'
|
||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||||
import { useProjectsLoader } from 'hooks/useProjectsLoader'
|
import { useProjectsLoader } from 'hooks/useProjectsLoader'
|
||||||
|
|
||||||
@ -61,6 +63,11 @@ const Home = () => {
|
|||||||
kclManager.cancelAllExecutions()
|
kclManager.cancelAllExecutions()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Load OS setting.
|
||||||
|
refreshNaturalScrollDirection().catch(reportRejection)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useHotkeys('backspace', (e) => {
|
useHotkeys('backspace', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
})
|
})
|
||||||
|
@ -389,6 +389,8 @@ pub enum MouseControlType {
|
|||||||
OnShape,
|
OnShape,
|
||||||
#[serde(alias = "Trackpad Friendly")]
|
#[serde(alias = "Trackpad Friendly")]
|
||||||
TrackpadFriendly,
|
TrackpadFriendly,
|
||||||
|
#[serde(alias = "Apple Trackpad")]
|
||||||
|
AppleTrackpad,
|
||||||
#[serde(alias = "Solidworks")]
|
#[serde(alias = "Solidworks")]
|
||||||
Solidworks,
|
Solidworks,
|
||||||
#[serde(alias = "NX")]
|
#[serde(alias = "NX")]
|
||||||
|
Reference in New Issue
Block a user