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_ActionWindowClose = 'selection:action-window-close',
|
||||||
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
||||||
Selection_ActionWindowPin = 'selection:action-window-pin',
|
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_ProcessAction = 'selection:process-action',
|
||||||
Selection_UpdateActionData = 'selection:update-action-data',
|
Selection_UpdateActionData = 'selection:update-action-data',
|
||||||
|
|
||||||
|
|||||||
@ -1393,6 +1393,50 @@ export class SelectionService {
|
|||||||
actionWindow.setAlwaysOnTop(isPinned)
|
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
|
* Update trigger mode behavior
|
||||||
* Switches between selection-based and alt-key based triggering
|
* 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
|
this.isIpcHandlerRegistered = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -456,7 +456,10 @@ const api = {
|
|||||||
ipcRenderer.invoke(IpcChannel.Selection_ProcessAction, actionItem, isFullScreen),
|
ipcRenderer.invoke(IpcChannel.Selection_ProcessAction, actionItem, isFullScreen),
|
||||||
closeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowClose),
|
closeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowClose),
|
||||||
minimizeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowMinimize),
|
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: {
|
agentTools: {
|
||||||
respondToPermission: (payload: {
|
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 { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
@ -8,11 +8,14 @@ import { IpcChannel } from '@shared/IpcChannel'
|
|||||||
import { Button, Slider, Tooltip } from 'antd'
|
import { Button, Slider, Tooltip } from 'antd'
|
||||||
import { Droplet, Minus, Pin, X } from 'lucide-react'
|
import { Droplet, Minus, Pin, X } from 'lucide-react'
|
||||||
import { DynamicIcon } from 'lucide-react/dynamic'
|
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 { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
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 ActionGeneral from './components/ActionGeneral'
|
||||||
import ActionTranslate from './components/ActionTranslate'
|
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
|
//we don't need to render the component if action is not set
|
||||||
if (!action) return null
|
if (!action) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WindowFrame $opacity={opacity / 100}>
|
<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' } : {}}>
|
<TitleBar $isWindowFocus={isWindowFocus} style={isMac ? { paddingLeft: '70px' } : {}}>
|
||||||
{action.icon && (
|
{action.icon && (
|
||||||
<TitleBarIcon>
|
<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
|
export default SelectionActionApp
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user