diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index a61c708a..8ca2f306 100755 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -18,6 +18,7 @@ "autostart:allow-disable", "autostart:allow-is-enabled", "core:window:allow-set-theme", - "notification:default" + "notification:default", + "core:webview:allow-set-webview-zoom" ] } diff --git a/src/hooks/useZoomControls.ts b/src/hooks/useZoomControls.ts new file mode 100644 index 00000000..36c42c4d --- /dev/null +++ b/src/hooks/useZoomControls.ts @@ -0,0 +1,93 @@ +import { useEffect, useState, useCallback } from 'react'; +import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; + +// Константы для управления масштабом +const ZOOM_STEP = 0.1; +const ZOOM_WHEEL_STEP = 0.05; +const MIN_ZOOM = 0.5; // 50% +const MAX_ZOOM = 2.0; // 200% + +export const useZoomControls = () => { + const [zoomLevel, setZoomLevel] = useState(1.0); + const appWindow = WebviewWindow.getCurrent(); + + useEffect(() => { + const setInitialZoom = async () => { + // 1. Получаем и физический размер, и коэффициент масштабирования + const size = await appWindow.innerSize(); + const scaleFactor = await appWindow.scaleFactor(); + + // 2. Вычисляем логическую ширину + const logicalWidth = size.width / scaleFactor; + + let initialZoom = 1.0; + + console.log(`Physical width: ${size.width}, Scale Factor: ${scaleFactor}, Logical width: ${logicalWidth}`); + + // 3. Используем логическую ширину для принятия решения + if (logicalWidth < 1300) { + initialZoom = 1.0; + } else if (logicalWidth > 2000) { + initialZoom = 2.0; + } + + await appWindow.setZoom(initialZoom); + setZoomLevel(initialZoom); + }; + + setInitialZoom(); + }, []); + + const handleZoom = useCallback((delta: number, isReset = false) => { + setZoomLevel(currentZoom => { + const newZoom = isReset ? 1.0 : currentZoom + delta; + const clampedZoom = Math.max(MIN_ZOOM, Math.min(newZoom, MAX_ZOOM)); + const roundedZoom = Math.round(clampedZoom * 100) / 100; + + appWindow.setZoom(roundedZoom); + const newStrokeWidth = 2 / roundedZoom; + document.documentElement.style.setProperty('--icon-stroke-width', newStrokeWidth.toString()); + return roundedZoom; + }); + }, [appWindow]); + + useEffect(() => { + const handleWheel = (event: WheelEvent) => { + if (event.ctrlKey || event.metaKey) { + event.preventDefault(); + const delta = event.deltaY > 0 ? -ZOOM_WHEEL_STEP : ZOOM_WHEEL_STEP; + handleZoom(delta); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.ctrlKey || event.metaKey) { + switch (event.code) { + case 'Equal': + case 'NumpadAdd': + event.preventDefault(); + handleZoom(ZOOM_STEP); + break; + case 'Minus': + case 'NumpadSubtract': + event.preventDefault(); + handleZoom(-ZOOM_STEP); + break; + case 'Digit0': + case 'Numpad0': + event.preventDefault(); + handleZoom(0, true); + break; + } + } + }; + + window.addEventListener('wheel', handleWheel, { passive: false }); + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('wheel', handleWheel); + window.removeEventListener('keydown', handleKeyDown); + }; + }, [handleZoom]); +}; diff --git a/src/index.css b/src/index.css index 4a1fa7c0..b265e9e6 100644 --- a/src/index.css +++ b/src/index.css @@ -125,3 +125,7 @@ /* h-full уже применен выше к body */ } } + +svg { + stroke-width: var(--icon-stroke-width, 2); +} diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index 410d3e84..69ff2abe 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -24,6 +24,7 @@ import { showNotice } from "@/services/noticeService"; import { NoticeManager } from "@/components/base/NoticeManager"; import { SidebarProvider, useSidebar } from "@/components/ui/sidebar"; import { AppSidebar } from "@/components/layout/sidebar"; +import {useZoomControls} from "@/hooks/useZoomControls"; const appWindow = getCurrentWebviewWindow(); export let portableFlag = false; @@ -143,6 +144,7 @@ const handleNoticeMessage = ( const Layout = () => { const mode = useThemeMode(); + useZoomControls(); const isDark = mode === "light" ? false : true; const { t } = useTranslation(); useCustomTheme();