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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { WindowAnchor } from "./anchor.ts";
|
|
2
|
+
import { dockedBounds } from "./anchor.ts";
|
|
3
|
+
import { foregroundWindowHandle } from "./native.ts";
|
|
4
|
+
import type { AppWindow } from "#src/winui.js";
|
|
5
|
+
import { DisplayArea, DisplayAreaFallback } from "#src/winui.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Which display a window is docked to: the one the user is working on (the
|
|
9
|
+
* foreground window's), the one the window is on, or the primary one.
|
|
10
|
+
*/
|
|
11
|
+
export const DockDisplay = {
|
|
12
|
+
active: "active",
|
|
13
|
+
current: "current",
|
|
14
|
+
primary: "primary"
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/** One of `DockDisplay`'s values. */
|
|
18
|
+
export type DockDisplay = (typeof DockDisplay)[keyof typeof DockDisplay];
|
|
19
|
+
|
|
20
|
+
const NO_WINDOW = 0n;
|
|
21
|
+
|
|
22
|
+
function activeDisplayArea(appWindow: AppWindow) {
|
|
23
|
+
const foreground = foregroundWindowHandle();
|
|
24
|
+
const hasForeground = foreground !== NO_WINDOW;
|
|
25
|
+
const windowId = hasForeground ? { value: foreground } : appWindow.id;
|
|
26
|
+
return DisplayArea.getFromWindowId(windowId, DisplayAreaFallback.Nearest);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DISPLAY_FINDERS = {
|
|
30
|
+
[DockDisplay.active]: activeDisplayArea,
|
|
31
|
+
[DockDisplay.current]: appWindow => DisplayArea.getFromWindowId(appWindow.id, DisplayAreaFallback.Nearest),
|
|
32
|
+
[DockDisplay.primary]: () => DisplayArea.primary
|
|
33
|
+
} as const satisfies Record<DockDisplay, (appWindow: AppWindow) => DisplayArea>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The display area `display` names for `appWindow`. The `active` one is the
|
|
37
|
+
* display of the foreground window, so an overlay that shows up while the user
|
|
38
|
+
* works on a second monitor lands there.
|
|
39
|
+
*
|
|
40
|
+
* @param options - The window and which display to find.
|
|
41
|
+
* @returns The WinUI `DisplayArea`: `workArea` is its desktop minus the taskbar, in device pixels.
|
|
42
|
+
*/
|
|
43
|
+
export function displayAreaOf({ appWindow, display }: {
|
|
44
|
+
appWindow: AppWindow;
|
|
45
|
+
display: DockDisplay;
|
|
46
|
+
}) {
|
|
47
|
+
return DISPLAY_FINDERS[display](appWindow);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Where `dockWindow` puts a window. */
|
|
51
|
+
export interface DockOptions {
|
|
52
|
+
/** The corner of the work area the window sits against. */
|
|
53
|
+
anchor: WindowAnchor;
|
|
54
|
+
|
|
55
|
+
/** Device pixels between the window and the two edges at that corner; 0 by default. */
|
|
56
|
+
inset?: number;
|
|
57
|
+
|
|
58
|
+
/** The display to dock to; the active one by default. */
|
|
59
|
+
display?: DockDisplay;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Moves the window against the `anchor` corner of a display's work area (the
|
|
64
|
+
* display minus the taskbar), `inset` device pixels in from both edges, with
|
|
65
|
+
* one `AppWindow.MoveAndResize`. The window keeps its size.
|
|
66
|
+
*
|
|
67
|
+
* A window moved to a display with another scale first goes there at its old
|
|
68
|
+
* size and is docked once Windows has rescaled it, so the corner it ends on is
|
|
69
|
+
* the one asked for at the size it really has.
|
|
70
|
+
*
|
|
71
|
+
* @param options - The window, the corner, the inset in device pixels (0 by
|
|
72
|
+
* default) and the display (the active one by default).
|
|
73
|
+
*/
|
|
74
|
+
export function dockWindow({ appWindow, anchor, inset = 0, display = DockDisplay.active }: DockOptions & {
|
|
75
|
+
appWindow: AppWindow;
|
|
76
|
+
}) {
|
|
77
|
+
const area = displayAreaOf({
|
|
78
|
+
appWindow,
|
|
79
|
+
display
|
|
80
|
+
});
|
|
81
|
+
const { workArea } = area;
|
|
82
|
+
const currentArea = DisplayArea.getFromWindowId(appWindow.id, DisplayAreaFallback.Nearest);
|
|
83
|
+
const isOnOtherDisplay = currentArea.displayId.value !== area.displayId.value;
|
|
84
|
+
if (isOnOtherDisplay) {
|
|
85
|
+
appWindow.move({
|
|
86
|
+
x: workArea.x,
|
|
87
|
+
y: workArea.y
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
appWindow.moveAndResize(
|
|
92
|
+
dockedBounds({
|
|
93
|
+
area: workArea,
|
|
94
|
+
size: appWindow.size,
|
|
95
|
+
anchor,
|
|
96
|
+
inset
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
package/src/window/index.ts
CHANGED
|
@@ -2,6 +2,10 @@ export type { RunWindowOptions } from "./application.ts";
|
|
|
2
2
|
export { exitApplication, runWindow } from "./application.ts";
|
|
3
3
|
export type { AppWindow, WindowOptions } from "./create-window.ts";
|
|
4
4
|
export { Backdrop } from "./backdrop.ts";
|
|
5
|
+
export type { WindowBounds, WindowSize } from "./anchor.ts";
|
|
6
|
+
export { SizeToContent, WindowAnchor } from "./anchor.ts";
|
|
7
|
+
export type { DockOptions } from "./dock.ts";
|
|
8
|
+
export { displayAreaOf, DockDisplay, dockWindow } from "./dock.ts";
|
|
5
9
|
export type { CaptionButtonColors, WindowRegion } from "./chrome.ts";
|
|
6
10
|
export { TitleBarHeight, WindowPresentation, WindowTheme } from "./chrome.ts";
|
|
7
11
|
export { createWindow } from "./create-window.ts";
|
package/src/window/native.ts
CHANGED
|
@@ -58,6 +58,18 @@ export function scaleOf(hwnd: bigint) {
|
|
|
58
58
|
return dpi > 0 ? dpi / BASE_DPI : 1;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
const getForegroundWindow = bind({
|
|
62
|
+
dll: "user32.dll",
|
|
63
|
+
entryPoint: "GetForegroundWindow",
|
|
64
|
+
parameters: [],
|
|
65
|
+
returnType: "pointer"
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** The handle of the window the user is working in; 0 when no window has the foreground. */
|
|
69
|
+
export function foregroundWindowHandle() {
|
|
70
|
+
return addressOf(getForegroundWindow.invoke([]).returnValue);
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
/**
|
|
62
74
|
* Makes the window `owner` own the window `hwnd`: the owned window stays above
|
|
63
75
|
* its owner, and closes, minimizes and restores with it.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { write } from "#src/renderer/host/reflect.js";
|
|
2
|
+
import type { Window } from "#src/winui.js";
|
|
3
|
+
import { ICompositionSupportsSystemBackdrop, SystemCompositor } from "#src/winui.js";
|
|
4
|
+
|
|
5
|
+
// The Windows App SDK's own Microsoft.UI.Xaml.Media.TransparentTintBackdrop is
|
|
6
|
+
// not in the metadata of any release this package restores (2.2 and 2.3 have
|
|
7
|
+
// DesktopAcrylicBackdrop, MicaBackdrop and the abstract SystemBackdrop only),
|
|
8
|
+
// so it cannot be generated. What it does is what a window offers through
|
|
9
|
+
// ICompositionSupportsSystemBackdrop: a composition brush placed behind the
|
|
10
|
+
// content, one of the system's (Windows.UI.Composition) and not of the window's
|
|
11
|
+
// own compositor (Microsoft.UI.Composition), which is what the interface takes.
|
|
12
|
+
// A color brush with alpha 0 there makes the window show the desktop through
|
|
13
|
+
// every pixel the content leaves clear, with per-pixel alpha.
|
|
14
|
+
|
|
15
|
+
const CLEAR = {
|
|
16
|
+
a: 0,
|
|
17
|
+
r: 0,
|
|
18
|
+
g: 0,
|
|
19
|
+
b: 0
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Puts a fully transparent system backdrop behind `window`'s content: the
|
|
24
|
+
* desktop shows through wherever the content paints nothing, and a partly
|
|
25
|
+
* transparent pixel blends with it. The window must have no XAML backdrop of
|
|
26
|
+
* its own (`applyBackdrop` takes it away first).
|
|
27
|
+
*
|
|
28
|
+
* @returns What takes the backdrop away again.
|
|
29
|
+
*/
|
|
30
|
+
export function attachTransparentBackdrop(window: Window) {
|
|
31
|
+
const target = window.as(ICompositionSupportsSystemBackdrop);
|
|
32
|
+
target.systemBackdrop = new SystemCompositor().createColorBrush(CLEAR);
|
|
33
|
+
return () => {
|
|
34
|
+
// The binding types the property as non-null, but null is how the target
|
|
35
|
+
// lets a backdrop go.
|
|
36
|
+
write(target, "systemBackdrop", null);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { applyShownInSwitchers } from "./apply-chrome.ts";
|
|
2
|
+
import type { DockOptions } from "./dock.ts";
|
|
3
|
+
import { dockWindow } from "./dock.ts";
|
|
2
4
|
import { scaleOf } from "./native.ts";
|
|
3
5
|
import type { OpenFilePickerOptions, PickerOptions, SaveFilePickerOptions } from "./pickers.ts";
|
|
4
6
|
import { pickFile, pickFilePaths, pickFolder, saveFile } from "./pickers.ts";
|
|
7
|
+
import { WindowGeometry } from "#src/web-apis/window-geometry.js";
|
|
5
8
|
import type { Window } from "#src/winui.js";
|
|
6
9
|
import {
|
|
7
10
|
FileOpenPicker,
|
|
@@ -18,11 +21,18 @@ import {
|
|
|
18
21
|
export class WindowHandle {
|
|
19
22
|
/** The native WinUI window. */
|
|
20
23
|
readonly window: Window;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The window's size and place as the web has them: `outerWidth`,
|
|
27
|
+
* `screenX`, `resizeTo`, `moveBy` and the rest, in CSS pixels.
|
|
28
|
+
*/
|
|
29
|
+
readonly geometry: WindowGeometry;
|
|
21
30
|
private isClosed = false;
|
|
22
31
|
private closeGuard = () => false;
|
|
23
32
|
|
|
24
33
|
constructor(window: Window) {
|
|
25
34
|
this.window = window;
|
|
35
|
+
this.geometry = new WindowGeometry(window.appWindow, () => this.scale);
|
|
26
36
|
window.onClosed(() => {
|
|
27
37
|
this.isClosed = true;
|
|
28
38
|
});
|
|
@@ -59,6 +69,22 @@ export class WindowHandle {
|
|
|
59
69
|
});
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Moves the window against a corner of a display's work area (the display
|
|
74
|
+
* minus the taskbar), `inset` device pixels in from both edges: an overlay
|
|
75
|
+
* that sits bottom-right above the taskbar of the display the user is on.
|
|
76
|
+
* With `sizeToContent` and the same `anchor` on the window, the corner stays
|
|
77
|
+
* as the content grows.
|
|
78
|
+
*
|
|
79
|
+
* @param options - The corner, the inset in device pixels and the display (the active one by default).
|
|
80
|
+
*/
|
|
81
|
+
dock(options: DockOptions) {
|
|
82
|
+
dockWindow({
|
|
83
|
+
appWindow: this.appWindow,
|
|
84
|
+
...options
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
/** Shows the window and gives it focus. */
|
|
63
89
|
activate() {
|
|
64
90
|
this.window.activate();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SizeToContent, WindowAnchor, WindowBounds, WindowSize } from "./anchor.ts";
|
|
2
|
+
import { fittedBounds, SizeToContent as SizeToContentKind, WindowAnchor as WindowAnchorKind } from "./anchor.ts";
|
|
1
3
|
import {
|
|
2
4
|
applyCaptionButtonColors,
|
|
3
5
|
applyPresentation,
|
|
@@ -13,6 +15,7 @@ import { isBackgroundRun } from "./background-mode.ts";
|
|
|
13
15
|
import { backgroundPlacement, keepInBackground } from "./background.ts";
|
|
14
16
|
import type { CaptionButtonColors, CaptionButtonProperty, TitleBarHeight, WindowRegion } from "./chrome.ts";
|
|
15
17
|
import { TitleBarHeight as TitleBarHeightKind, WindowPresentation, WindowTheme } from "./chrome.ts";
|
|
18
|
+
import { toDevicePixels } from "./css-pixels.ts";
|
|
16
19
|
import {
|
|
17
20
|
clearWindowClip,
|
|
18
21
|
clipToRoundedRectangle,
|
|
@@ -28,22 +31,20 @@ import type { FrameworkElement } from "#src/winui.js";
|
|
|
28
31
|
import {
|
|
29
32
|
DisplayArea,
|
|
30
33
|
DisplayAreaFallback,
|
|
34
|
+
Grid,
|
|
31
35
|
NonClientRegionKind,
|
|
36
|
+
ScrollBarVisibility,
|
|
37
|
+
ScrollMode,
|
|
38
|
+
ScrollViewer,
|
|
32
39
|
Window,
|
|
33
40
|
WindowActivationState
|
|
34
41
|
} from "#src/winui.js";
|
|
35
42
|
|
|
43
|
+
export type { WindowBounds, WindowSize } from "./anchor.ts";
|
|
44
|
+
|
|
36
45
|
// What <Window> (components/hand-written/Window.svelte) does: a native window
|
|
37
46
|
// around the component's content, kept in line with its props.
|
|
38
47
|
|
|
39
|
-
/** The window's position and size, in device pixels, as AppWindow has them. */
|
|
40
|
-
export interface WindowBounds {
|
|
41
|
-
x: number;
|
|
42
|
-
y: number;
|
|
43
|
-
width: number;
|
|
44
|
-
height: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
48
|
/**
|
|
48
49
|
* Where the window opens, in device pixels; a size left out is the width or
|
|
49
50
|
* height prop's.
|
|
@@ -110,6 +111,23 @@ export interface WindowProps {
|
|
|
110
111
|
width?: number;
|
|
111
112
|
height?: number;
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Which sides of the window follow its content: the content is measured
|
|
116
|
+
* after every layout pass and the window is resized to it (frame included,
|
|
117
|
+
* within the size limits) with one `AppWindow.MoveAndResize`, so a growing
|
|
118
|
+
* content never flickers. A side that follows the content ignores the
|
|
119
|
+
* `width` or `height` prop and the user's resizing. Windowed presentation
|
|
120
|
+
* only; the default is `none`.
|
|
121
|
+
*/
|
|
122
|
+
sizeToContent?: SizeToContent | undefined;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The corner that stays where it is when the window is sized to its content
|
|
126
|
+
* (and when the scale changes the size): a window docked bottom-right whose
|
|
127
|
+
* content grows taller grows upward. The default is `topLeft`.
|
|
128
|
+
*/
|
|
129
|
+
anchor?: WindowAnchor | undefined;
|
|
130
|
+
|
|
113
131
|
/** The least the user can resize to, in device-independent pixels. */
|
|
114
132
|
minWidth?: number;
|
|
115
133
|
minHeight?: number;
|
|
@@ -228,12 +246,6 @@ export interface WindowProps {
|
|
|
228
246
|
onboundschanged?: (bounds: WindowBounds) => void;
|
|
229
247
|
}
|
|
230
248
|
|
|
231
|
-
/** A size in device pixels. */
|
|
232
|
-
export interface WindowSize {
|
|
233
|
-
width: number;
|
|
234
|
-
height: number;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
249
|
/** What `attachWindow` opens a window around, and how. */
|
|
238
250
|
export interface AttachOptions {
|
|
239
251
|
content: FrameworkElement;
|
|
@@ -244,11 +256,40 @@ export interface AttachOptions {
|
|
|
244
256
|
size?: WindowSize | undefined;
|
|
245
257
|
}
|
|
246
258
|
|
|
247
|
-
|
|
248
|
-
|
|
259
|
+
// A side of the window in device pixels: the prop's, unless the prop is left
|
|
260
|
+
// out or the content sets that side.
|
|
261
|
+
function sideOf({ value, current, isFitted, scale }: {
|
|
262
|
+
value: number | undefined;
|
|
263
|
+
current: number;
|
|
264
|
+
isFitted: boolean;
|
|
265
|
+
scale: number;
|
|
266
|
+
}) {
|
|
267
|
+
const isKept = value === undefined || isFitted;
|
|
268
|
+
if (isKept) {
|
|
269
|
+
return current;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return toDevicePixels({
|
|
273
|
+
value,
|
|
274
|
+
scale
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// A size limit in device pixels; `fallback` when the app names none.
|
|
279
|
+
function limitOf({ value, fallback, scale }: {
|
|
280
|
+
value: number | undefined;
|
|
281
|
+
fallback: number;
|
|
249
282
|
scale: number;
|
|
250
283
|
}) {
|
|
251
|
-
|
|
284
|
+
const isNamed = value !== undefined;
|
|
285
|
+
if (!isNamed) {
|
|
286
|
+
return fallback;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return toDevicePixels({
|
|
290
|
+
value,
|
|
291
|
+
scale
|
|
292
|
+
});
|
|
252
293
|
}
|
|
253
294
|
|
|
254
295
|
// A prop the app left out is undefined; a guard names the test.
|
|
@@ -281,6 +322,8 @@ class WindowHost {
|
|
|
281
322
|
private isClipApplied = false;
|
|
282
323
|
private isTopApplied = false;
|
|
283
324
|
private hasShown = false;
|
|
325
|
+
private isFitting = false;
|
|
326
|
+
private scroller: ScrollViewer | null = null;
|
|
284
327
|
private readonly isTakenOver: boolean;
|
|
285
328
|
private readonly openSize: WindowSize | undefined;
|
|
286
329
|
private appliedSize: {
|
|
@@ -314,6 +357,7 @@ class WindowHost {
|
|
|
314
357
|
|
|
315
358
|
this.handle.guardClose(() => this.isCloseRefused());
|
|
316
359
|
window.content = content;
|
|
360
|
+
this.keepInLine(() => this.syncContainer());
|
|
317
361
|
this.keepInLine(() => this.syncText());
|
|
318
362
|
this.keepInLine(() => this.syncBackdrop());
|
|
319
363
|
this.keepInLine(() => this.syncTheme());
|
|
@@ -330,6 +374,7 @@ class WindowHost {
|
|
|
330
374
|
}
|
|
331
375
|
|
|
332
376
|
$effect(() => this.syncSize());
|
|
377
|
+
$effect(() => this.fitToContent());
|
|
333
378
|
this.syncOwner();
|
|
334
379
|
this.listen();
|
|
335
380
|
this.watchScale();
|
|
@@ -379,6 +424,51 @@ class WindowHost {
|
|
|
379
424
|
$effect(sync);
|
|
380
425
|
}
|
|
381
426
|
|
|
427
|
+
// A measure offers an element the space it has and clamps what it asks for
|
|
428
|
+
// to that, so a root as large as the window could never ask for more or
|
|
429
|
+
// less than the window is. Inside a ScrollViewer that may scroll along a
|
|
430
|
+
// side the content sets, the root is offered all the room there (the
|
|
431
|
+
// scroll bars stay hidden), and what it asks for is what it needs.
|
|
432
|
+
private syncContainer() {
|
|
433
|
+
const { isWidthFitted, isHeightFitted } = this.fittedSides;
|
|
434
|
+
const isAnySideFitted = isWidthFitted || isHeightFitted;
|
|
435
|
+
const isFitted = isAnySideFitted && this.isWindowed;
|
|
436
|
+
const { window } = this.handle;
|
|
437
|
+
if (!isFitted) {
|
|
438
|
+
this.uncontain();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const scroller = this.scroller ?? new ScrollViewer();
|
|
443
|
+
scroller.horizontalScrollBarVisibility = isWidthFitted ? ScrollBarVisibility.Hidden : ScrollBarVisibility.Disabled;
|
|
444
|
+
scroller.horizontalScrollMode = isWidthFitted ? ScrollMode.Enabled : ScrollMode.Disabled;
|
|
445
|
+
scroller.verticalScrollBarVisibility = isHeightFitted ? ScrollBarVisibility.Hidden : ScrollBarVisibility.Disabled;
|
|
446
|
+
scroller.verticalScrollMode = isHeightFitted ? ScrollMode.Enabled : ScrollMode.Disabled;
|
|
447
|
+
const isContained = this.scroller !== null;
|
|
448
|
+
if (isContained) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
window.content = new Grid();
|
|
453
|
+
scroller.content = this.content;
|
|
454
|
+
window.content = scroller;
|
|
455
|
+
this.scroller = scroller;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private uncontain() {
|
|
459
|
+
const { scroller } = this;
|
|
460
|
+
const isContained = scroller !== null;
|
|
461
|
+
if (!isContained) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const { window } = this.handle;
|
|
466
|
+
window.content = new Grid();
|
|
467
|
+
scroller.content = null;
|
|
468
|
+
window.content = this.content;
|
|
469
|
+
this.scroller = null;
|
|
470
|
+
}
|
|
471
|
+
|
|
382
472
|
private syncText() {
|
|
383
473
|
const { window } = this.handle;
|
|
384
474
|
window.title = this.props.title ?? "";
|
|
@@ -540,28 +630,28 @@ class WindowHost {
|
|
|
540
630
|
}
|
|
541
631
|
|
|
542
632
|
if (isGiven(minWidth)) {
|
|
543
|
-
presenter.preferredMinimumWidth =
|
|
633
|
+
presenter.preferredMinimumWidth = toDevicePixels({
|
|
544
634
|
value: minWidth,
|
|
545
635
|
scale
|
|
546
636
|
});
|
|
547
637
|
}
|
|
548
638
|
|
|
549
639
|
if (isGiven(minHeight)) {
|
|
550
|
-
presenter.preferredMinimumHeight =
|
|
640
|
+
presenter.preferredMinimumHeight = toDevicePixels({
|
|
551
641
|
value: minHeight,
|
|
552
642
|
scale
|
|
553
643
|
});
|
|
554
644
|
}
|
|
555
645
|
|
|
556
646
|
if (isGiven(maxWidth)) {
|
|
557
|
-
presenter.preferredMaximumWidth =
|
|
647
|
+
presenter.preferredMaximumWidth = toDevicePixels({
|
|
558
648
|
value: maxWidth,
|
|
559
649
|
scale
|
|
560
650
|
});
|
|
561
651
|
}
|
|
562
652
|
|
|
563
653
|
if (isGiven(maxHeight)) {
|
|
564
|
-
presenter.preferredMaximumHeight =
|
|
654
|
+
presenter.preferredMaximumHeight = toDevicePixels({
|
|
565
655
|
value: maxHeight,
|
|
566
656
|
scale
|
|
567
657
|
});
|
|
@@ -665,7 +755,7 @@ class WindowHost {
|
|
|
665
755
|
hwnd,
|
|
666
756
|
width,
|
|
667
757
|
height,
|
|
668
|
-
radius:
|
|
758
|
+
radius: toDevicePixels({
|
|
669
759
|
value: cornerRadius,
|
|
670
760
|
scale: this.handle.scale
|
|
671
761
|
})
|
|
@@ -720,11 +810,11 @@ class WindowHost {
|
|
|
720
810
|
const { scale } = this.handle;
|
|
721
811
|
const current = this.openSize ?? appWindow.size;
|
|
722
812
|
const size = {
|
|
723
|
-
width: props.width === undefined ? current.width :
|
|
813
|
+
width: props.width === undefined ? current.width : toDevicePixels({
|
|
724
814
|
value: props.width,
|
|
725
815
|
scale
|
|
726
816
|
}),
|
|
727
|
-
height: props.height === undefined ? current.height :
|
|
817
|
+
height: props.height === undefined ? current.height : toDevicePixels({
|
|
728
818
|
value: props.height,
|
|
729
819
|
scale
|
|
730
820
|
})
|
|
@@ -769,7 +859,8 @@ class WindowHost {
|
|
|
769
859
|
}
|
|
770
860
|
|
|
771
861
|
// A new width or height resizes the window; the user's own resizing is not
|
|
772
|
-
// undone, since the props did not change.
|
|
862
|
+
// undone, since the props did not change. A side that follows the content
|
|
863
|
+
// (sizeToContent) is the content's to set.
|
|
773
864
|
private syncSize() {
|
|
774
865
|
const { width, height } = this.props;
|
|
775
866
|
const isChanged = width !== this.appliedSize.width || height !== this.appliedSize.height;
|
|
@@ -783,18 +874,108 @@ class WindowHost {
|
|
|
783
874
|
};
|
|
784
875
|
const { appWindow } = this.handle.window;
|
|
785
876
|
const { scale } = this.handle;
|
|
877
|
+
const { isWidthFitted, isHeightFitted } = this.fittedSides;
|
|
786
878
|
appWindow.resize({
|
|
787
|
-
width:
|
|
879
|
+
width: sideOf({
|
|
788
880
|
value: width,
|
|
881
|
+
current: appWindow.size.width,
|
|
882
|
+
isFitted: isWidthFitted,
|
|
789
883
|
scale
|
|
790
884
|
}),
|
|
791
|
-
height:
|
|
885
|
+
height: sideOf({
|
|
792
886
|
value: height,
|
|
887
|
+
current: appWindow.size.height,
|
|
888
|
+
isFitted: isHeightFitted,
|
|
793
889
|
scale
|
|
794
890
|
})
|
|
795
891
|
});
|
|
796
892
|
}
|
|
797
893
|
|
|
894
|
+
private get fittedSides() {
|
|
895
|
+
const mode = this.props.sizeToContent ?? SizeToContentKind.none;
|
|
896
|
+
return {
|
|
897
|
+
isWidthFitted: mode === SizeToContentKind.width || mode === SizeToContentKind.both,
|
|
898
|
+
isHeightFitted: mode === SizeToContentKind.height || mode === SizeToContentKind.both
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Sizes the window to what its content asks for, whenever the prop, the
|
|
903
|
+
// limits, the scale or a layout pass changes it: one MoveAndResize, so the
|
|
904
|
+
// anchor corner never moves between the size and the position.
|
|
905
|
+
private fitToContent() {
|
|
906
|
+
const mode = this.props.sizeToContent ?? SizeToContentKind.none;
|
|
907
|
+
const anchor = this.props.anchor ?? WindowAnchorKind.topLeft;
|
|
908
|
+
const { minWidth, minHeight, maxWidth, maxHeight } = this.props;
|
|
909
|
+
const isIdle = mode === SizeToContentKind.none || !this.isWindowed || this.isFitting;
|
|
910
|
+
if (isIdle) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const { desiredSize } = this.content;
|
|
915
|
+
const isMeasured = desiredSize.width > 0 && desiredSize.height > 0;
|
|
916
|
+
if (!isMeasured) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const { appWindow, scale } = this.handle;
|
|
921
|
+
const { size, position, clientSize } = appWindow;
|
|
922
|
+
const frame = {
|
|
923
|
+
width: size.width - clientSize.width,
|
|
924
|
+
height: size.height - clientSize.height
|
|
925
|
+
};
|
|
926
|
+
const target = fittedBounds({
|
|
927
|
+
bounds: {
|
|
928
|
+
...position,
|
|
929
|
+
...size
|
|
930
|
+
},
|
|
931
|
+
desired: {
|
|
932
|
+
width: toDevicePixels({
|
|
933
|
+
value: desiredSize.width,
|
|
934
|
+
scale
|
|
935
|
+
}) + frame.width,
|
|
936
|
+
height: toDevicePixels({
|
|
937
|
+
value: desiredSize.height,
|
|
938
|
+
scale
|
|
939
|
+
}) + frame.height
|
|
940
|
+
},
|
|
941
|
+
mode,
|
|
942
|
+
anchor,
|
|
943
|
+
limits: {
|
|
944
|
+
minWidth: limitOf({
|
|
945
|
+
value: minWidth,
|
|
946
|
+
fallback: 0,
|
|
947
|
+
scale
|
|
948
|
+
}),
|
|
949
|
+
minHeight: limitOf({
|
|
950
|
+
value: minHeight,
|
|
951
|
+
fallback: 0,
|
|
952
|
+
scale
|
|
953
|
+
}),
|
|
954
|
+
maxWidth: limitOf({
|
|
955
|
+
value: maxWidth,
|
|
956
|
+
fallback: Infinity,
|
|
957
|
+
scale
|
|
958
|
+
}),
|
|
959
|
+
maxHeight: limitOf({
|
|
960
|
+
value: maxHeight,
|
|
961
|
+
fallback: Infinity,
|
|
962
|
+
scale
|
|
963
|
+
})
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
const hasTarget = target !== null;
|
|
967
|
+
if (!hasTarget) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
this.isFitting = true;
|
|
972
|
+
try {
|
|
973
|
+
appWindow.moveAndResize(target);
|
|
974
|
+
} finally {
|
|
975
|
+
this.isFitting = false;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
798
979
|
private isCloseRefused() {
|
|
799
980
|
return this.props.onclosing?.() === true;
|
|
800
981
|
}
|
|
@@ -815,6 +996,7 @@ class WindowHost {
|
|
|
815
996
|
this.releaseBackdrop = null;
|
|
816
997
|
this.props.onclosed?.();
|
|
817
998
|
}),
|
|
999
|
+
this.content.onLayoutUpdated(() => this.fitToContent()),
|
|
818
1000
|
window.appWindow.onChanged((_sender, event) => {
|
|
819
1001
|
const isMoved = event.didPositionChange || event.didSizeChange;
|
|
820
1002
|
if (isMoved) {
|
|
@@ -836,6 +1018,7 @@ class WindowHost {
|
|
|
836
1018
|
this.syncLimits();
|
|
837
1019
|
this.syncRegions();
|
|
838
1020
|
this.syncShape();
|
|
1021
|
+
this.fitToContent();
|
|
839
1022
|
this.props.onscalechanged?.(scale);
|
|
840
1023
|
}
|
|
841
1024
|
});
|
package/src/winui.ts
CHANGED
|
@@ -8,6 +8,9 @@ export { Application } from "#winui/microsoft/ui/xaml/Application";
|
|
|
8
8
|
export { ColumnDefinition } from "#winui/microsoft/ui/xaml/controls/ColumnDefinition";
|
|
9
9
|
export { Grid } from "#winui/microsoft/ui/xaml/controls/Grid";
|
|
10
10
|
export { RowDefinition } from "#winui/microsoft/ui/xaml/controls/RowDefinition";
|
|
11
|
+
export { ScrollBarVisibility } from "#winui/microsoft/ui/xaml/controls/ScrollBarVisibility";
|
|
12
|
+
export { ScrollMode } from "#winui/microsoft/ui/xaml/controls/ScrollMode";
|
|
13
|
+
export { ScrollViewer } from "#winui/microsoft/ui/xaml/controls/ScrollViewer";
|
|
11
14
|
export { Run } from "#winui/microsoft/ui/xaml/documents/Run";
|
|
12
15
|
export { DispatcherQueue } from "#winui/microsoft/ui/dispatching/DispatcherQueue";
|
|
13
16
|
export { DesktopAcrylicBackdrop } from "#winui/microsoft/ui/xaml/media/DesktopAcrylicBackdrop";
|
|
@@ -18,6 +21,8 @@ export { DesktopAcrylicKind } from "#winui/microsoft/ui/composition/system-backd
|
|
|
18
21
|
export { SystemBackdropConfiguration } from "#winui/microsoft/ui/composition/system-backdrops/SystemBackdropConfiguration";
|
|
19
22
|
export { SystemBackdropTheme } from "#winui/microsoft/ui/composition/system-backdrops/SystemBackdropTheme";
|
|
20
23
|
export { ICompositionSupportsSystemBackdrop } from "#winui/microsoft/ui/composition/ICompositionSupportsSystemBackdrop";
|
|
24
|
+
export { Compositor as SystemCompositor } from "#winui/windows/ui/composition/Compositor";
|
|
25
|
+
export { CompositionColorBrush as SystemCompositionColorBrush } from "#winui/windows/ui/composition/CompositionColorBrush";
|
|
21
26
|
export { MicaKind } from "#winui/microsoft/ui/composition/system-backdrops/MicaKind";
|
|
22
27
|
export { AppWindow } from "#winui/microsoft/ui/windowing/AppWindow";
|
|
23
28
|
export { AppWindowPresenterKind } from "#winui/microsoft/ui/windowing/AppWindowPresenterKind";
|