Skip to content

Commit

Permalink
style(stage-tamagotchi): better inertia
Browse files Browse the repository at this point in the history
  • Loading branch information
nekomeowww committed Feb 20, 2025
1 parent 5403839 commit 2a3e290
Showing 1 changed file with 90 additions and 49 deletions.
139 changes: 90 additions & 49 deletions packages/stage-tamagotchi/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { join } from 'node:path'
import { env, platform } from 'node:process'
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import { app, BrowserWindow, dialog, ipcMain, Menu, screen, shell } from 'electron'
import { animate } from 'popmotion'
import { inertia } from 'popmotion'

import icon from '../../build/icon.png?asset'

Expand All @@ -11,6 +11,9 @@ let mainWindow: BrowserWindow
let currentAnimationX: { stop: () => void } | null = null
let currentAnimationY: { stop: () => void } | null = null
let isDragging = false
let lastMousePosition = { x: 0, y: 0 }
let lastMouseTime = Date.now()
let currentVelocity = { x: 0, y: 0 }
let dragOffset = { x: 0, y: 0 }

function createWindow(): void {
Expand Down Expand Up @@ -53,11 +56,15 @@ function createWindow(): void {
mainWindow.loadFile(join(import.meta.dirname, '..', '..', 'out', 'renderer', 'index.html'))
}

ipcMain.on('start-window-drag', (_, clickX: number, clickY: number) => {
ipcMain.on('start-window-drag', (_) => {
isDragging = true
const mousePos = screen.getCursorScreenPoint()
const [windowX, windowY] = mainWindow.getPosition()

// Calculate the offset between cursor and window position
dragOffset = {
x: clickX,
y: clickY,
x: mousePos.x - windowX,
y: mousePos.y - windowY,
}

// Stop any existing animations
Expand All @@ -70,12 +77,16 @@ function createWindow(): void {
currentAnimationY = null
}

// Initialize last position for velocity tracking
lastMousePosition = { x: mousePos.x, y: mousePos.y }
lastMouseTime = Date.now()
currentVelocity = { x: 0, y: 0 }

// Start global mouse tracking
if (!globalMouseTracker) {
globalMouseTracker = setInterval(() => {
const mousePos = screen.getCursorScreenPoint()
if (isDragging) {
// Simulate move-window event with global cursor position
handleWindowMove(mousePos.x, mousePos.y)
}
}, 16) // ~60fps
Expand All @@ -88,6 +99,61 @@ function createWindow(): void {
clearInterval(globalMouseTracker)
globalMouseTracker = null
}

// Apply inertia animation when drag ends
const [currentX, currentY] = mainWindow.getPosition()
let latestX = currentX
let latestY = currentY

const inertiaConfig = {
power: 0.4, // Reduced from 0.6 for stronger resistance
timeConstant: 250, // Reduced from 400 for quicker deceleration
modifyTarget: (v: number) => v,
min: 0,
max: Infinity,
}

// Clamp velocity to reasonable values
const clampVelocity = (v: number) => {
const maxVelocity = 500 // Reduced from 800 for less momentum
const minVelocity = -500
return Math.min(Math.max(v, minVelocity), maxVelocity)
}

// Reduce velocity amplification and clamp values
const amplifiedVelocity = {
x: clampVelocity(currentVelocity.x * 0.2), // Reduced from 0.3 for less momentum
y: clampVelocity(currentVelocity.y * 0.2),
}

// Ignore very small movements
if (Math.abs(amplifiedVelocity.x) > 35 || Math.abs(amplifiedVelocity.y) > 35) {
currentAnimationX = inertia({
from: currentX,
velocity: amplifiedVelocity.x,
...inertiaConfig,
onUpdate: (x) => {
latestX = Math.round(x)
mainWindow.setPosition(latestX, Math.round(latestY))
},
onComplete: () => {
currentAnimationX = null
},
})

currentAnimationY = inertia({
from: currentY,
velocity: amplifiedVelocity.y,
...inertiaConfig,
onUpdate: (y) => {
latestY = Math.round(y)
mainWindow.setPosition(Math.round(latestX), latestY)
},
onComplete: () => {
currentAnimationY = null
},
})
}
})

ipcMain.on('move-window', (_, cursorX: number, cursorY: number) => {
Expand Down Expand Up @@ -221,51 +287,26 @@ function handleWindowMove(cursorX: number, cursorY: number) {
if (!isDragging)
return

// Stop current animations
if (currentAnimationX)
currentAnimationX.stop()
if (currentAnimationY)
currentAnimationY.stop()

const targetX = cursorX - dragOffset.x
const targetY = cursorY - dragOffset.y
const [currentX, currentY] = mainWindow.getPosition()
let latestX = currentX
let latestY = currentY

// Shared animation config for squishie bounce effect
const springConfig = {
stiffness: 2000, // Reduced for more elastic feel
damping: 100, // Lower damping for more bounces
mass: 0.5, // Higher mass for more momentum
restSpeed: 0.1, // Lower rest speed to allow more bouncing
// Calculate actual velocity based on mouse movement
const currentTime = Date.now()
const deltaTime = currentTime - lastMouseTime

if (deltaTime > 0) {
// Smooth out velocity calculation with some averaging
const newVelocityX = (cursorX - lastMousePosition.x) / deltaTime * 1000
const newVelocityY = (cursorY - lastMousePosition.y) / deltaTime * 1000

currentVelocity = {
x: currentVelocity.x * 0.8 + newVelocityX * 0.2, // Smooth velocity
y: currentVelocity.y * 0.8 + newVelocityY * 0.2,
}
}

currentAnimationX = animate({
from: currentX,
to: targetX,
type: 'spring',
...springConfig,
onUpdate: (x) => {
latestX = x
mainWindow.setPosition(Math.round(latestX), Math.round(latestY))
},
onComplete: () => {
currentAnimationX = null
},
})
// Update window position based on cursor position and offset
const newX = cursorX - dragOffset.x
const newY = cursorY - dragOffset.y
mainWindow.setPosition(Math.round(newX), Math.round(newY))

currentAnimationY = animate({
from: currentY,
to: targetY,
type: 'spring',
...springConfig,
onUpdate: (y) => {
latestY = y
mainWindow.setPosition(Math.round(latestX), Math.round(latestY))
},
onComplete: () => {
currentAnimationY = null
},
})
lastMousePosition = { x: cursorX, y: cursorY }
lastMouseTime = currentTime
}

0 comments on commit 2a3e290

Please sign in to comment.