Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/stage-tamagotchi/src/main/services/electron/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { createContext } from '@moeru/eventa/adapters/electron/main'
import type { BrowserWindow } from 'electron'

import { defineInvokeHandler } from '@moeru/eventa'
import { isMacOS, isWindows } from 'std-env'

import { electron } from '../../../shared/eventa'

export function createAppService(params: { context: ReturnType<typeof createContext>['context'], window: BrowserWindow }) {
defineInvokeHandler(params.context, electron.app.isMacOS, () => isMacOS)
defineInvokeHandler(params.context, electron.app.isWindows, () => isWindows)
}
1 change: 1 addition & 0 deletions apps/stage-tamagotchi/src/main/services/electron/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './app'
export * from './auto-updater'
export * from './screen'
export * from './window'
21 changes: 21 additions & 0 deletions apps/stage-tamagotchi/src/main/services/electron/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { bounds, startLoopGetBounds } from '../../../shared/electron/window'
import { electron } from '../../../shared/eventa'
import { onAppBeforeQuit, onAppWindowAllClosed } from '../../libs/bootkit/lifecycle'
import { useLoop } from '../../libs/event-loop'
import { resizeWindowByDelta } from '../../windows/shared/window'

export function createWindowService(params: { context: ReturnType<typeof createContext>['context'], window: BrowserWindow }) {
const { start, stop } = useLoop(() => {
Expand Down Expand Up @@ -35,6 +36,13 @@ export function createWindowService(params: { context: ReturnType<typeof createC
height: 0,
}
})

defineInvokeHandler(params.context, electron.window.setBounds, (newBounds, options) => {
if (params.window.webContents.id === options?.raw.ipcMainEvent.sender.id) {
params.window.setBounds(newBounds[0])
}
})

defineInvokeHandler(params.context, electron.window.setIgnoreMouseEvents, (opts, options) => {
if (params.window.webContents.id === options?.raw.ipcMainEvent.sender.id) {
params.window.setIgnoreMouseEvents(...opts)
Expand All @@ -52,4 +60,17 @@ export function createWindowService(params: { context: ReturnType<typeof createC
params.window.setBackgroundMaterial(backgroundMaterial[0])
}
})

defineInvokeHandler(params.context, electron.window.resize, (payload, options) => {
if (params.window.webContents.id !== options?.raw.ipcMainEvent.sender.id) {
return
}

resizeWindowByDelta({
window: params.window,
deltaX: payload.deltaX,
deltaY: payload.deltaY,
direction: payload.direction,
})
})
}
41 changes: 41 additions & 0 deletions apps/stage-tamagotchi/src/main/windows/shared/window.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { BrowserWindow, BrowserWindowConstructorOptions } from 'electron'

import type { ResizeDirection } from '../../../shared/electron/window'

import { isMacOS } from 'std-env'

export function toggleWindowShow(window?: BrowserWindow | null): void {
Expand Down Expand Up @@ -40,3 +42,42 @@ export function spotlightLikeWindowConfig(): BrowserWindowConstructorOptions {
titleBarStyle: isMacOS ? 'hidden' : undefined,
}
}

export function resizeWindowByDelta(params: {
window: BrowserWindow
deltaX: number
deltaY: number
direction: ResizeDirection
minWidth?: number
minHeight?: number
}): void {
const bounds = params.window.getBounds()
const minWidth = params.minWidth ?? 100
const minHeight = params.minHeight ?? 200

let { x, y, width, height } = bounds

if (params.direction.includes('e')) {
width = Math.max(minWidth, width + params.deltaX)
}
if (params.direction.includes('w')) {
const newWidth = Math.max(minWidth, width - params.deltaX)
if (newWidth !== width) {
x = x + (width - newWidth)
width = newWidth
}
}

if (params.direction.includes('s')) {
height = Math.max(minHeight, height + params.deltaY)
}
if (params.direction.includes('n')) {
const newHeight = Math.max(minHeight, height - params.deltaY)
if (newHeight !== height) {
y = y + (height - newHeight)
height = newHeight
}
}

params.window.setBounds({ x, y, width, height })
}
3 changes: 3 additions & 0 deletions apps/stage-tamagotchi/src/renderer/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { useI18n } from 'vue-i18n'
import { RouterView, useRoute, useRouter } from 'vue-router'
import { toast, Toaster } from 'vue-sonner'

import ResizeHandler from './components/ResizeHandler.vue'

import { electronOpenSettings, electronStartTrackMousePosition } from '../shared/eventa'
import { useElectronEventaContext } from './composables/electron-vueuse'

Expand Down Expand Up @@ -81,6 +83,7 @@ onUnmounted(() => contextBridgeStore.dispose())
<ToasterRoot @close="id => toast.dismiss(id)">
<Toaster />
</ToasterRoot>
<ResizeHandler />
<RouterView />
</template>

Expand Down
50 changes: 50 additions & 0 deletions apps/stage-tamagotchi/src/renderer/components/ResizeHandler.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup lang="ts">
import { useAsyncState } from '@vueuse/core'

import { electron } from '../../shared/electron'
import { useElectronEventaInvoke, useElectronWindowResize } from '../composables/electron-vueuse'

const isWindows = useElectronEventaInvoke(electron.app.isWindows)
const { handleResizeStart } = useElectronWindowResize()
const isWindowsRef = useAsyncState(() => isWindows(), false)
</script>

<template>
<div v-if="isWindowsRef" class="resize-handles">
<div class="handle n" @mousedown="handleResizeStart($event, 'n')" />
<div class="handle s" @mousedown="handleResizeStart($event, 's')" />
<div class="handle e" @mousedown="handleResizeStart($event, 'e')" />
<div class="handle w" @mousedown="handleResizeStart($event, 'w')" />
<div class="handle ne" @mousedown="handleResizeStart($event, 'ne')" />
<div class="handle nw" @mousedown="handleResizeStart($event, 'nw')" />
<div class="handle se" @mousedown="handleResizeStart($event, 'se')" />
<div class="handle sw" @mousedown="handleResizeStart($event, 'sw')" />
</div>
</template>

<style scoped>
.resize-handles {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 9999;
}

.handle {
position: absolute;
pointer-events: auto;
}

.handle.n { top: 0; left: 5px; right: 5px; height: 5px; cursor: n-resize; }
.handle.s { bottom: 0; left: 5px; right: 5px; height: 5px; cursor: s-resize; }
.handle.e { top: 5px; bottom: 5px; right: 0; width: 5px; cursor: e-resize; }
.handle.w { top: 5px; bottom: 5px; left: 0; width: 5px; cursor: w-resize; }

.handle.nw { top: 0; left: 0; width: 10px; height: 10px; cursor: nw-resize; }
.handle.ne { top: 0; right: 0; width: 10px; height: 10px; cursor: ne-resize; }
.handle.sw { bottom: 0; left: 0; width: 10px; height: 10px; cursor: sw-resize; }
.handle.se { bottom: 0; right: 0; width: 10px; height: 10px; cursor: se-resize; }
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { useElectronMouseInElement } from './use-electron-mouse-in-element'
export { useElectronMouseInWindow } from './use-electron-mouse-in-window'
export { useElectronRelativeMouse } from './use-electron-relative-mouse'
export { useElectronWindowBounds } from './use-electron-window-bounds'
export { useElectronWindowResize } from './use-electron-window-resize'
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { ResizeDirection } from '../../../../shared/electron/window'

import { useElectronEventaInvoke } from '..'
import { electron } from '../../../../shared/eventa'

export function useElectronWindowResize() {
const isWindows = useElectronEventaInvoke(electron.app.isWindows)
const resizeWindow = useElectronEventaInvoke(electron.window.resize)

const handleResizeStart = async (e: MouseEvent, direction: ResizeDirection) => {
if (!await isWindows())
return

e.preventDefault()
e.stopPropagation()

let lastX = e.screenX
let lastY = e.screenY

const handleMouseMove = (moveEvent: MouseEvent) => {
const deltaX = moveEvent.screenX - lastX
const deltaY = moveEvent.screenY - lastY

if (deltaX !== 0 || deltaY !== 0) {
resizeWindow({ deltaX, deltaY, direction })
lastX = moveEvent.screenX
lastY = moveEvent.screenY
}
}

const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}

document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
}

return {
handleResizeStart,
}
}
11 changes: 11 additions & 0 deletions apps/stage-tamagotchi/src/shared/electron/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineInvokeEventa } from '@moeru/eventa'

const isMacOS = defineInvokeEventa<boolean>('eventa:invoke:electron:app:is-mac-os')
const isWindows = defineInvokeEventa<boolean>('eventa:invoke:electron:app:is-windows')
const isLinux = defineInvokeEventa<boolean>('eventa:invoke:electron:app:is-linux')

export const app = {
isMacOS,
isWindows,
isLinux,
}
2 changes: 2 additions & 0 deletions apps/stage-tamagotchi/src/shared/electron/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { app } from './app'
import { screen } from './screen'
import { systemPreferences } from './system-preferences'
import { window } from './window'
Expand All @@ -6,4 +7,5 @@ export const electron = {
screen,
window,
systemPreferences,
app,
}
5 changes: 5 additions & 0 deletions apps/stage-tamagotchi/src/shared/electron/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ export const bounds = defineEventa<Rectangle>('eventa:event:electron:window:boun
export const startLoopGetBounds = defineInvokeEventa('eventa:event:electron:window:start-loop-get-bounds')

const getBounds = defineInvokeEventa<ReturnType<BrowserWindow['getBounds']>>('eventa:invoke:electron:window:get-bounds')
const setBounds = defineInvokeEventa<void, Parameters<BrowserWindow['setBounds']>>('eventa:invoke:electron:window:set-bounds')
const setIgnoreMouseEvents = defineInvokeEventa<void, [boolean, { forward: boolean }]>('eventa:invoke:electron:window:set-ignore-mouse-events')
const setVibrancy = defineInvokeEventa<void, Parameters<BrowserWindow['setVibrancy']>>('eventa:invoke:electron:window:set-vibrancy')
const setBackgroundMaterial = defineInvokeEventa<void, Parameters<BrowserWindow['setBackgroundMaterial']>>('eventa:invoke:electron:window:set-background-material')
const resize = defineInvokeEventa<void, { deltaX: number, deltaY: number, direction: ResizeDirection }>('eventa:invoke:electron:window:resize')

export type VibrancyType = Parameters<BrowserWindow['setVibrancy']>[0]
export type BackgroundMaterialType = Parameters<BrowserWindow['setBackgroundMaterial']>[0]
export type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'

export const window = {
getBounds,
setBounds,
setIgnoreMouseEvents,
setVibrancy,
setBackgroundMaterial,
resize,
}