diff --git a/app.go b/app.go index 9aad33a..a70031e 100644 --- a/app.go +++ b/app.go @@ -288,3 +288,28 @@ func (a *App) OpenOutputFile(path string) error { } return exec.Command("xdg-open", path).Start() } + +func (a *App) WindowMinimise() { + if a.ctx != nil { + runtime.WindowMinimise(a.ctx) + } +} + +func (a *App) WindowToggleMaximise() { + if a.ctx != nil { + runtime.WindowToggleMaximise(a.ctx) + } +} + +func (a *App) WindowClose() { + if a.ctx != nil { + runtime.Quit(a.ctx) + } +} + +func (a *App) WindowIsMaximised() bool { + if a.ctx != nil { + return runtime.WindowIsMaximised(a.ctx) + } + return false +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts index f528acd..e4a4b97 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -124,7 +124,7 @@ function renderShell() { const root = document.getElementById('app')!; const verText = state.appInfo?.version ? `v${state.appInfo.version}` : 'v2.0'; root.innerHTML = ` -
+
▶
ConvertGUI @@ -140,6 +140,17 @@ function renderShell() {
+
+ + + +
@@ -899,6 +910,7 @@ function bindGlobalEvents() { window.addEventListener('resize', () => { fitVideoStage(); + void syncMaximizedUI(); }); window.addEventListener('keydown', (e) => { @@ -1285,7 +1297,66 @@ function bindEvents() { if (state.donePath) void api?.OpenOutputFile(state.donePath); }); + // Window titlebar controls (frameless mode) + el<'button'>('winMinBtn')?.addEventListener('click', async () => { + if (window.runtime?.WindowMinimise) { + window.runtime.WindowMinimise(); + } else { + await api?.WindowMinimise(); + } + }); + + const toggleMaximize = async () => { + if (window.runtime?.WindowToggleMaximise) { + window.runtime.WindowToggleMaximise(); + } else { + await api?.WindowToggleMaximise(); + } + await syncMaximizedUI(); + }; + + el<'button'>('winMaxBtn')?.addEventListener('click', () => { + void toggleMaximize(); + }); + + el<'button'>('winCloseBtn')?.addEventListener('click', async () => { + if (window.runtime?.WindowClose) { + window.runtime.WindowClose(); + } else { + await api?.WindowClose(); + } + }); + + el<'header'>('topbar')?.addEventListener('dblclick', (e) => { + const target = e.target as HTMLElement; + if (target.closest('button, input, select, .file-pill')) return; + void toggleMaximize(); + }); + bindWheelControls(); + void syncMaximizedUI(); +} + +async function syncMaximizedUI() { + const api = appApi(); + let isMax = false; + try { + if (window.runtime?.WindowIsMaximised) { + isMax = await window.runtime.WindowIsMaximised(); + } else if (api?.WindowIsMaximised) { + isMax = await api.WindowIsMaximised(); + } + } catch { + // ignore + } + document.body.classList.toggle('maximized', isMax); + const maxBtn = el<'button'>('winMaxBtn'); + if (maxBtn) { + maxBtn.innerHTML = isMax + ? `` + : ``; + maxBtn.title = isMax ? 'Восстановить' : 'Развернуть'; + } } function setupWailsEvents() { diff --git a/frontend/src/style.css b/frontend/src/style.css index 5192573..939098c 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -32,22 +32,102 @@ body { overflow: hidden; } +/* Custom sleek dark scrollbars (eliminates default white Windows scrollbars) */ +::-webkit-scrollbar { + width: 7px; + height: 7px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + border-radius: 999px; + border: 1px solid transparent; + background-clip: padding-box; +} +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); + background-clip: padding-box; +} +* { + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.18) transparent; +} + #app { display: flex; flex-direction: column; height: 100vh; + box-sizing: border-box; + background: var(--bg); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + transition: border-radius 0.15s ease, border 0.15s ease; +} + +body.maximized #app { + border: none; + border-radius: 0; + box-shadow: none; } /* Top Bar */ .topbar { + --wails-draggable: drag; display: flex; align-items: center; justify-content: space-between; gap: 12px; - padding: 10px 16px; + padding: 6px 0 6px 14px; background: var(--panel); border-bottom: 1px solid var(--border); flex-shrink: 0; + user-select: none; +} + +.topbar button, +.topbar input, +.topbar select, +.topbar .file-pill, +.win-controls { + --wails-draggable: no-drag; +} + +/* Window Control Buttons (Minimize, Maximize, Close) */ +.win-controls { + display: flex; + align-items: stretch; + height: 36px; + margin-left: 4px; + --wails-draggable: no-drag; +} + +.win-btn { + width: 44px; + height: 100%; + border: none !important; + border-radius: 0 !important; + background: transparent !important; + color: var(--muted) !important; + display: flex !important; + align-items: center; + justify-content: center; + cursor: pointer; + padding: 0 !important; + transition: background 0.12s ease, color 0.12s ease; +} + +.win-btn:hover { + background: rgba(255, 255, 255, 0.08) !important; + color: var(--text) !important; +} + +.win-btn.close:hover { + background: #e81123 !important; + color: #fff !important; } .brand { diff --git a/frontend/src/wails.d.ts b/frontend/src/wails.d.ts index cf819ff..03f21a8 100644 --- a/frontend/src/wails.d.ts +++ b/frontend/src/wails.d.ts @@ -122,6 +122,10 @@ declare global { CancelConvert(): Promise; OpenInExplorer(path: string): Promise; OpenOutputFile(path: string): Promise; + WindowMinimise(): Promise; + WindowToggleMaximise(): Promise; + WindowClose(): Promise; + WindowIsMaximised(): Promise; }; }; }; @@ -129,6 +133,10 @@ declare global { EventsOn(eventName: string, callback: (...args: any[]) => void): () => void; OnFileDrop?(callback: (x: number, y: number, paths: string[]) => void, useDropTarget?: boolean): void; OnFileDropOff?(): void; + WindowMinimise?(): void; + WindowToggleMaximise?(): void; + WindowClose?(): void; + WindowIsMaximised?(): Promise; }; } } diff --git a/main.go b/main.go index 2caa323..c7a8981 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,8 @@ func main() { AssetServer: &assetserver.Options{ Assets: assets, }, - BackgroundColour: &options.RGBA{R: 13, G: 17, B: 23, A: 1}, + BackgroundColour: &options.RGBA{R: 11, G: 15, B: 23, A: 1}, + Frameless: true, OnStartup: app.startup, OnShutdown: app.shutdown, DragAndDrop: &options.DragAndDrop{ @@ -54,9 +55,15 @@ func main() { DisableWebViewDrop: false, }, Windows: &windows.Options{ - WebviewIsTransparent: false, - WindowIsTranslucent: false, - Theme: windows.Dark, + WebviewIsTransparent: false, + WindowIsTranslucent: false, + DisableFramelessWindowDecorations: false, + Theme: windows.Dark, + CustomTheme: &windows.ThemeSettings{ + DarkModeTitleBar: windows.RGB(17, 24, 39), + DarkModeTitleText: windows.RGB(241, 245, 249), + DarkModeBorder: windows.RGB(36, 48, 68), + }, }, Bind: []interface{}{ app,