From 7c3ee9cd6624785408316f338b46e0de0c6f3d0e Mon Sep 17 00:00:00 2001 From: PathGao <42336971+PathGao@users.noreply.github.com> Date: Sun, 8 Nov 2026 14:35:10 +0800 Subject: [PATCH] fix(titlebar): show the restore glyph on a maximized window On Windows the maximize button kept its single-square glyph and its "Maximize" label after the window was maximized (#1061). It now draws the restore glyph (two overlapping 8px squares, offset 2px, like the system caption button) and is labelled "Restore" while the window is maximized. The state was already right: every way of maximizing or restoring (the button, a title-bar double click, Win+Up, Aero Snap) ends in a WM_SIZE, which tao turns into the resize event the title bar listens to. Only the button never read it. --- scripts/windows.spec.ts | 69 +++++++++++++++++++++++++++++- src/lib/components/TitleBar.svelte | 15 ++++++- src/lib/locales/cs.ts | 1 + src/lib/locales/da.ts | 1 + src/lib/locales/de.ts | 1 + src/lib/locales/el.ts | 1 + src/lib/locales/es.ts | 1 + src/lib/locales/fi.ts | 1 + src/lib/locales/fr.ts | 1 + src/lib/locales/hu.ts | 1 + src/lib/locales/id.ts | 1 + src/lib/locales/it.ts | 1 + src/lib/locales/ja.ts | 1 + src/lib/locales/ko.ts | 1 + src/lib/locales/nl.ts | 1 + src/lib/locales/no.ts | 1 + src/lib/locales/pl.ts | 1 + src/lib/locales/pt-BR.ts | 1 + src/lib/locales/pt.ts | 1 + src/lib/locales/ro.ts | 1 + src/lib/locales/ru.ts | 1 + src/lib/locales/sk.ts | 1 + src/lib/locales/sv.ts | 1 + src/lib/locales/tr.ts | 1 + src/lib/locales/vi.ts | 1 + src/lib/locales/zh-CN.ts | 1 + src/lib/locales/zh-TW.ts | 1 + src/lib/utils/i18n.ts | 1 + 28 files changed, 107 insertions(+), 3 deletions(-) diff --git a/scripts/windows.spec.ts b/scripts/windows.spec.ts index 5fb6b2b4..e2d4ab4a 100644 --- a/scripts/windows.spec.ts +++ b/scripts/windows.spec.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { flushSync, mount, unmount } from 'svelte'; -import { afterAll, beforeAll, describe, test, vi } from 'vitest'; +import { afterAll, beforeAll, describe, onTestFinished, test, vi } from 'vitest'; import type { TranslationKey } from '../src/lib/utils/i18n.js'; import type { OpenFilePickerDeps } from '../src/lib/sessions/openFilePicker.svelte.js'; import type { OpenFileTarget } from '../src/lib/utils/openFileTarget.js'; @@ -1329,6 +1329,73 @@ describe('window organization', () => { }); }); +// #1061: on Windows the maximize button kept its maximize glyph and label on a +// maximized window. Every way of maximizing (the button, a title-bar double +// click, Win+Up, Aero Snap) ends in a WM_SIZE, which tao reports as a resize, +// so the button asks again on each resize. +describe('the maximize button follows the window state', () => { + let maximized = true; + let onResize: ((event: unknown) => void) | undefined; + // Unmounting unlistens, which goes through the event plugin's own global. + (window as any).__TAURI_EVENT_PLUGIN_INTERNALS__ ??= { unregisterListener() {} }; + useTauri({ + invoke: (cmd: string, args: any) => { + if (cmd === 'get_os_type') return Promise.resolve('windows'); + if (cmd === 'plugin:window|is_maximized') return Promise.resolve(maximized); + if (cmd === 'plugin:event|listen' && args.event === 'tauri://resize') onResize = args.handler; + return Promise.resolve(null); + }, + }); + + test('a maximized window offers Restore, and Maximize again once it is restored', async () => { + const { settings } = await import('../src/lib/stores/settings.svelte.js'); + const { default: TitleBar } = await import('../src/lib/components/TitleBar.svelte'); + const osType = settings.osType; + settings.osType = 'windows'; + const target = document.createElement('div'); + document.body.append(target); + const instance = mount(TitleBar, { + target, + props: { + isFocused: true, + isScrolled: false, + currentFile: '', + liveMode: false, + windowTitle: '', + showHome: true, + isEditing: false, + ontoggleHome: () => {}, + ononpenFileLocation: () => {}, + ontoggleLiveMode: () => {}, + onsetViewMode: () => {}, + theme: 'dark', + }, + intro: false, + }); + onTestFinished(() => { + unmount(instance); + target.remove(); + settings.osType = osType; + }); + const settle = async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + flushSync(); + }; + const button = () => target.querySelector('.maximize-btn')!; + + await settle(); + assert.equal(button().getAttribute('aria-label'), t('common.restore', 'en')); + assert.equal(button().querySelectorAll('rect').length, 1, 'restore draws the front square'); + assert.equal(button().querySelectorAll('path').length, 1, 'and the back square behind it'); + + maximized = false; + onResize!({ event: 'tauri://resize', id: 0, payload: { width: 800, height: 600 } }); + await settle(); + assert.equal(button().getAttribute('aria-label'), t('common.maximize', 'en')); + assert.equal(button().querySelectorAll('path').length, 0, 'maximize is a single square'); + }); +}); + // #1029: with "Ask me", a file opened from outside asks which window it goes to. // Rust has already sent a file that some window has open to that window, so // what reaches the picker is open nowhere, or open here. Every dependency is a diff --git a/src/lib/components/TitleBar.svelte b/src/lib/components/TitleBar.svelte index af7f573e..2d96d196 100644 --- a/src/lib/components/TitleBar.svelte +++ b/src/lib/components/TitleBar.svelte @@ -1007,8 +1007,19 @@ -