mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
fix(windows): add manual window resize for SelectionAction window (#11766)
Implement custom window resize functionality for the SelectionAction window on Windows only. This is a workaround for an Electron bug where native window resize doesn't work with frame: false + transparent: true. - Add IPC channel and API for window resize - Implement resize handler in SelectionService - Add 8 resize handles (4 edges + 4 corners) in SelectionActionApp - Only enable on Windows, other platforms use native resize Bug reference: https://github.com/electron/electron/issues/42738 All workaround code is documented and can be removed once the bug is fixed. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
parent
f8c33db450
commit
bc00c11a00
@ -293,6 +293,8 @@ export enum IpcChannel {
|
||||
Selection_ActionWindowClose = 'selection:action-window-close',
|
||||
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
||||
Selection_ActionWindowPin = 'selection:action-window-pin',
|
||||
// [Windows only] Electron bug workaround - can be removed once https://github.com/electron/electron/issues/48554 is fixed
|
||||
Selection_ActionWindowResize = 'selection:action-window-resize',
|
||||
Selection_ProcessAction = 'selection:process-action',
|
||||
Selection_UpdateActionData = 'selection:update-action-data',
|
||||
|
||||
|
||||
@ -1393,6 +1393,50 @@ export class SelectionService {
|
||||
actionWindow.setAlwaysOnTop(isPinned)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Windows only] Manual window resize handler
|
||||
*
|
||||
* ELECTRON BUG WORKAROUND:
|
||||
* In Electron, when using `frame: false` + `transparent: true`, the native window
|
||||
* resize functionality is broken on Windows. This is a known Electron bug.
|
||||
* See: https://github.com/electron/electron/issues/48554
|
||||
*
|
||||
* This method can be removed once the Electron bug is fixed.
|
||||
*/
|
||||
public resizeActionWindow(actionWindow: BrowserWindow, deltaX: number, deltaY: number, direction: string): void {
|
||||
const bounds = actionWindow.getBounds()
|
||||
const minWidth = 300
|
||||
const minHeight = 200
|
||||
|
||||
let { x, y, width, height } = bounds
|
||||
|
||||
// Handle horizontal resize
|
||||
if (direction.includes('e')) {
|
||||
width = Math.max(minWidth, width + deltaX)
|
||||
}
|
||||
if (direction.includes('w')) {
|
||||
const newWidth = Math.max(minWidth, width - deltaX)
|
||||
if (newWidth !== width) {
|
||||
x = x + (width - newWidth)
|
||||
width = newWidth
|
||||
}
|
||||
}
|
||||
|
||||
// Handle vertical resize
|
||||
if (direction.includes('s')) {
|
||||
height = Math.max(minHeight, height + deltaY)
|
||||
}
|
||||
if (direction.includes('n')) {
|
||||
const newHeight = Math.max(minHeight, height - deltaY)
|
||||
if (newHeight !== height) {
|
||||
y = y + (height - newHeight)
|
||||
height = newHeight
|
||||
}
|
||||
}
|
||||
|
||||
actionWindow.setBounds({ x, y, width, height })
|
||||
}
|
||||
|
||||
/**
|
||||
* Update trigger mode behavior
|
||||
* Switches between selection-based and alt-key based triggering
|
||||
@ -1510,6 +1554,18 @@ export class SelectionService {
|
||||
}
|
||||
})
|
||||
|
||||
// [Windows only] Electron bug workaround - can be removed once fixed
|
||||
// See: https://github.com/electron/electron/issues/48554
|
||||
ipcMain.handle(
|
||||
IpcChannel.Selection_ActionWindowResize,
|
||||
(event, deltaX: number, deltaY: number, direction: string) => {
|
||||
const actionWindow = BrowserWindow.fromWebContents(event.sender)
|
||||
if (actionWindow) {
|
||||
selectionService?.resizeActionWindow(actionWindow, deltaX, deltaY, direction)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
this.isIpcHandlerRegistered = true
|
||||
}
|
||||
|
||||
|
||||
@ -456,7 +456,10 @@ const api = {
|
||||
ipcRenderer.invoke(IpcChannel.Selection_ProcessAction, actionItem, isFullScreen),
|
||||
closeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowClose),
|
||||
minimizeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowMinimize),
|
||||
pinActionWindow: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowPin, isPinned)
|
||||
pinActionWindow: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowPin, isPinned),
|
||||
// [Windows only] Electron bug workaround - can be removed once https://github.com/electron/electron/issues/48554 is fixed
|
||||
resizeActionWindow: (deltaX: number, deltaY: number, direction: string) =>
|
||||
ipcRenderer.invoke(IpcChannel.Selection_ActionWindowResize, deltaX, deltaY, direction)
|
||||
},
|
||||
agentTools: {
|
||||
respondToPermission: (payload: {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { isMac, isWin } from '@renderer/config/constant'
|
||||
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
@ -8,11 +8,14 @@ import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { Button, Slider, Tooltip } from 'antd'
|
||||
import { Droplet, Minus, Pin, X } from 'lucide-react'
|
||||
import { DynamicIcon } from 'lucide-react/dynamic'
|
||||
import type { FC } from 'react'
|
||||
import type { FC, MouseEvent as ReactMouseEvent } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
// [Windows only] Electron bug workaround type - can be removed once https://github.com/electron/electron/issues/48554 is fixed
|
||||
type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'
|
||||
|
||||
import ActionGeneral from './components/ActionGeneral'
|
||||
import ActionTranslate from './components/ActionTranslate'
|
||||
|
||||
@ -185,11 +188,62 @@ const SelectionActionApp: FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Windows only] Manual window resize handler
|
||||
*
|
||||
* ELECTRON BUG WORKAROUND:
|
||||
* In Electron, when using `frame: false` + `transparent: true`, the native window
|
||||
* resize functionality is broken on Windows. This is a known Electron bug.
|
||||
* See: https://github.com/electron/electron/issues/48554
|
||||
*
|
||||
* This custom resize implementation can be removed once the Electron bug is fixed.
|
||||
*/
|
||||
const handleResizeStart = useCallback((e: ReactMouseEvent, direction: ResizeDirection) => {
|
||||
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) {
|
||||
window.api.selection.resizeActionWindow(deltaX, deltaY, direction)
|
||||
lastX = moveEvent.screenX
|
||||
lastY = moveEvent.screenY
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseUp = () => {
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', handleMouseUp)
|
||||
}, [])
|
||||
|
||||
//we don't need to render the component if action is not set
|
||||
if (!action) return null
|
||||
|
||||
return (
|
||||
<WindowFrame $opacity={opacity / 100}>
|
||||
{/* [Windows only] Custom resize handles - Electron bug workaround, can be removed once fixed */}
|
||||
{isWin && (
|
||||
<>
|
||||
<ResizeHandle $direction="n" onMouseDown={(e) => handleResizeStart(e, 'n')} />
|
||||
<ResizeHandle $direction="s" onMouseDown={(e) => handleResizeStart(e, 's')} />
|
||||
<ResizeHandle $direction="e" onMouseDown={(e) => handleResizeStart(e, 'e')} />
|
||||
<ResizeHandle $direction="w" onMouseDown={(e) => handleResizeStart(e, 'w')} />
|
||||
<ResizeHandle $direction="ne" onMouseDown={(e) => handleResizeStart(e, 'ne')} />
|
||||
<ResizeHandle $direction="nw" onMouseDown={(e) => handleResizeStart(e, 'nw')} />
|
||||
<ResizeHandle $direction="se" onMouseDown={(e) => handleResizeStart(e, 'se')} />
|
||||
<ResizeHandle $direction="sw" onMouseDown={(e) => handleResizeStart(e, 'sw')} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<TitleBar $isWindowFocus={isWindowFocus} style={isMac ? { paddingLeft: '70px' } : {}}>
|
||||
{action.icon && (
|
||||
<TitleBarIcon>
|
||||
@ -431,4 +485,90 @@ const OpacitySlider = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
/**
|
||||
* [Windows only] Custom resize handle styled component
|
||||
*
|
||||
* ELECTRON BUG WORKAROUND:
|
||||
* This component can be removed once https://github.com/electron/electron/issues/48554 is fixed.
|
||||
*/
|
||||
const ResizeHandle = styled.div<{ $direction: ResizeDirection }>`
|
||||
position: absolute;
|
||||
-webkit-app-region: no-drag;
|
||||
z-index: 10;
|
||||
|
||||
${({ $direction }) => {
|
||||
const edgeSize = '6px'
|
||||
const cornerSize = '12px'
|
||||
|
||||
switch ($direction) {
|
||||
case 'n':
|
||||
return `
|
||||
top: 0;
|
||||
left: ${cornerSize};
|
||||
right: ${cornerSize};
|
||||
height: ${edgeSize};
|
||||
cursor: ns-resize;
|
||||
`
|
||||
case 's':
|
||||
return `
|
||||
bottom: 0;
|
||||
left: ${cornerSize};
|
||||
right: ${cornerSize};
|
||||
height: ${edgeSize};
|
||||
cursor: ns-resize;
|
||||
`
|
||||
case 'e':
|
||||
return `
|
||||
right: 0;
|
||||
top: ${cornerSize};
|
||||
bottom: ${cornerSize};
|
||||
width: ${edgeSize};
|
||||
cursor: ew-resize;
|
||||
`
|
||||
case 'w':
|
||||
return `
|
||||
left: 0;
|
||||
top: ${cornerSize};
|
||||
bottom: ${cornerSize};
|
||||
width: ${edgeSize};
|
||||
cursor: ew-resize;
|
||||
`
|
||||
case 'ne':
|
||||
return `
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: ${cornerSize};
|
||||
height: ${cornerSize};
|
||||
cursor: nesw-resize;
|
||||
`
|
||||
case 'nw':
|
||||
return `
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: ${cornerSize};
|
||||
height: ${cornerSize};
|
||||
cursor: nwse-resize;
|
||||
`
|
||||
case 'se':
|
||||
return `
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: ${cornerSize};
|
||||
height: ${cornerSize};
|
||||
cursor: nwse-resize;
|
||||
`
|
||||
case 'sw':
|
||||
return `
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: ${cornerSize};
|
||||
height: ${cornerSize};
|
||||
cursor: nesw-resize;
|
||||
`
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}}
|
||||
`
|
||||
|
||||
export default SelectionActionApp
|
||||
|
||||
Loading…
Reference in New Issue
Block a user