Desktop: Add the transparent viewport hole punch and hook up window button plumbing (#2949)

This commit is contained in:
Keavon Chambers
2025-07-28 02:13:32 -07:00
committed by GitHub
parent d9de1a1c73
commit 66cd7a3b76
26 changed files with 300 additions and 77 deletions
@@ -1,18 +1,17 @@
<script lang="ts" context="module">
export type ApplicationPlatform = "Windows" | "Mac" | "Linux" | "Web";
</script>
<script lang="ts">
import type { AppWindowPlatform } from "@graphite/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import StatusBar from "@graphite/components/window/status-bar/StatusBar.svelte";
import TitleBar from "@graphite/components/window/title-bar/TitleBar.svelte";
import Workspace from "@graphite/components/window/workspace/Workspace.svelte";
let platform: ApplicationPlatform = "Web";
let maximized: true;
export let platform: AppWindowPlatform;
export let maximized: boolean;
export let viewportHolePunch: boolean;
</script>
<LayoutCol class="main-window">
<LayoutCol class="main-window" classes={{ "viewport-hole-punch": viewportHolePunch }}>
<TitleBar {platform} {maximized} />
<Workspace />
@@ -1,12 +1,8 @@
<script lang="ts" context="module">
export type Platform = "Windows" | "Mac" | "Linux" | "Web";
</script>
<script lang="ts">
import { getContext, onMount } from "svelte";
import type { Editor } from "@graphite/editor";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/messages";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, type AppWindowPlatform, UpdateMenuBarLayout } from "@graphite/messages";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import { platformIsMac } from "@graphite/utility-functions/platform";
@@ -17,7 +13,7 @@
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte";
export let platform: Platform;
export let platform: AppWindowPlatform;
export let maximized: boolean;
const editor = getContext<Editor>("editor");
@@ -73,7 +69,7 @@
<!-- Menu bar (or on Mac: window buttons) -->
<LayoutRow class="left">
{#if platform === "Mac"}
<WindowButtonsMac {maximized} />
<WindowButtonsMac />
{:else}
{#each entries as entry}
<TextButton label={entry.label} icon={entry.icon} menuListChildren={entry.children} action={entry.action} flush={true} />
@@ -1,13 +1,17 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Editor } from "@graphite/editor";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
export let maximized = false;
const editor = getContext<Editor>("editor");
</script>
<LayoutRow class="window-buttons-mac">
<div class="close" title="Close" />
<div class="minimize" title={maximized ? "Minimize" : "Maximize"} />
<div class="zoom" title="Zoom" />
<div class="close" on:click={() => editor.handle.appWindowClose()} />
<div class="minimize" on:click={() => editor.handle.appWindowMinimize()} />
<div class="zoom" on:click={() => editor.handle.appWindowMaximize()} />
</LayoutRow>
<style lang="scss" global>
@@ -1,23 +1,32 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Editor } from "@graphite/editor";
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
const editor = getContext<Editor>("editor");
const fullscreen = getContext<FullscreenState>("fullscreen");
$: requestFullscreenHotkeys = fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked;
async function handleClick() {
async function handleClick(e: MouseEvent) {
// TODO: Remove this debugging option to switch from web to desktop window buttons
if (e.ctrlKey && e.shiftKey && e.altKey) {
editor.handle.appWindowMinimize();
editor.handle.appWindowMinimize();
return;
}
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
else fullscreen.enterFullscreen();
}
</script>
<LayoutRow class="window-buttons-web" on:click={() => handleClick()} tooltip={($fullscreen.windowFullscreen ? "Exit" : "Enter") + " Fullscreen (F11)"}>
<LayoutRow class="window-buttons-web" on:click={handleClick} tooltip={$fullscreen.windowFullscreen ? "Exit Fullscreen (F11)" : "Enter Fullscreen (F11)"}>
{#if requestFullscreenHotkeys}
<TextLabel italic={true}>Go fullscreen to access all hotkeys</TextLabel>
{/if}
@@ -1,23 +1,23 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Editor } from "@graphite/editor";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
export let maximized = false;
export let maximized;
const editor = getContext<Editor>("editor");
</script>
<LayoutRow class="window-button windows minimize" tooltip="Minimize">
<LayoutRow class="window-button windows" tooltip="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
<IconLabel icon={"WindowButtonWinMinimize"} />
</LayoutRow>
{#if !maximized}
<LayoutRow class="window-button windows maximize" tooltip="Maximize">
<IconLabel icon={"WindowButtonWinMaximize"} />
</LayoutRow>
{:else}
<LayoutRow class="window-button windows restore-down" tooltip="Restore Down">
<IconLabel icon={"WindowButtonWinRestoreDown"} />
</LayoutRow>
{/if}
<LayoutRow class="window-button windows close" tooltip="Close">
<LayoutRow class="window-button windows" tooltip={maximized ? "Restore Down" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
<IconLabel icon={maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
</LayoutRow>
<LayoutRow class="window-button windows" tooltip="Close" on:click={() => editor.handle.appWindowClose()}>
<IconLabel icon={"WindowButtonWinClose"} />
</LayoutRow>
@@ -39,7 +39,7 @@
}
}
&.close:hover {
&:last-of-type:hover {
background: #e81123;
}
}
@@ -42,6 +42,13 @@
export let clickAction: ((index: number) => void) | undefined = undefined;
export let closeAction: ((index: number) => void) | undefined = undefined;
let className = "";
export { className as class };
export let classes: Record<string, boolean> = {};
let styleName = "";
export { styleName as style };
export let styles: Record<string, string | number | undefined> = {};
let tabElements: (LayoutRow | undefined)[] = [];
function platformModifiers(reservedKey: boolean): LayoutKeysGroup {
@@ -90,7 +97,7 @@
}
</script>
<LayoutCol class="panel" on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)}>
<LayoutCol on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
<LayoutRow class="tab-group" scrollableX={true}>
{#each tabLabels as tabLabel, tabIndex}
@@ -194,6 +201,7 @@
.tab-bar {
height: 28px;
min-height: auto;
background: var(--color-1-nearblack); // Needed for the viewport hole punch on desktop
&.min-widths .tab-group .tab {
min-width: 120px;
@@ -336,5 +344,11 @@
}
}
}
// Needed for the viewport hole punch on desktop
.viewport-hole-punch &.document-panel,
.viewport-hole-punch &.document-panel .panel-body:not(:has(.empty-panel)) {
background: none;
}
}
</style>
@@ -137,6 +137,7 @@
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["content"] }} data-subdivision-name="content">
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["document"] }} data-subdivision-name="document">
<Panel
class="document-panel"
panelType={$portfolio.documents.length > 0 ? "Document" : undefined}
tabCloseButtons={true}
tabMinWidths={true}
@@ -176,8 +177,9 @@
flex: 1 1 100%;
.workspace-grid-subdivision {
min-height: 28px;
position: relative;
flex: 1 1 0;
min-height: 28px;
&.folded {
flex-grow: 0;
@@ -196,5 +198,15 @@
cursor: ew-resize;
}
}
// Needed for the viewport hole punch on desktop
.viewport-hole-punch & .workspace-grid-subdivision:has(.panel.document-panel)::after {
content: "";
position: absolute;
inset: 6px;
border-radius: 6px;
box-shadow: 0 0 0 calc(100vw + 100vh) var(--color-2-mildblack);
z-index: -1;
}
}
</style>