winui-svelte 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +118 -6
- package/README.md +70 -90
- package/dist/bindings/.dynwinrt-managed +1 -1
- package/dist/components/convert.d.ts +3 -3
- package/dist/components/convert.d.ts.map +1 -1
- package/dist/components/fluent-glyph.d.ts +4 -0
- package/dist/components/fluent-glyph.d.ts.map +1 -1
- package/dist/components/generated/controls/FontIcon.svelte.d.ts +1 -1
- package/dist/components/generated/controls/FontIconSource.svelte.d.ts +1 -1
- package/dist/components/generated/controls/UIElement.svelte.d.ts +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/css/runtime/resources.d.ts +1 -1
- package/dist/dev/dev-window.d.ts +23 -0
- package/dist/dev/dev-window.d.ts.map +1 -0
- package/dist/dev/dev-window.js +42 -0
- package/dist/dev/dev-window.js.map +1 -0
- package/dist/dev/index.d.ts +3 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/start-dev.d.ts +47 -0
- package/dist/dev/start-dev.d.ts.map +1 -0
- package/dist/dev/start-dev.js +81 -0
- package/dist/dev/start-dev.js.map +1 -0
- package/dist/enums.d.ts +2 -0
- package/dist/enums.d.ts.map +1 -1
- package/dist/enums.js +2 -0
- package/dist/enums.js.map +1 -1
- package/dist/web-apis/window-geometry.d.ts +39 -0
- package/dist/web-apis/window-geometry.d.ts.map +1 -0
- package/dist/web-apis/window-geometry.js +95 -0
- package/dist/web-apis/window-geometry.js.map +1 -0
- package/dist/win2d/Canvas.svelte +1 -0
- package/dist/window/anchor.d.ts +92 -0
- package/dist/window/anchor.d.ts.map +1 -0
- package/dist/window/anchor.js +100 -0
- package/dist/window/anchor.js.map +1 -0
- package/dist/window/backdrop.d.ts +4 -1
- package/dist/window/backdrop.d.ts.map +1 -1
- package/dist/window/backdrop.js +10 -2
- package/dist/window/backdrop.js.map +1 -1
- package/dist/window/create-window.d.ts +1 -1
- package/dist/window/create-window.d.ts.map +1 -1
- package/dist/window/create-window.js +3 -1
- package/dist/window/create-window.js.map +1 -1
- package/dist/window/css-pixels.d.ts +14 -0
- package/dist/window/css-pixels.d.ts.map +1 -0
- package/dist/window/css-pixels.js +12 -0
- package/dist/window/css-pixels.js.map +1 -0
- package/dist/window/dock.d.ts +51 -0
- package/dist/window/dock.d.ts.map +1 -0
- package/dist/window/dock.js +69 -0
- package/dist/window/dock.js.map +1 -0
- package/dist/window/index.d.ts +4 -0
- package/dist/window/index.d.ts.map +1 -1
- package/dist/window/index.js +2 -0
- package/dist/window/index.js.map +1 -1
- package/dist/window/native.d.ts +2 -0
- package/dist/window/native.d.ts.map +1 -1
- package/dist/window/native.js +10 -0
- package/dist/window/native.js.map +1 -1
- package/dist/window/transparent-backdrop.d.ts +11 -0
- package/dist/window/transparent-backdrop.d.ts.map +1 -0
- package/dist/window/transparent-backdrop.js +35 -0
- package/dist/window/transparent-backdrop.js.map +1 -0
- package/dist/window/window-handle.d.ts +17 -0
- package/dist/window/window-handle.d.ts.map +1 -1
- package/dist/window/window-handle.js +23 -0
- package/dist/window/window-handle.js.map +1 -1
- package/dist/window/window-host.svelte.d.ts +17 -12
- package/dist/window/window-host.svelte.d.ts.map +1 -1
- package/dist/window/window-host.svelte.js +168 -14
- package/dist/window/window-host.svelte.js.map +1 -1
- package/dist/winui.d.ts +5 -0
- package/dist/winui.d.ts.map +1 -1
- package/dist/winui.js +5 -0
- package/dist/winui.js.map +1 -1
- package/package.json +25 -7
- package/src/components/convert.ts +3 -3
- package/src/components/fluent-glyph.ts +6 -0
- package/src/components/generated/controls/FontIcon.svelte.ts +1 -1
- package/src/components/generated/controls/FontIconSource.svelte.ts +1 -1
- package/src/components/generated/controls/UIElement.svelte.ts +1 -1
- package/src/components/index.ts +3 -1
- package/src/dev/dev-window.ts +63 -0
- package/src/dev/index.ts +2 -0
- package/src/dev/start-dev.ts +120 -0
- package/src/enums.ts +2 -0
- package/src/web-apis/window-geometry.ts +106 -0
- package/src/window/anchor.ts +156 -0
- package/src/window/backdrop.ts +11 -2
- package/src/window/create-window.ts +5 -1
- package/src/window/css-pixels.ts +18 -0
- package/src/window/dock.ts +99 -0
- package/src/window/index.ts +4 -0
- package/src/window/native.ts +12 -0
- package/src/window/transparent-backdrop.ts +38 -0
- package/src/window/window-handle.ts +26 -0
- package/src/window/window-host.svelte.ts +210 -27
- package/src/winui.ts +5 -0
- package/web-types.json +3203 -1507
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FluentGlyphCode, FluentGlyphValue } from "./fluent-glyph.ts";
|
|
2
2
|
import type { FontFamilyName } from "./font-family-name.ts";
|
|
3
3
|
import {
|
|
4
4
|
Application,
|
|
@@ -203,8 +203,8 @@ export type GeometryInput = string | Geometry;
|
|
|
203
203
|
/** A font family name or a font family. */
|
|
204
204
|
export type FontFamilyInput = FontFamilyName | (string & Record<never, never>) | FontFamily;
|
|
205
205
|
|
|
206
|
-
/** An icon glyph: a
|
|
207
|
-
export type GlyphInput =
|
|
206
|
+
/** An icon glyph: a Fluent name ("Save"), its code point (`FluentGlyph.Save`), code points to compose, or text. */
|
|
207
|
+
export type GlyphInput = FluentGlyphValue | (string & Record<never, never>) | readonly FluentGlyphCode[];
|
|
208
208
|
|
|
209
209
|
/** A URI string or a URI. */
|
|
210
210
|
export type UriInput = string | Uri;
|
|
@@ -1545,3 +1545,9 @@ export const FluentGlyph = {
|
|
|
1545
1545
|
|
|
1546
1546
|
/** The name of an icon of Segoe Fluent Icons. */
|
|
1547
1547
|
export type FluentGlyphName = keyof typeof FluentGlyph;
|
|
1548
|
+
|
|
1549
|
+
/** The code point of an icon of Segoe Fluent Icons: the only numbers a `glyph` takes. */
|
|
1550
|
+
export type FluentGlyphCode = (typeof FluentGlyph)[FluentGlyphName];
|
|
1551
|
+
|
|
1552
|
+
/** An icon of Segoe Fluent Icons by its name or its code point. */
|
|
1553
|
+
export type FluentGlyphValue = FluentGlyphName | FluentGlyphCode;
|
|
@@ -59,7 +59,7 @@ export interface FontIconProps extends IconElementProps {
|
|
|
59
59
|
/**
|
|
60
60
|
* Gets or sets the character code that identifies the icon glyph. The hexadecimal character code for the icon glyph.
|
|
61
61
|
*
|
|
62
|
-
* Accepts an icon glyph: a
|
|
62
|
+
* Accepts an icon glyph: a Fluent name ("Save"), its code point (`FluentGlyph.Save`), code points to compose, or text.
|
|
63
63
|
*
|
|
64
64
|
* @see https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.fonticon.glyph
|
|
65
65
|
*/
|
|
@@ -59,7 +59,7 @@ export interface FontIconSourceProps extends IconSourceProps {
|
|
|
59
59
|
/**
|
|
60
60
|
* Gets or sets the character code that identifies the icon glyph. The hexadecimal character code for the icon glyph.
|
|
61
61
|
*
|
|
62
|
-
* Accepts an icon glyph: a
|
|
62
|
+
* Accepts an icon glyph: a Fluent name ("Save"), its code point (`FluentGlyph.Save`), code points to compose, or text.
|
|
63
63
|
*
|
|
64
64
|
* @see https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.fonticonsource.glyph
|
|
65
65
|
*/
|
|
@@ -761,7 +761,7 @@ export interface UIElementProps {
|
|
|
761
761
|
/**
|
|
762
762
|
* Gets or sets the visibility of a UIElement. A UIElement that is not visible is not rendered and does not communicate its desired size to layout. A value of the enumeration. The default value is Visible.
|
|
763
763
|
*
|
|
764
|
-
* Also settable in CSS as `
|
|
764
|
+
* Also settable in CSS as `display`
|
|
765
765
|
*
|
|
766
766
|
* @see https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.uielement.visibility
|
|
767
767
|
*/
|
package/src/components/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type {
|
|
|
22
22
|
export { NavigationCacheMode, NavigationMode } from "./hand-written/navigation/navigation-stack.ts";
|
|
23
23
|
export { MoveDirection } from "./hand-written/items/reorder.ts";
|
|
24
24
|
export { FluentGlyph } from "./fluent-glyph.ts";
|
|
25
|
-
export type { FluentGlyphName } from "./fluent-glyph.ts";
|
|
25
|
+
export type { FluentGlyphCode, FluentGlyphName, FluentGlyphValue } from "./fluent-glyph.ts";
|
|
26
26
|
export type { FontFamilyName } from "./font-family-name.ts";
|
|
27
27
|
export { isColorText, ticksDate, ticksMilliseconds } from "./values.ts";
|
|
28
28
|
export type { ColorInput, ColorKeyword, ColorText, GridLengthText, HexColor } from "./values.ts";
|
|
@@ -42,5 +42,7 @@ export { goToState, installXaml, resourceDictionaryOf, xamlOf } from "./xaml.ts"
|
|
|
42
42
|
export type { XamlSource } from "./xaml-source.ts";
|
|
43
43
|
export { XamlKind } from "./xaml-source.ts";
|
|
44
44
|
export type { CaptionButtonColors, TitleBarHeight, WindowPresentation, WindowRegion, WindowTheme } from "#src/window/chrome.js";
|
|
45
|
+
export type { DockDisplay, DockOptions } from "#src/window/dock.js";
|
|
46
|
+
export type { SizeToContent, WindowAnchor } from "#src/window/anchor.js";
|
|
45
47
|
export type { WindowHandle } from "#src/window/window-handle.js";
|
|
46
48
|
export type { WindowBounds, WindowPlacement } from "#src/window/window-host.svelte.js";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Json } from "#src/messaging/json.js";
|
|
2
|
+
import type * as StateModule from "#src/state/index.js";
|
|
3
|
+
import { createWindowModuleRunner } from "#src/vite/runner.js";
|
|
4
|
+
import type { WindowOptions } from "#src/window/create-window.js";
|
|
5
|
+
import type * as WindowModule from "#src/window/index.js";
|
|
6
|
+
import { runOnUiThread } from "#src/window/thread.js";
|
|
7
|
+
import type { MessagePort } from "node:worker_threads";
|
|
8
|
+
|
|
9
|
+
/** The window's chrome `startDev` takes: what `createWindow` takes besides the content and the owner. */
|
|
10
|
+
export type DevWindowChrome = Omit<Extract<WindowOptions<object>, { component: unknown }>, "component" | "owner" | "props">;
|
|
11
|
+
|
|
12
|
+
/** What the dev window's thread is started with. */
|
|
13
|
+
export interface DevWindowData {
|
|
14
|
+
modules: MessagePort;
|
|
15
|
+
/** The host's end of the link, when the app has one: the component then gets a `channel` prop. */
|
|
16
|
+
link: MessagePort | null;
|
|
17
|
+
component: string;
|
|
18
|
+
props: Record<string, Json>;
|
|
19
|
+
window: DevWindowChrome;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The window's side of `startDev`: enters the apartment, loads the component
|
|
24
|
+
* through the module server (so a saved file updates the open window) and
|
|
25
|
+
* opens it in a window.
|
|
26
|
+
*/
|
|
27
|
+
export async function runDevWindow(data: DevWindowData) {
|
|
28
|
+
await runOnUiThread(async () => {
|
|
29
|
+
const { runner } = createWindowModuleRunner(data.modules);
|
|
30
|
+
const { createWindow, exitWhenHostEnds, runWindow } = await runner.import<typeof WindowModule>("winui-svelte/window");
|
|
31
|
+
const { default: component } = await runner.import(data.component);
|
|
32
|
+
const { createHostChannel, createMessagePortTransport } = await runner.import<typeof StateModule>(
|
|
33
|
+
"winui-svelte/state"
|
|
34
|
+
);
|
|
35
|
+
const { link } = data;
|
|
36
|
+
await runWindow({
|
|
37
|
+
onStart() {
|
|
38
|
+
const hasLink = link !== null;
|
|
39
|
+
if (!hasLink) {
|
|
40
|
+
createWindow({
|
|
41
|
+
...data.window,
|
|
42
|
+
component,
|
|
43
|
+
props: data.props
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const channel = createHostChannel({
|
|
49
|
+
transport: createMessagePortTransport({ port: link })
|
|
50
|
+
});
|
|
51
|
+
exitWhenHostEnds({ link: channel });
|
|
52
|
+
createWindow({
|
|
53
|
+
...data.window,
|
|
54
|
+
component,
|
|
55
|
+
props: {
|
|
56
|
+
...data.props,
|
|
57
|
+
channel
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
package/src/dev/index.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { DevWindowChrome, DevWindowData } from "./dev-window.ts";
|
|
2
|
+
import type { Json } from "#src/messaging/json.js";
|
|
3
|
+
import type { Transport } from "#src/state/host.js";
|
|
4
|
+
import { createStdioTransport, reserveStdout } from "#src/state/host.js";
|
|
5
|
+
import { startModuleServer } from "#src/vite/server.js";
|
|
6
|
+
import { initWindowsAppSdk } from "#src/window/bootstrap.js";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
import { MessageChannel, Worker } from "node:worker_threads";
|
|
10
|
+
|
|
11
|
+
/** What `startDev` opens and where its modules come from. */
|
|
12
|
+
export interface DevOptions {
|
|
13
|
+
/** The folder of the app's sources, which the dev server serves and watches. */
|
|
14
|
+
root: string;
|
|
15
|
+
|
|
16
|
+
/** The window's root component, as a path under `root` that starts with a slash (`"/App.svelte"`). */
|
|
17
|
+
component: string;
|
|
18
|
+
|
|
19
|
+
/** The component's props: JSON, since they cross to the window's thread. */
|
|
20
|
+
props?: Record<string, Json>;
|
|
21
|
+
|
|
22
|
+
/** The window's title, size, backdrop and the other chrome `createWindow` takes, besides its content and owner. */
|
|
23
|
+
window?: DevWindowChrome;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The app's link to the framework that starts it (a Tauri core, an Electron
|
|
27
|
+
* main process, a script): `"stdio"` when it started this process as a
|
|
28
|
+
* sidecar and speaks JSON lines on its pipes, or a transport of its own
|
|
29
|
+
* (`utilityProcess`'s parent port, say). The component then gets a
|
|
30
|
+
* `channel` prop (`hostState`, `channel.send`), and the window closes when
|
|
31
|
+
* the link ends.
|
|
32
|
+
*/
|
|
33
|
+
host?: "stdio" | Transport;
|
|
34
|
+
|
|
35
|
+
/** The app's Vite config, whose plugins should include `winuiSvelte()`; the plugin's defaults serve without one. */
|
|
36
|
+
configFile?: string;
|
|
37
|
+
|
|
38
|
+
/** Modules Node loads itself rather than Vite: an app's own native modules. */
|
|
39
|
+
isNative?: (file: string) => boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The window's thread is a worker that loads this package's dev-window module,
|
|
43
|
+
// from source (.ts) or from the published package (.js).
|
|
44
|
+
const WINDOW_MODULE = path.join(import.meta.dirname, `dev-window${path.extname(import.meta.filename)}`);
|
|
45
|
+
const WORKER_SOURCE = `const { workerData } = require("node:worker_threads");
|
|
46
|
+
import(workerData.moduleUrl).then(module => module.runDevWindow(workerData.data));`;
|
|
47
|
+
|
|
48
|
+
// The host's transport and the window's end of a MessageChannel, joined: what
|
|
49
|
+
// the host sends reaches the window, what the window sends reaches the host.
|
|
50
|
+
function linkTo(host: "stdio" | Transport) {
|
|
51
|
+
const isStdio = host === "stdio";
|
|
52
|
+
if (isStdio) {
|
|
53
|
+
reserveStdout();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const transport = isStdio ? createStdioTransport() : host;
|
|
57
|
+
const link = new MessageChannel();
|
|
58
|
+
transport.listen({
|
|
59
|
+
message: frame => link.port1.postMessage(frame),
|
|
60
|
+
malformed: reason => process.stderr.write(
|
|
61
|
+
`host: malformed message: ${reason}
|
|
62
|
+
`
|
|
63
|
+
),
|
|
64
|
+
closed: () => link.port1.close()
|
|
65
|
+
});
|
|
66
|
+
link.port1.on("message", frame => transport.send(frame));
|
|
67
|
+
return link;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Opens the app in a window and keeps it live: a saved `.svelte` or `.ts`
|
|
72
|
+
* file updates the open window without a restart. It starts the Windows App
|
|
73
|
+
* SDK, a Vite dev server for the app's modules and the window's thread, so the
|
|
74
|
+
* host is one call:
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { startDev } from "winui-svelte/dev";
|
|
78
|
+
*
|
|
79
|
+
* process.exitCode = await startDev({
|
|
80
|
+
* root: import.meta.dirname,
|
|
81
|
+
* component: "/App.svelte",
|
|
82
|
+
* window: { title: "My app" }
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @returns The exit code, once the window has closed and the dev server has stopped.
|
|
87
|
+
*/
|
|
88
|
+
export async function startDev({ component, props = {}, window = {}, host, ...serverOptions }: DevOptions) {
|
|
89
|
+
const link = host === undefined ? null : linkTo(host);
|
|
90
|
+
initWindowsAppSdk();
|
|
91
|
+
const modules = new MessageChannel();
|
|
92
|
+
const server = await startModuleServer({
|
|
93
|
+
...serverOptions,
|
|
94
|
+
port: modules.port1
|
|
95
|
+
});
|
|
96
|
+
const data: DevWindowData = {
|
|
97
|
+
modules: modules.port2,
|
|
98
|
+
link: link?.port2 ?? null,
|
|
99
|
+
component,
|
|
100
|
+
props,
|
|
101
|
+
window
|
|
102
|
+
};
|
|
103
|
+
const worker = new Worker(WORKER_SOURCE, {
|
|
104
|
+
eval: true,
|
|
105
|
+
workerData: {
|
|
106
|
+
moduleUrl: pathToFileURL(WINDOW_MODULE).href,
|
|
107
|
+
data
|
|
108
|
+
},
|
|
109
|
+
transferList: link === null ? [modules.port2] : [modules.port2, link.port2]
|
|
110
|
+
});
|
|
111
|
+
worker.on("error", error => {
|
|
112
|
+
process.stderr.write(`${error.stack ?? String(error)}\n`);
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
});
|
|
115
|
+
const code = await new Promise<number>(resolve => {
|
|
116
|
+
worker.on("exit", resolve);
|
|
117
|
+
});
|
|
118
|
+
await server.close();
|
|
119
|
+
return process.exitCode ?? code;
|
|
120
|
+
}
|
package/src/enums.ts
CHANGED
|
@@ -12,6 +12,8 @@ export { XamlKind } from "./components/xaml-source.ts";
|
|
|
12
12
|
export { FocusKind } from "./renderer/host/focus-element.ts";
|
|
13
13
|
export { TrayGesture } from "./tray/click-detector.ts";
|
|
14
14
|
export { TrayBalloonIcon } from "./tray/tray-core.ts";
|
|
15
|
+
export { SizeToContent, WindowAnchor } from "./window/anchor.ts";
|
|
15
16
|
export { Backdrop } from "./window/backdrop.ts";
|
|
16
17
|
export { TitleBarHeight, WindowPresentation, WindowTheme } from "./window/chrome.ts";
|
|
18
|
+
export { DockDisplay } from "./window/dock.ts";
|
|
17
19
|
export { PickerLocation } from "./window/pickers.ts";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { toCssPixels, toDevicePixels } from "#src/window/css-pixels.js";
|
|
2
|
+
import type { AppWindow } from "#src/winui.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One window's size and place as the web's `window` has them (`outerWidth`,
|
|
6
|
+
* `outerHeight`, `screenX`, `screenY`, `devicePixelRatio`, `resizeTo`,
|
|
7
|
+
* `resizeBy`, `moveTo`, `moveBy`), in CSS pixels, over its `AppWindow`. A
|
|
8
|
+
* process holds many windows, so there is no `window` global for it; a
|
|
9
|
+
* `WindowHandle` gives each window its own (`handle.geometry`).
|
|
10
|
+
*
|
|
11
|
+
* CSS pixels are WinUI's device-independent pixels, at the window's scale.
|
|
12
|
+
*/
|
|
13
|
+
export class WindowGeometry {
|
|
14
|
+
private readonly appWindow: AppWindow;
|
|
15
|
+
private readonly readScale: () => number;
|
|
16
|
+
|
|
17
|
+
/** Made by `WindowHandle`. */
|
|
18
|
+
constructor(appWindow: AppWindow, readScale: () => number) {
|
|
19
|
+
this.appWindow = appWindow;
|
|
20
|
+
this.readScale = readScale;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Device pixels per CSS pixel. */
|
|
24
|
+
// fallow-ignore-next-line unused-class-member
|
|
25
|
+
get devicePixelRatio() {
|
|
26
|
+
return this.readScale();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The window's width with its frame, in CSS pixels. */
|
|
30
|
+
get outerWidth() {
|
|
31
|
+
return toCssPixels({
|
|
32
|
+
value: this.appWindow.size.width,
|
|
33
|
+
scale: this.readScale()
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The window's height with its frame, in CSS pixels. */
|
|
38
|
+
get outerHeight() {
|
|
39
|
+
return toCssPixels({
|
|
40
|
+
value: this.appWindow.size.height,
|
|
41
|
+
scale: this.readScale()
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The window's left edge on the desktop, in CSS pixels at this window's scale. */
|
|
46
|
+
get screenX() {
|
|
47
|
+
return toCssPixels({
|
|
48
|
+
value: this.appWindow.position.x,
|
|
49
|
+
scale: this.readScale()
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The window's top edge on the desktop, in CSS pixels at this window's scale. */
|
|
54
|
+
get screenY() {
|
|
55
|
+
return toCssPixels({
|
|
56
|
+
value: this.appWindow.position.y,
|
|
57
|
+
scale: this.readScale()
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resizes the window (frame included) to `width` by `height` CSS pixels,
|
|
63
|
+
* keeping its top-left corner: `AppWindow.Resize`. A side that
|
|
64
|
+
* `sizeToContent` sets is set again by the content.
|
|
65
|
+
*/
|
|
66
|
+
resizeTo(width: number, height: number) {
|
|
67
|
+
const scale = this.readScale();
|
|
68
|
+
this.appWindow.resize({
|
|
69
|
+
width: toDevicePixels({
|
|
70
|
+
value: width,
|
|
71
|
+
scale
|
|
72
|
+
}),
|
|
73
|
+
height: toDevicePixels({
|
|
74
|
+
value: height,
|
|
75
|
+
scale
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Grows (or shrinks) the window by `width` and `height` CSS pixels. */
|
|
81
|
+
// fallow-ignore-next-line unused-class-member
|
|
82
|
+
resizeBy(width: number, height: number) {
|
|
83
|
+
this.resizeTo(this.outerWidth + width, this.outerHeight + height);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Puts the window's top-left corner at `x`, `y` CSS pixels on the desktop: `AppWindow.Move`. */
|
|
87
|
+
moveTo(x: number, y: number) {
|
|
88
|
+
const scale = this.readScale();
|
|
89
|
+
this.appWindow.move({
|
|
90
|
+
x: toDevicePixels({
|
|
91
|
+
value: x,
|
|
92
|
+
scale
|
|
93
|
+
}),
|
|
94
|
+
y: toDevicePixels({
|
|
95
|
+
value: y,
|
|
96
|
+
scale
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Moves the window by `x` and `y` CSS pixels. */
|
|
102
|
+
// fallow-ignore-next-line unused-class-member
|
|
103
|
+
moveBy(x: number, y: number) {
|
|
104
|
+
this.moveTo(this.screenX + x, this.screenY + y);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A corner of a window or of a display's work area. A window that changes
|
|
3
|
+
* size keeps its anchor corner where it is; a window docked to an anchor sits
|
|
4
|
+
* against that corner.
|
|
5
|
+
*/
|
|
6
|
+
export const WindowAnchor = {
|
|
7
|
+
topLeft: "topLeft",
|
|
8
|
+
topRight: "topRight",
|
|
9
|
+
bottomLeft: "bottomLeft",
|
|
10
|
+
bottomRight: "bottomRight"
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
/** One of `WindowAnchor`'s values. */
|
|
14
|
+
export type WindowAnchor = (typeof WindowAnchor)[keyof typeof WindowAnchor];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Which of a window's sides follow its content: the width, the height, both,
|
|
18
|
+
* or neither (the window keeps the size its props and its user give it).
|
|
19
|
+
*/
|
|
20
|
+
export const SizeToContent = {
|
|
21
|
+
none: "none",
|
|
22
|
+
width: "width",
|
|
23
|
+
height: "height",
|
|
24
|
+
both: "both"
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
/** One of `SizeToContent`'s values. */
|
|
28
|
+
export type SizeToContent = (typeof SizeToContent)[keyof typeof SizeToContent];
|
|
29
|
+
|
|
30
|
+
/** A position and a size in device pixels, as AppWindow and DisplayArea have them. */
|
|
31
|
+
export interface WindowBounds {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A size in device pixels. */
|
|
39
|
+
export interface WindowSize {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The least and the most a window may be, in device pixels; `Infinity` is no most. */
|
|
45
|
+
export interface WindowLimits {
|
|
46
|
+
minWidth: number;
|
|
47
|
+
minHeight: number;
|
|
48
|
+
maxWidth: number;
|
|
49
|
+
maxHeight: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ANCHOR_SIDES = {
|
|
53
|
+
[WindowAnchor.topLeft]: {
|
|
54
|
+
isRight: false,
|
|
55
|
+
isBottom: false
|
|
56
|
+
},
|
|
57
|
+
[WindowAnchor.topRight]: {
|
|
58
|
+
isRight: true,
|
|
59
|
+
isBottom: false
|
|
60
|
+
},
|
|
61
|
+
[WindowAnchor.bottomLeft]: {
|
|
62
|
+
isRight: false,
|
|
63
|
+
isBottom: true
|
|
64
|
+
},
|
|
65
|
+
[WindowAnchor.bottomRight]: {
|
|
66
|
+
isRight: true,
|
|
67
|
+
isBottom: true
|
|
68
|
+
}
|
|
69
|
+
} as const satisfies Record<WindowAnchor, {
|
|
70
|
+
isRight: boolean;
|
|
71
|
+
isBottom: boolean;
|
|
72
|
+
}>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The window `bounds` at `size` with its `anchor` corner where it was: a
|
|
76
|
+
* window anchored bottom-right that grows taller grows upward.
|
|
77
|
+
*/
|
|
78
|
+
export function anchoredBounds({ bounds, size, anchor }: {
|
|
79
|
+
bounds: WindowBounds;
|
|
80
|
+
size: WindowSize;
|
|
81
|
+
anchor: WindowAnchor;
|
|
82
|
+
}) {
|
|
83
|
+
const { isRight, isBottom } = ANCHOR_SIDES[anchor];
|
|
84
|
+
return {
|
|
85
|
+
x: isRight ? bounds.x + bounds.width - size.width : bounds.x,
|
|
86
|
+
y: isBottom ? bounds.y + bounds.height - size.height : bounds.y,
|
|
87
|
+
width: size.width,
|
|
88
|
+
height: size.height
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Where a window of `size` sits against the `anchor` corner of `area` (a
|
|
94
|
+
* display's work area), `inset` device pixels in from both of its edges.
|
|
95
|
+
*/
|
|
96
|
+
export function dockedBounds({ area, size, anchor, inset }: {
|
|
97
|
+
area: WindowBounds;
|
|
98
|
+
size: WindowSize;
|
|
99
|
+
anchor: WindowAnchor;
|
|
100
|
+
inset: number;
|
|
101
|
+
}) {
|
|
102
|
+
const { isRight, isBottom } = ANCHOR_SIDES[anchor];
|
|
103
|
+
return {
|
|
104
|
+
x: isRight ? area.x + area.width - size.width - inset : area.x + inset,
|
|
105
|
+
y: isBottom ? area.y + area.height - size.height - inset : area.y + inset,
|
|
106
|
+
width: size.width,
|
|
107
|
+
height: size.height
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function clamp({ value, least, most }: {
|
|
112
|
+
value: number;
|
|
113
|
+
least: number;
|
|
114
|
+
most: number;
|
|
115
|
+
}) {
|
|
116
|
+
return Math.min(Math.max(value, least), most);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The bounds a window sized to its content takes: the sides `mode` names go to
|
|
121
|
+
* `desired` (what the content asks for, frame included) within `limits`, and
|
|
122
|
+
* the `anchor` corner stays. null when the window is already that size, so a
|
|
123
|
+
* caller makes no call for it.
|
|
124
|
+
*/
|
|
125
|
+
export function fittedBounds({ bounds, desired, mode, anchor, limits }: {
|
|
126
|
+
bounds: WindowBounds;
|
|
127
|
+
desired: WindowSize;
|
|
128
|
+
mode: SizeToContent;
|
|
129
|
+
anchor: WindowAnchor;
|
|
130
|
+
limits: WindowLimits;
|
|
131
|
+
}) {
|
|
132
|
+
const isWidthFitted = mode === SizeToContent.width || mode === SizeToContent.both;
|
|
133
|
+
const isHeightFitted = mode === SizeToContent.height || mode === SizeToContent.both;
|
|
134
|
+
const size = {
|
|
135
|
+
width: isWidthFitted ? clamp({
|
|
136
|
+
value: desired.width,
|
|
137
|
+
least: limits.minWidth,
|
|
138
|
+
most: limits.maxWidth
|
|
139
|
+
}) : bounds.width,
|
|
140
|
+
height: isHeightFitted ? clamp({
|
|
141
|
+
value: desired.height,
|
|
142
|
+
least: limits.minHeight,
|
|
143
|
+
most: limits.maxHeight
|
|
144
|
+
}) : bounds.height
|
|
145
|
+
};
|
|
146
|
+
const isUnchanged = size.width === bounds.width && size.height === bounds.height;
|
|
147
|
+
if (isUnchanged) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return anchoredBounds({
|
|
152
|
+
bounds,
|
|
153
|
+
size,
|
|
154
|
+
anchor
|
|
155
|
+
});
|
|
156
|
+
}
|
package/src/window/backdrop.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AcrylicVariant, attachAcrylic } from "./acrylic-controller.ts";
|
|
2
|
+
import { attachTransparentBackdrop } from "./transparent-backdrop.ts";
|
|
2
3
|
import { write } from "#src/renderer/host/reflect.js";
|
|
3
4
|
import type { FrameworkElement, Window } from "#src/winui.js";
|
|
4
5
|
import { DesktopAcrylicBackdrop, MicaBackdrop, MicaKind } from "#src/winui.js";
|
|
@@ -7,7 +8,9 @@ import { DesktopAcrylicBackdrop, MicaBackdrop, MicaKind } from "#src/winui.js";
|
|
|
7
8
|
* The material behind a window's content. Acrylic blurs and tints what is
|
|
8
9
|
* behind the window (`acrylicBase` and `acrylicThin` are its other two
|
|
9
10
|
* variants, made by the system backdrop controller); Mica tints with the desktop wallpaper (Mica Alt more
|
|
10
|
-
* strongly, as a tabbed title bar has it);
|
|
11
|
+
* strongly, as a tabbed title bar has it); transparent shows the desktop
|
|
12
|
+
* through every pixel the content leaves clear (with per-pixel alpha; pair it
|
|
13
|
+
* with `isBorderless`); none leaves the theme's solid color.
|
|
11
14
|
*/
|
|
12
15
|
export const Backdrop = {
|
|
13
16
|
acrylic: "acrylic",
|
|
@@ -15,7 +18,8 @@ export const Backdrop = {
|
|
|
15
18
|
acrylicThin: "acrylicThin",
|
|
16
19
|
mica: "mica",
|
|
17
20
|
micaAlt: "micaAlt",
|
|
18
|
-
none: "none"
|
|
21
|
+
none: "none",
|
|
22
|
+
transparent: "transparent"
|
|
19
23
|
} as const;
|
|
20
24
|
|
|
21
25
|
/** One of `Backdrop`'s values. */
|
|
@@ -64,6 +68,7 @@ export function applyBackdrop({ window, content, backdrop }: {
|
|
|
64
68
|
}) {
|
|
65
69
|
const variant = controllerVariants.get(backdrop);
|
|
66
70
|
const isControlled = variant !== undefined;
|
|
71
|
+
const isTransparent = backdrop === Backdrop.transparent;
|
|
67
72
|
const systemBackdrop = isControlled ? null : systemBackdropFor(backdrop);
|
|
68
73
|
const isRemoved = systemBackdrop === null;
|
|
69
74
|
if (isRemoved) {
|
|
@@ -74,6 +79,10 @@ export function applyBackdrop({ window, content, backdrop }: {
|
|
|
74
79
|
window.systemBackdrop = systemBackdrop;
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
if (isTransparent) {
|
|
83
|
+
return attachTransparentBackdrop(window);
|
|
84
|
+
}
|
|
85
|
+
|
|
77
86
|
if (isControlled) {
|
|
78
87
|
return attachAcrylic({
|
|
79
88
|
window,
|
|
@@ -21,6 +21,8 @@ interface WindowChrome extends Pick<
|
|
|
21
21
|
| "isShownInSwitchers"
|
|
22
22
|
| "isToolWindow"
|
|
23
23
|
| "owner"
|
|
24
|
+
| "anchor"
|
|
25
|
+
| "sizeToContent"
|
|
24
26
|
| "title"
|
|
25
27
|
> {
|
|
26
28
|
/** In device pixels. */
|
|
@@ -123,7 +125,9 @@ export function createWindow<Props extends object>(options: WindowOptions<Props>
|
|
|
123
125
|
isExcludedFromCapture: options.isExcludedFromCapture,
|
|
124
126
|
isHidden: options.isHidden,
|
|
125
127
|
isShownInSwitchers: options.isShownInSwitchers,
|
|
126
|
-
isToolWindow: options.isToolWindow
|
|
128
|
+
isToolWindow: options.isToolWindow,
|
|
129
|
+
sizeToContent: options.sizeToContent,
|
|
130
|
+
anchor: options.anchor
|
|
127
131
|
},
|
|
128
132
|
titleBar: () => null,
|
|
129
133
|
size: options.size
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device-independent pixels (what the web calls CSS pixels) as device pixels,
|
|
3
|
+
* at `scale` device pixels per device-independent pixel.
|
|
4
|
+
*/
|
|
5
|
+
export function toDevicePixels({ value, scale }: {
|
|
6
|
+
value: number;
|
|
7
|
+
scale: number;
|
|
8
|
+
}) {
|
|
9
|
+
return Math.round(value * scale);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Device pixels as device-independent (CSS) pixels, at `scale` device pixels per one. */
|
|
13
|
+
export function toCssPixels({ value, scale }: {
|
|
14
|
+
value: number;
|
|
15
|
+
scale: number;
|
|
16
|
+
}) {
|
|
17
|
+
return Math.round(value / scale);
|
|
18
|
+
}
|