parent
3a520071cf
commit
0656f8abc5
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<polygon points="11,8 11,11 8,11 8,12 12,12 12,8" />
|
||||
<polygon points="1,1 4,1 4,0 0,0 0,4 1,4" />
|
||||
<polygon points="4,11 1,11 1,8 0,8 0,12 4,12" />
|
||||
<polygon points="11,1 11,4 12,4 12,0 8,0 8,1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 268 B |
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<polygon points="9,12 9,9 12,9 12,8 8,8 8,12" />
|
||||
<polygon points="3,3 0,3 0,4 4,4 4,0 3,0" />
|
||||
<polygon points="0,9 3,9 3,12 4,12 4,8 0,8" />
|
||||
<polygon points="9,3 9,0 8,0 8,4 12,4 12,3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 260 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path d="M6,0C2.69,0,0,2.69,0,6s2.69,6,6,6s6-2.69,6-6S9.31,0,6,0z M6.35,11C5.71,11,5.2,10.49,5.2,9.85c0-0.63,0.51-1.15,1.15-1.15c0.63,0,1.15,0.51,1.15,1.15C7.49,10.49,6.98,11,6.35,11z M8.59,5.04C8.2,5.76,7.21,5.98,7.21,6.9v0.99h-1.8V6.9c0-1.89,1.88-1.62,1.88-3.12c0-1.41-2.16-1.88-2.77,0.15L3,3.46C4.25-0.96,10.66,1.2,8.59,5.04z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 402 B |
|
|
@ -196,10 +196,12 @@ img {
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import MainWindow from "./components/window/MainWindow.vue";
|
||||
import LayoutRow from "./components/layout/LayoutRow.vue";
|
||||
import fullscreen from "@/utilities/fullscreen";
|
||||
import MainWindow from "@/components/window/MainWindow.vue";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
|
||||
export default defineComponent({
|
||||
provide: { fullscreen },
|
||||
data() {
|
||||
return {
|
||||
showUnsupportedModal: !("BigInt64Array" in window),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
<Icon :icon="entry.icon" v-if="entry.icon && drawIcon" />
|
||||
<div class="no-icon" v-else-if="drawIcon" />
|
||||
<span class="entry-label">{{ entry.label }}</span>
|
||||
<UserInputLabel v-if="entry.shortcut && entry.shortcut.length" :inputKeys="[entry.shortcut]" />
|
||||
<Icon :icon="'Info'" v-if="entry.shortcutRequiresLock && !fullscreen.keyboardLocked" :title="keyboardLockInfoMessage" />
|
||||
<UserInputLabel v-else-if="entry.shortcut && entry.shortcut.length" :inputKeys="[entry.shortcut]" />
|
||||
<div class="submenu-arrow" v-if="entry.children && entry.children.length"></div>
|
||||
<div class="no-submenu-arrow" v-else></div>
|
||||
<MenuList
|
||||
|
|
@ -121,6 +122,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { keyboardLockApiSupported } from "@/utilities/fullscreen";
|
||||
import FloatingMenu, { MenuDirection, MenuType } from "./FloatingMenu.vue";
|
||||
import Separator, { SeparatorDirection, SeparatorType } from "../Separator.vue";
|
||||
import Icon from "../labels/Icon.vue";
|
||||
|
|
@ -134,13 +136,18 @@ interface MenuListEntryData {
|
|||
icon?: string;
|
||||
// TODO: Add `checkbox` (which overrides any `icon`)
|
||||
shortcut?: Array<string>;
|
||||
shortcutRequiresLock?: boolean;
|
||||
action?: Function;
|
||||
children?: SectionsOfMenuListEntries;
|
||||
}
|
||||
|
||||
export type MenuListEntry = MenuListEntryData & { ref?: typeof FloatingMenu | typeof MenuList };
|
||||
|
||||
const KEYBOARD_LOCK_USE_FULLSCREEN = "This hotkey is reserved by the browser, but becomes available in fullscreen mode";
|
||||
const KEYBOARD_LOCK_SWITCH_BROWSER = "This hotkey is reserved by the browser, but becomes available in Chrome, Edge, and Opera which support the Keyboard.lock() API";
|
||||
|
||||
const MenuList = defineComponent({
|
||||
inject: ["fullscreen"],
|
||||
props: {
|
||||
direction: { type: String as PropType<MenuDirection>, default: MenuDirection.Bottom },
|
||||
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
|
|
@ -242,6 +249,7 @@ const MenuList = defineComponent({
|
|||
data() {
|
||||
return {
|
||||
currentEntry: this.activeEntry,
|
||||
keyboardLockInfoMessage: keyboardLockApiSupported() ? KEYBOARD_LOCK_USE_FULLSCREEN : KEYBOARD_LOCK_SWITCH_BROWSER,
|
||||
SeparatorDirection,
|
||||
SeparatorType,
|
||||
MenuDirection,
|
||||
|
|
|
|||
|
|
@ -65,13 +65,13 @@ const menuEntries: MenuListEntries = [
|
|||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "New", icon: "File", shortcut: ["Ctrl", "N"], action: async () => (await wasm).new_document() },
|
||||
{ label: "New", icon: "File", shortcut: ["Ctrl", "N"], shortcutRequiresLock: true, action: async () => (await wasm).new_document() },
|
||||
{ label: "Open…", shortcut: ["Ctrl", "O"] },
|
||||
{
|
||||
label: "Open Recent",
|
||||
shortcut: ["Ctrl", "⇧", "O"],
|
||||
children: [
|
||||
[{ label: "Reopen Last Closed", shortcut: ["Ctrl", "⇧", "T"] }, { label: "Clear Recently Opened" }],
|
||||
[{ label: "Reopen Last Closed", shortcut: ["Ctrl", "⇧", "T"], shortcutRequiresLock: true }, { label: "Clear Recently Opened" }],
|
||||
[
|
||||
{ label: "Some Recent File.gdd" },
|
||||
{ label: "Another Recent File.gdd" },
|
||||
|
|
@ -83,7 +83,7 @@ const menuEntries: MenuListEntries = [
|
|||
},
|
||||
],
|
||||
[
|
||||
{ label: "Close", shortcut: ["Ctrl", "W"], action: async () => (await wasm).close_active_document_with_confirmation() },
|
||||
{ label: "Close", shortcut: ["Ctrl", "W"], shortcutRequiresLock: true, action: async () => (await wasm).close_active_document_with_confirmation() },
|
||||
{ label: "Close All", shortcut: ["Ctrl", "Alt", "W"], action: async () => (await wasm).close_all_documents_with_confirmation() },
|
||||
],
|
||||
[
|
||||
|
|
|
|||
|
|
@ -86,11 +86,14 @@ import Link from "../../../../assets/12px-solid/link.svg";
|
|||
import Grid from "../../../../assets/12px-solid/grid.svg";
|
||||
import Overlays from "../../../../assets/12px-solid/overlays.svg";
|
||||
import Snapping from "../../../../assets/12px-solid/snapping.svg";
|
||||
import Info from "../../../../assets/12px-solid/info.svg";
|
||||
import Swap from "../../../../assets/12px-solid/swap.svg";
|
||||
import ResetColors from "../../../../assets/12px-solid/reset-colors.svg";
|
||||
import DropdownArrow from "../../../../assets/12px-solid/dropdown-arrow.svg";
|
||||
import VerticalEllipsis from "../../../../assets/12px-solid/vertical-ellipsis.svg";
|
||||
import CloseX from "../../../../assets/12px-solid/close-x.svg";
|
||||
import FullscreenEnter from "../../../../assets/12px-solid/fullscreen-enter.svg";
|
||||
import FullscreenExit from "../../../../assets/12px-solid/fullscreen-exit.svg";
|
||||
import WindowButtonWinMinimize from "../../../../assets/12px-solid/window-button-win-minimize.svg";
|
||||
import WindowButtonWinMaximize from "../../../../assets/12px-solid/window-button-win-maximize.svg";
|
||||
import WindowButtonWinRestoreDown from "../../../../assets/12px-solid/window-button-win-restore-down.svg";
|
||||
|
|
@ -164,11 +167,14 @@ const icons = {
|
|||
Grid: { component: Grid, size: 12 },
|
||||
Overlays: { component: Overlays, size: 12 },
|
||||
Snapping: { component: Snapping, size: 12 },
|
||||
Info: { component: Info, size: 12 },
|
||||
Swap: { component: Swap, size: 12 },
|
||||
ResetColors: { component: ResetColors, size: 12 },
|
||||
DropdownArrow: { component: DropdownArrow, size: 12 },
|
||||
VerticalEllipsis: { component: VerticalEllipsis, size: 12 },
|
||||
CloseX: { component: CloseX, size: 12 },
|
||||
FullscreenEnter: { component: FullscreenEnter, size: 12 },
|
||||
FullscreenExit: { component: FullscreenExit, size: 12 },
|
||||
WindowButtonWinMinimize: { component: WindowButtonWinMinimize, size: 12 },
|
||||
WindowButtonWinMaximize: { component: WindowButtonWinMaximize, size: 12 },
|
||||
WindowButtonWinRestoreDown: { component: WindowButtonWinRestoreDown, size: 12 },
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export default defineComponent({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
platform: ApplicationPlatform.Windows,
|
||||
platform: ApplicationPlatform.Web,
|
||||
maximized: true,
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
</div>
|
||||
<div class="header-third">
|
||||
<WindowButtonsWindows :maximized="maximized" v-if="platform === ApplicationPlatform.Windows || platform === ApplicationPlatform.Linux" />
|
||||
<WindowButtonsWeb :maximized="maximized" v-if="platform === ApplicationPlatform.Web" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -36,15 +37,10 @@ import MenuBarInput from "../../widgets/inputs/MenuBarInput.vue";
|
|||
import WindowTitle from "./WindowTitle.vue";
|
||||
import WindowButtonsWindows from "./WindowButtonsWindows.vue";
|
||||
import WindowButtonsMac from "./WindowButtonsMac.vue";
|
||||
import WindowButtonsWeb from "./WindowButtonsWeb.vue";
|
||||
import { ApplicationPlatform } from "../MainWindow.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MenuBarInput,
|
||||
WindowTitle,
|
||||
WindowButtonsWindows,
|
||||
WindowButtonsMac,
|
||||
},
|
||||
props: {
|
||||
platform: { type: String, required: true },
|
||||
maximized: { type: Boolean, required: true },
|
||||
|
|
@ -54,5 +50,12 @@ export default defineComponent({
|
|||
ApplicationPlatform,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
MenuBarInput,
|
||||
WindowTitle,
|
||||
WindowButtonsWindows,
|
||||
WindowButtonsMac,
|
||||
WindowButtonsWeb,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@
|
|||
border-radius: 50%;
|
||||
|
||||
&.close {
|
||||
background: #ff5f57;
|
||||
background: #ff5a52;
|
||||
}
|
||||
|
||||
&.minimize {
|
||||
background: #ffbd2f;
|
||||
background: #e6c029;
|
||||
}
|
||||
|
||||
&.zoom {
|
||||
background: #29c93f;
|
||||
background: #54c22b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="window-buttons-web" @click="handleClick" :title="fullscreen.windowFullscreen ? 'Exit Fullscreen (F11)' : 'Enter Fullscreen (F11)'">
|
||||
<TextLabel v-if="requestFullscreenHotkeys">Click to access all hotkeys</TextLabel>
|
||||
<Icon :icon="fullscreen.windowFullscreen ? 'FullscreenExit' : 'FullscreenEnter'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.window-buttons-web {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
fill: var(--color-e-nearwhite);
|
||||
|
||||
.text-label {
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-6-lowergray);
|
||||
color: var(--color-f-white);
|
||||
fill: var(--color-f-white);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import fullscreen, { keyboardLockApiSupported, enterFullscreen, exitFullscreen } from "@/utilities/fullscreen";
|
||||
import Icon from "@/components/widgets/labels/Icon.vue";
|
||||
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
||||
|
||||
const canUseKeyboardLock = keyboardLockApiSupported();
|
||||
|
||||
export default defineComponent({
|
||||
inject: ["fullscreen"],
|
||||
methods: {
|
||||
async handleClick() {
|
||||
if (fullscreen.windowFullscreen) exitFullscreen();
|
||||
else enterFullscreen();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
requestFullscreenHotkeys() {
|
||||
return canUseKeyboardLock && !fullscreen.keyboardLocked;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Icon,
|
||||
TextLabel,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
import { createApp } from "vue";
|
||||
import { fullscreenModeChanged } from "@/utilities/fullscreen";
|
||||
import { handleKeyUp, handleKeyDown } from "@/utilities/input";
|
||||
import App from "./App.vue";
|
||||
|
||||
// Bind global browser events
|
||||
document.addEventListener("contextmenu", (e) => e.preventDefault());
|
||||
document.addEventListener("fullscreenchange", () => fullscreenModeChanged());
|
||||
window.addEventListener("keyup", (e: KeyboardEvent) => handleKeyUp(e));
|
||||
window.addEventListener("keydown", (e: KeyboardEvent) => handleKeyDown(e));
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { reactive, readonly } from "vue";
|
||||
|
||||
const state = reactive({
|
||||
windowFullscreen: false,
|
||||
keyboardLocked: false,
|
||||
});
|
||||
|
||||
export function fullscreenModeChanged() {
|
||||
state.windowFullscreen = Boolean(document.fullscreenElement);
|
||||
if (!state.windowFullscreen) state.keyboardLocked = false;
|
||||
}
|
||||
|
||||
export function keyboardLockApiSupported(): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return "keyboard" in navigator && "lock" in (navigator as any).keyboard;
|
||||
}
|
||||
|
||||
export async function enterFullscreen() {
|
||||
await document.documentElement.requestFullscreen();
|
||||
|
||||
if (keyboardLockApiSupported()) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (navigator as any).keyboard.lock(["ControlLeft", "ControlRight"]);
|
||||
state.keyboardLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
export async function exitFullscreen() {
|
||||
await document.exitFullscreen();
|
||||
}
|
||||
|
||||
export async function toggleFullscreen() {
|
||||
if (state.windowFullscreen) await exitFullscreen();
|
||||
else await enterFullscreen();
|
||||
}
|
||||
|
||||
export default readonly(state);
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { toggleFullscreen } from "@/utilities/fullscreen";
|
||||
|
||||
const wasm = import("@/../wasm/pkg");
|
||||
|
||||
export function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): boolean {
|
||||
|
|
@ -6,7 +8,11 @@ export function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): boolean
|
|||
if (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable) return false;
|
||||
|
||||
// Don't redirect a fullscreen request
|
||||
if (e.key.toLowerCase() === "f11") return false;
|
||||
if (e.key.toLowerCase() === "f11" && e.type === "keydown" && !e.repeat) {
|
||||
e.preventDefault();
|
||||
toggleFullscreen();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't redirect a reload request
|
||||
if (e.key.toLowerCase() === "f5") return false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue