mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Desktop: Add fullscreen window mode (#3625)
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
import { isDesktop } from "/src/utility-functions/platform";
|
||||
|
||||
const appWindow = getContext<AppWindowState>("appWindow");
|
||||
const editor = getContext<Editor>("editor");
|
||||
@@ -19,7 +20,8 @@
|
||||
|
||||
let menuBarLayout: Layout = [];
|
||||
|
||||
$: showFullscreenButton = $appWindow.platform === "Web" || $fullscreen.windowFullscreen;
|
||||
$: showFullscreenButton = $appWindow.platform === "Web" || $fullscreen.windowFullscreen || (isDesktop() && $appWindow.fullscreen);
|
||||
$: isFullscreen = isDesktop() ? $appWindow.fullscreen : $fullscreen.windowFullscreen;
|
||||
// On Mac, the menu bar height needs to be scaled by the inverse of the UI scale to fit its native window buttons
|
||||
$: height = $appWindow.platform === "Mac" ? 28 * (1 / $appWindow.uiScale) : 28;
|
||||
|
||||
@@ -39,20 +41,23 @@
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
<!-- Window frame -->
|
||||
<LayoutRow class="window-frame" on:mousedown={() => editor.handle.appWindowDrag()} on:dblclick={() => editor.handle.appWindowMaximize()} />
|
||||
<LayoutRow class="window-frame" on:mousedown={() => !isFullscreen && editor.handle.appWindowDrag()} on:dblclick={() => !isFullscreen && editor.handle.appWindowMaximize()} />
|
||||
<!-- Window buttons -->
|
||||
<LayoutRow class="window-buttons" classes={{ fullscreen: showFullscreenButton, windows: $appWindow.platform === "Windows", linux: $appWindow.platform === "Linux" }}>
|
||||
{#if $appWindow.platform !== "Mac"}
|
||||
{#if showFullscreenButton}
|
||||
<LayoutRow
|
||||
tooltipLabel={$fullscreen.windowFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||
tooltipLabel={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||
tooltipDescription={$appWindow.platform === "Web" && $fullscreen.keyboardLockApiSupported
|
||||
? "While fullscreen, keyboard shortcuts normally reserved by the browser become available."
|
||||
: undefined}
|
||||
tooltipShortcut={$tooltip.f11Shortcut}
|
||||
on:click={() => ($fullscreen.windowFullscreen ? fullscreen.exitFullscreen : fullscreen.enterFullscreen)()}
|
||||
tooltipShortcut={$tooltip.fullscreenShortcut}
|
||||
on:click={() => {
|
||||
if (isDesktop()) editor.handle.appWindowFullscreen();
|
||||
else ($fullscreen.windowFullscreen ? fullscreen.exitFullscreen : fullscreen.enterFullscreen)();
|
||||
}}
|
||||
>
|
||||
<IconLabel icon={$fullscreen.windowFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
|
||||
<IconLabel icon={isFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
|
||||
</LayoutRow>
|
||||
{:else}
|
||||
<LayoutRow tooltipLabel="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
||||
@@ -115,7 +120,7 @@
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&.windows > .layout-row {
|
||||
&.windows:not(.fullscreen) > .layout-row {
|
||||
padding: 0 17px;
|
||||
|
||||
&:hover {
|
||||
@@ -127,7 +132,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.linux > .layout-row {
|
||||
&.linux:not(.fullscreen) > .layout-row {
|
||||
padding: 0 12px;
|
||||
|
||||
&:hover {
|
||||
@@ -136,4 +141,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
|
||||
</style>
|
||||
|
||||
@@ -84,28 +84,12 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
|
||||
// Cut, copy, and paste is handled in the backend on desktop
|
||||
if (isDesktop() && accelKey && ["KeyX", "KeyC", "KeyV"].includes(key)) return true;
|
||||
// But on web, we want to not redirect paste
|
||||
if (!isDesktop() && key === "KeyV" && accelKey) return false;
|
||||
|
||||
// Don't redirect user input from text entry into HTML elements
|
||||
if (targetIsTextField(e.target || undefined) && key !== "Escape" && !(accelKey && ["Enter", "NumpadEnter"].includes(key))) return false;
|
||||
|
||||
// Don't redirect paste in web
|
||||
if (key === "KeyV" && accelKey) return false;
|
||||
|
||||
// Don't redirect a fullscreen request
|
||||
if (key === "F11" && e.type === "keydown" && !e.repeat) {
|
||||
e.preventDefault();
|
||||
fullscreen.toggleFullscreen();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't redirect a reload request
|
||||
if (key === "F5") return false;
|
||||
if (key === "KeyR" && accelKey) return false;
|
||||
|
||||
// Don't redirect debugging tools
|
||||
if (["F12", "F8"].includes(key)) return false;
|
||||
if (["KeyC", "KeyI", "KeyJ"].includes(key) && accelKey && e.shiftKey) return false;
|
||||
|
||||
// Don't redirect tab or enter if not in canvas (to allow navigating elements)
|
||||
potentiallyRestoreCanvasFocus(e);
|
||||
if (!canvasFocused && !targetIsTextField(e.target || undefined) && ["Tab", "Enter", "NumpadEnter", "Space", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowUp"].includes(key)) return false;
|
||||
@@ -113,6 +97,24 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
// Don't redirect if a MenuList is open
|
||||
if (window.document.querySelector("[data-floating-menu-content]")) return false;
|
||||
|
||||
// Web-only keyboard shortcuts
|
||||
if (!isDesktop()) {
|
||||
// Don't redirect a fullscreen request, but process it immediately instead
|
||||
if (((operatingSystem() !== "Mac" && key === "F11") || (operatingSystem() === "Mac" && e.ctrlKey && e.metaKey && key === "KeyF")) && e.type === "keydown" && !e.repeat) {
|
||||
e.preventDefault();
|
||||
fullscreen.toggleFullscreen();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't redirect a reload request
|
||||
if (key === "F5") return false;
|
||||
if (key === "KeyR" && accelKey) return false;
|
||||
|
||||
// Don't redirect debugging tools
|
||||
if (["F12", "F8"].includes(key)) return false;
|
||||
if (["KeyC", "KeyI", "KeyJ"].includes(key) && accelKey && e.shiftKey) return false;
|
||||
}
|
||||
|
||||
// Redirect to the backend
|
||||
return true;
|
||||
}
|
||||
@@ -239,7 +241,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
}
|
||||
|
||||
function onMouseDown(e: MouseEvent) {
|
||||
// Block middle mouse button auto-scroll mode (the circlar gizmo that appears and allows quick scrolling by moving the cursor above or below it)
|
||||
// Block middle mouse button auto-scroll mode (the circular gizmo that appears and allows quick scrolling by moving the cursor above or below it)
|
||||
if (e.button === BUTTON_MIDDLE) e.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
@@ -107,9 +107,11 @@ export class SendUIMetadata extends JsMessage {
|
||||
readonly nodeTypes!: FrontendNodeType[];
|
||||
}
|
||||
|
||||
export class SendShortcutF11 extends JsMessage {
|
||||
export class SendShortcutFullscreen extends JsMessage {
|
||||
@Transform(({ value }: { value: ActionShortcut }) => value || undefined)
|
||||
readonly shortcut!: ActionShortcut | undefined;
|
||||
@Transform(({ value }: { value: ActionShortcut }) => value || undefined)
|
||||
readonly shortcutMac!: ActionShortcut | undefined;
|
||||
}
|
||||
|
||||
export class SendShortcutAltClick extends JsMessage {
|
||||
@@ -335,6 +337,8 @@ export class WindowPointerLockMove extends JsMessage {
|
||||
readonly y!: number;
|
||||
}
|
||||
|
||||
export class WindowFullscreen extends JsMessage {}
|
||||
|
||||
// Rust enum `Key`
|
||||
export type KeyRaw = string;
|
||||
// Serde converts a Rust `Key` enum variant into this format with both the `Key` variant name (called `RawKey` in TS) and the localized `label` for the key
|
||||
@@ -1666,7 +1670,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
DisplayEditableTextboxTransform,
|
||||
DisplayRemoveEditableTextbox,
|
||||
SendUIMetadata,
|
||||
SendShortcutF11,
|
||||
SendShortcutFullscreen,
|
||||
SendShortcutAltClick,
|
||||
SendShortcutShiftClick,
|
||||
TriggerAboutGraphiteLocalizedCommitDate,
|
||||
@@ -1734,6 +1738,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
UpdateMaximized,
|
||||
UpdateFullscreen,
|
||||
WindowPointerLockMove,
|
||||
WindowFullscreen,
|
||||
UpdatePropertiesPanelLayout,
|
||||
UpdatePropertiesPanelState,
|
||||
UpdateStatusBarHintsLayout,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
import { type Editor } from "@graphite/editor";
|
||||
import { WindowFullscreen } from "@graphite/messages";
|
||||
|
||||
export function createFullscreenState(_: Editor) {
|
||||
export function createFullscreenState(editor: Editor) {
|
||||
// Experimental Keyboard API: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/keyboard
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const keyboardLockApiSupported: Readonly<boolean> = "keyboard" in navigator && (navigator as any).keyboard && "lock" in (navigator as any).keyboard;
|
||||
@@ -50,6 +51,10 @@ export function createFullscreenState(_: Editor) {
|
||||
});
|
||||
}
|
||||
|
||||
editor.subscriptions.subscribeJsMessage(WindowFullscreen, () => {
|
||||
toggleFullscreen();
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
fullscreenModeChanged,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
import { type Editor } from "@graphite/editor";
|
||||
import { SendShortcutAltClick, SendShortcutF11, SendShortcutShiftClick, type ActionShortcut } from "@graphite/messages";
|
||||
import { SendShortcutAltClick, SendShortcutFullscreen, SendShortcutShiftClick, type ActionShortcut } from "@graphite/messages";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
|
||||
const SHOW_TOOLTIP_DELAY_MS = 500;
|
||||
|
||||
@@ -12,7 +13,7 @@ export function createTooltipState(editor: Editor) {
|
||||
position: { x: 0, y: 0 },
|
||||
shiftClickShortcut: undefined as ActionShortcut | undefined,
|
||||
altClickShortcut: undefined as ActionShortcut | undefined,
|
||||
f11Shortcut: undefined as ActionShortcut | undefined,
|
||||
fullscreenShortcut: undefined as ActionShortcut | undefined,
|
||||
});
|
||||
|
||||
let tooltipTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
@@ -63,9 +64,9 @@ export function createTooltipState(editor: Editor) {
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(SendShortcutF11, async (data) => {
|
||||
editor.subscriptions.subscribeJsMessage(SendShortcutFullscreen, async (data) => {
|
||||
update((state) => {
|
||||
state.f11Shortcut = data.shortcut;
|
||||
state.fullscreenShortcut = operatingSystem() === "Mac" ? data.shortcutMac : data.shortcut;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user