mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 03:18:06 +08:00
Implement input hints based on the active tool state (#388)
* Hook up user input hints to display in the frontend status bar Closes #171 * MVP hint system based on tool FSM * Fix hints for Fill and Eyedropper tools * Add icons for keyboard shortcuts * Fix hints for Pen Tool * Cleanup
This commit is contained in:
@@ -78,7 +78,7 @@
|
||||
|
||||
.user-input-label {
|
||||
margin: 0;
|
||||
margin-left: 4px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.submenu-arrow {
|
||||
|
||||
@@ -66,13 +66,13 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "New", icon: "File", shortcut: ["Ctrl", "N"], shortcutRequiresLock: true, action: async () => editor.instance.new_document() },
|
||||
{ label: "Open…", shortcut: ["Ctrl", "O"], action: async () => editor.instance.open_document() },
|
||||
{ label: "New", icon: "File", shortcut: ["KeyControl", "KeyN"], shortcutRequiresLock: true, action: async () => editor.instance.new_document() },
|
||||
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: async () => editor.instance.open_document() },
|
||||
{
|
||||
label: "Open Recent",
|
||||
shortcut: ["Ctrl", "⇧", "O"],
|
||||
shortcut: ["KeyControl", "KeyShift", "KeyO"],
|
||||
children: [
|
||||
[{ label: "Reopen Last Closed", shortcut: ["Ctrl", "⇧", "T"], shortcutRequiresLock: true }, { label: "Clear Recently Opened" }],
|
||||
[{ label: "Reopen Last Closed", shortcut: ["KeyControl", "KeyShift", "KeyT"], shortcutRequiresLock: true }, { label: "Clear Recently Opened" }],
|
||||
[
|
||||
{ label: "Some Recent File.gdd" },
|
||||
{ label: "Another Recent File.gdd" },
|
||||
@@ -84,20 +84,20 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "Close", shortcut: ["Ctrl", "W"], shortcutRequiresLock: true, action: async () => editor.instance.close_active_document_with_confirmation() },
|
||||
{ label: "Close All", shortcut: ["Ctrl", "Alt", "W"], action: async () => editor.instance.close_all_documents_with_confirmation() },
|
||||
{ label: "Close", shortcut: ["KeyControl", "KeyW"], shortcutRequiresLock: true, action: async () => editor.instance.close_active_document_with_confirmation() },
|
||||
{ label: "Close All", shortcut: ["KeyControl", "KeyAlt", "KeyW"], action: async () => editor.instance.close_all_documents_with_confirmation() },
|
||||
],
|
||||
[
|
||||
{ label: "Save", shortcut: ["Ctrl", "S"], action: async () => editor.instance.save_document() },
|
||||
{ label: "Save As…", shortcut: ["Ctrl", "⇧", "S"], action: async () => editor.instance.save_document() },
|
||||
{ label: "Save All", shortcut: ["Ctrl", "Alt", "S"] },
|
||||
{ label: "Save", shortcut: ["KeyControl", "KeyS"], action: async () => editor.instance.save_document() },
|
||||
{ label: "Save As…", shortcut: ["KeyControl", "KeyShift", "KeyS"], action: async () => editor.instance.save_document() },
|
||||
{ label: "Save All", shortcut: ["KeyControl", "KeyAlt", "KeyS"] },
|
||||
{ label: "Auto-Save", checkbox: true, checked: true },
|
||||
],
|
||||
[
|
||||
{ label: "Import…", shortcut: ["Ctrl", "I"] },
|
||||
{ label: "Export…", shortcut: ["Ctrl", "E"], action: async () => editor.instance.export_document() },
|
||||
{ label: "Import…", shortcut: ["KeyControl", "KeyI"] },
|
||||
{ label: "Export…", shortcut: ["KeyControl", "KeyE"], action: async () => editor.instance.export_document() },
|
||||
],
|
||||
[{ label: "Quit", shortcut: ["Ctrl", "Q"] }],
|
||||
[{ label: "Quit", shortcut: ["KeyControl", "KeyQ"] }],
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -105,13 +105,13 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "Undo", shortcut: ["Ctrl", "Z"], action: async () => editor.instance.undo() },
|
||||
{ label: "Redo", shortcut: ["Ctrl", "⇧", "Z"], action: async () => editor.instance.redo() },
|
||||
{ label: "Undo", shortcut: ["KeyControl", "KeyZ"], action: async () => editor.instance.undo() },
|
||||
{ label: "Redo", shortcut: ["KeyControl", "KeyShift", "KeyZ"], action: async () => editor.instance.redo() },
|
||||
],
|
||||
[
|
||||
{ label: "Cut", shortcut: ["Ctrl", "X"] },
|
||||
{ label: "Copy", icon: "Copy", shortcut: ["Ctrl", "C"] },
|
||||
{ label: "Paste", icon: "Paste", shortcut: ["Ctrl", "V"] },
|
||||
{ label: "Cut", shortcut: ["KeyControl", "KeyX"] },
|
||||
{ label: "Copy", icon: "Copy", shortcut: ["KeyControl", "KeyC"] },
|
||||
{ label: "Paste", icon: "Paste", shortcut: ["KeyControl", "KeyV"] },
|
||||
],
|
||||
],
|
||||
},
|
||||
@@ -120,16 +120,24 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "Select All", shortcut: ["Ctrl", "A"], action: async () => editor.instance.select_all_layers() },
|
||||
{ label: "Deselect All", shortcut: ["Ctrl", "Alt", "A"], action: async () => editor.instance.deselect_all_layers() },
|
||||
{ label: "Select All", shortcut: ["KeyControl", "KeyA"], action: async () => editor.instance.select_all_layers() },
|
||||
{ label: "Deselect All", shortcut: ["KeyControl", "KeyAlt", "KeyA"], action: async () => editor.instance.deselect_all_layers() },
|
||||
{
|
||||
label: "Order",
|
||||
children: [
|
||||
[
|
||||
{ label: "Raise To Front", shortcut: ["Ctrl", "Shift", "]"], action: async () => editor.instance.reorder_selected_layers(editor.rawWasm.i32_max()) },
|
||||
{ label: "Raise", shortcut: ["Ctrl", "]"], action: async () => editor.instance.reorder_selected_layers(1) },
|
||||
{ label: "Lower", shortcut: ["Ctrl", "["], action: async () => editor.instance.reorder_selected_layers(-1) },
|
||||
{ label: "Lower to Back", shortcut: ["Ctrl", "Shift", "["], action: async () => editor.instance.reorder_selected_layers(editor.rawWasm.i32_min()) },
|
||||
{
|
||||
label: "Raise To Front",
|
||||
shortcut: ["KeyControl", "KeyShift", "KeyLeftBracket"],
|
||||
action: async () => editor.instance.reorder_selected_layers(editor.rawWasm.i32_max()),
|
||||
},
|
||||
{ label: "Raise", shortcut: ["KeyControl", "KeyRightBracket"], action: async () => editor.instance.reorder_selected_layers(1) },
|
||||
{ label: "Lower", shortcut: ["KeyControl", "KeyLeftBracket"], action: async () => editor.instance.reorder_selected_layers(-1) },
|
||||
{
|
||||
label: "Lower to Back",
|
||||
shortcut: ["KeyControl", "KeyShift", "KeyRightBracket"],
|
||||
action: async () => editor.instance.reorder_selected_layers(editor.rawWasm.i32_min()),
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
|
||||
@@ -99,17 +99,28 @@ import WindowButtonWinMinimize from "@/../assets/12px-solid/window-button-win-mi
|
||||
import WindowButtonWinMaximize from "@/../assets/12px-solid/window-button-win-maximize.svg";
|
||||
import WindowButtonWinRestoreDown from "@/../assets/12px-solid/window-button-win-restore-down.svg";
|
||||
import WindowButtonWinClose from "@/../assets/12px-solid/window-button-win-close.svg";
|
||||
import KeyboardArrowUp from "@/../assets/12px-solid/keyboard-arrow-up.svg";
|
||||
import KeyboardArrowRight from "@/../assets/12px-solid/keyboard-arrow-right.svg";
|
||||
import KeyboardArrowDown from "@/../assets/12px-solid/keyboard-arrow-down.svg";
|
||||
import KeyboardArrowLeft from "@/../assets/12px-solid/keyboard-arrow-left.svg";
|
||||
import KeyboardBackspace from "@/../assets/12px-solid/keyboard-backspace.svg";
|
||||
import KeyboardCommand from "@/../assets/12px-solid/keyboard-command.svg";
|
||||
import KeyboardEnter from "@/../assets/12px-solid/keyboard-enter.svg";
|
||||
import KeyboardOption from "@/../assets/12px-solid/keyboard-option.svg";
|
||||
import KeyboardShift from "@/../assets/12px-solid/keyboard-shift.svg";
|
||||
import KeyboardSpace from "@/../assets/12px-solid/keyboard-space.svg";
|
||||
import KeyboardTab from "@/../assets/12px-solid/keyboard-tab.svg";
|
||||
|
||||
import MouseHintNone from "@/../assets/16px-two-tone/mouse-hint-none.svg";
|
||||
import MouseHintLMB from "@/../assets/16px-two-tone/mouse-hint-lmb.svg";
|
||||
import MouseHintRMB from "@/../assets/16px-two-tone/mouse-hint-rmb.svg";
|
||||
import MouseHintMMB from "@/../assets/16px-two-tone/mouse-hint-mmb.svg";
|
||||
import MouseHintLmb from "@/../assets/16px-two-tone/mouse-hint-lmb.svg";
|
||||
import MouseHintRmb from "@/../assets/16px-two-tone/mouse-hint-rmb.svg";
|
||||
import MouseHintMmb from "@/../assets/16px-two-tone/mouse-hint-mmb.svg";
|
||||
import MouseHintScrollUp from "@/../assets/16px-two-tone/mouse-hint-scroll-up.svg";
|
||||
import MouseHintScrollDown from "@/../assets/16px-two-tone/mouse-hint-scroll-down.svg";
|
||||
import MouseHintDrag from "@/../assets/16px-two-tone/mouse-hint-drag.svg";
|
||||
import MouseHintLMBDrag from "@/../assets/16px-two-tone/mouse-hint-lmb-drag.svg";
|
||||
import MouseHintRMBDrag from "@/../assets/16px-two-tone/mouse-hint-rmb-drag.svg";
|
||||
import MouseHintMMBDrag from "@/../assets/16px-two-tone/mouse-hint-mmb-drag.svg";
|
||||
import MouseHintLmbDrag from "@/../assets/16px-two-tone/mouse-hint-lmb-drag.svg";
|
||||
import MouseHintRmbDrag from "@/../assets/16px-two-tone/mouse-hint-rmb-drag.svg";
|
||||
import MouseHintMmbDrag from "@/../assets/16px-two-tone/mouse-hint-mmb-drag.svg";
|
||||
|
||||
import NodeTypePath from "@/../assets/24px-full-color/node-type-path.svg";
|
||||
import NodeTypeFolder from "@/../assets/24px-full-color/node-type-folder.svg";
|
||||
@@ -182,16 +193,27 @@ const icons = {
|
||||
WindowButtonWinMaximize: { component: WindowButtonWinMaximize, size: 12 },
|
||||
WindowButtonWinRestoreDown: { component: WindowButtonWinRestoreDown, size: 12 },
|
||||
WindowButtonWinClose: { component: WindowButtonWinClose, size: 12 },
|
||||
KeyboardArrowUp: { component: KeyboardArrowUp, size: 12 },
|
||||
KeyboardArrowRight: { component: KeyboardArrowRight, size: 12 },
|
||||
KeyboardArrowDown: { component: KeyboardArrowDown, size: 12 },
|
||||
KeyboardArrowLeft: { component: KeyboardArrowLeft, size: 12 },
|
||||
KeyboardBackspace: { component: KeyboardBackspace, size: 12 },
|
||||
KeyboardCommand: { component: KeyboardCommand, size: 12 },
|
||||
KeyboardEnter: { component: KeyboardEnter, size: 12 },
|
||||
KeyboardOption: { component: KeyboardOption, size: 12 },
|
||||
KeyboardShift: { component: KeyboardShift, size: 12 },
|
||||
KeyboardSpace: { component: KeyboardSpace, size: 12 },
|
||||
KeyboardTab: { component: KeyboardTab, size: 12 },
|
||||
MouseHintNone: { component: MouseHintNone, size: 16 },
|
||||
MouseHintLMB: { component: MouseHintLMB, size: 16 },
|
||||
MouseHintRMB: { component: MouseHintRMB, size: 16 },
|
||||
MouseHintMMB: { component: MouseHintMMB, size: 16 },
|
||||
MouseHintLmb: { component: MouseHintLmb, size: 16 },
|
||||
MouseHintRmb: { component: MouseHintRmb, size: 16 },
|
||||
MouseHintMmb: { component: MouseHintMmb, size: 16 },
|
||||
MouseHintScrollUp: { component: MouseHintScrollUp, size: 16 },
|
||||
MouseHintScrollDown: { component: MouseHintScrollDown, size: 16 },
|
||||
MouseHintDrag: { component: MouseHintDrag, size: 16 },
|
||||
MouseHintLMBDrag: { component: MouseHintLMBDrag, size: 16 },
|
||||
MouseHintRMBDrag: { component: MouseHintRMBDrag, size: 16 },
|
||||
MouseHintMMBDrag: { component: MouseHintMMBDrag, size: 16 },
|
||||
MouseHintLmbDrag: { component: MouseHintLmbDrag, size: 16 },
|
||||
MouseHintRmbDrag: { component: MouseHintRmbDrag, size: 16 },
|
||||
MouseHintMmbDrag: { component: MouseHintMmbDrag, size: 16 },
|
||||
NodeTypePath: { component: NodeTypePath, size: 24 },
|
||||
NodeTypeFolder: { component: NodeTypeFolder, size: 24 },
|
||||
};
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
<div class="user-input-label">
|
||||
<template v-for="(keyGroup, keyGroupIndex) in inputKeys" :key="keyGroupIndex">
|
||||
<span class="group-gap" v-if="keyGroupIndex > 0"></span>
|
||||
<span class="input-key" v-for="inputKey in keyGroup" :key="inputKey" :class="keyCapWidth(inputKey)">
|
||||
{{ inputKey }}
|
||||
</span>
|
||||
<template v-for="inputKey in keyGroup" :key="((keyInfo = keyTextOrIcon(inputKey)), inputKey)">
|
||||
<span class="input-key" :class="keyInfo.width">
|
||||
<IconLabel v-if="keyInfo.icon" :icon="keyInfo.icon" />
|
||||
<template v-else>{{ keyInfo.text }}</template>
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<span class="input-mouse" v-if="inputMouse">
|
||||
<IconLabel :icon="mouseInputInteractionToIcon(inputMouse)" />
|
||||
<IconLabel :icon="mouseMovementIcon(inputMouse)" />
|
||||
</span>
|
||||
<span class="hint-text" v-if="hasSlotContent">
|
||||
<slot></slot>
|
||||
@@ -46,27 +49,33 @@
|
||||
border-color: var(--color-7-middlegray);
|
||||
border-radius: 4px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
}
|
||||
// Firefox renders the text 1px lower than Chrome (tested on Windows) with 16px line-height, so moving it up 1 pixel with 15px makes them agree
|
||||
line-height: 15px;
|
||||
|
||||
.input-key.width-16 {
|
||||
width: 16px;
|
||||
}
|
||||
&.width-16 {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.input-key.width-24 {
|
||||
width: 24px;
|
||||
}
|
||||
&.width-24 {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.input-key.width-32 {
|
||||
width: 32px;
|
||||
}
|
||||
&.width-32 {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.input-key.width-40 {
|
||||
width: 40px;
|
||||
}
|
||||
&.width-40 {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.input-key.width-48 {
|
||||
width: 48px;
|
||||
&.width-48 {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
margin: 1px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.input-mouse {
|
||||
@@ -92,15 +101,15 @@ import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||
|
||||
export enum MouseInputInteraction {
|
||||
"None" = "None",
|
||||
"LMB" = "LMB",
|
||||
"RMB" = "RMB",
|
||||
"MMB" = "MMB",
|
||||
"Lmb" = "Lmb",
|
||||
"Rmb" = "Rmb",
|
||||
"Mmb" = "Mmb",
|
||||
"ScrollUp" = "ScrollUp",
|
||||
"ScrollDown" = "ScrollDown",
|
||||
"Drag" = "Drag",
|
||||
"LMBDrag" = "LMBDrag",
|
||||
"RMBDrag" = "RMBDrag",
|
||||
"MMBDrag" = "MMBDrag",
|
||||
"LmbDrag" = "LmbDrag",
|
||||
"RmbDrag" = "RmbDrag",
|
||||
"MmbDrag" = "MmbDrag",
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@@ -115,29 +124,89 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
keyCapWidth(keyText: string) {
|
||||
return `width-${keyText.length * 8 + 8}`;
|
||||
keyTextOrIcon(keyText: string): { text: string | null; icon: string | null; width: string } {
|
||||
// Definitions
|
||||
const textMap: Record<string, string> = {
|
||||
Control: "Ctrl",
|
||||
Alt: "Alt",
|
||||
Delete: "Del",
|
||||
PageUp: "PgUp",
|
||||
PageDown: "PgDn",
|
||||
Equals: "=",
|
||||
Minus: "-",
|
||||
Plus: "+",
|
||||
Escape: "Esc",
|
||||
Comma: ",",
|
||||
Period: ".",
|
||||
LeftBracket: "[",
|
||||
RightBracket: "]",
|
||||
LeftCurlyBracket: "{",
|
||||
RightCurlyBracket: "}",
|
||||
};
|
||||
const iconsAndWidths: Record<string, number> = {
|
||||
ArrowUp: 1,
|
||||
ArrowRight: 1,
|
||||
ArrowDown: 1,
|
||||
ArrowLeft: 1,
|
||||
Backspace: 2,
|
||||
Command: 2,
|
||||
Enter: 2,
|
||||
Option: 2,
|
||||
Shift: 2,
|
||||
Tab: 2,
|
||||
Space: 3,
|
||||
};
|
||||
|
||||
// Strip off the "Key" prefix
|
||||
const text = keyText.replace(/^(?:Key)?(.*)$/, "$1");
|
||||
|
||||
// If it's an icon, return the icon identifier
|
||||
if (text in iconsAndWidths) {
|
||||
return {
|
||||
text: null,
|
||||
icon: `Keyboard${text}`,
|
||||
width: `width-${iconsAndWidths[text] * 8 + 8}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, return the text string
|
||||
let result;
|
||||
|
||||
// Letters and numbers
|
||||
if (/^[A-Z0-9]$/.test(text)) {
|
||||
result = text;
|
||||
}
|
||||
// Abbreviated names
|
||||
else if (text in textMap) {
|
||||
result = textMap[text];
|
||||
}
|
||||
// Other
|
||||
else {
|
||||
result = text;
|
||||
}
|
||||
|
||||
return { text: result, icon: null, width: `width-${(result || " ").length * 8 + 8}` };
|
||||
},
|
||||
mouseInputInteractionToIcon(mouseInputInteraction: MouseInputInteraction) {
|
||||
mouseMovementIcon(mouseInputInteraction: MouseInputInteraction) {
|
||||
switch (mouseInputInteraction) {
|
||||
case MouseInputInteraction.LMB:
|
||||
return "MouseHintLMB";
|
||||
case MouseInputInteraction.RMB:
|
||||
return "MouseHintRMB";
|
||||
case MouseInputInteraction.MMB:
|
||||
return "MouseHintMMB";
|
||||
case MouseInputInteraction.Lmb:
|
||||
return "MouseHintLmb";
|
||||
case MouseInputInteraction.Rmb:
|
||||
return "MouseHintRmb";
|
||||
case MouseInputInteraction.Mmb:
|
||||
return "MouseHintMmb";
|
||||
case MouseInputInteraction.ScrollUp:
|
||||
return "MouseHintScrollUp";
|
||||
case MouseInputInteraction.ScrollDown:
|
||||
return "MouseHintScrollDown";
|
||||
case MouseInputInteraction.Drag:
|
||||
return "MouseHintDrag";
|
||||
case MouseInputInteraction.LMBDrag:
|
||||
return "MouseHintLMBDrag";
|
||||
case MouseInputInteraction.RMBDrag:
|
||||
return "MouseHintRMBDrag";
|
||||
case MouseInputInteraction.MMBDrag:
|
||||
return "MouseHintMMBDrag";
|
||||
case MouseInputInteraction.LmbDrag:
|
||||
return "MouseHintLmbDrag";
|
||||
case MouseInputInteraction.RmbDrag:
|
||||
return "MouseHintRmbDrag";
|
||||
case MouseInputInteraction.MmbDrag:
|
||||
return "MouseHintMmbDrag";
|
||||
default:
|
||||
case MouseInputInteraction.None:
|
||||
return "MouseHintNone";
|
||||
|
||||
@@ -1,43 +1,22 @@
|
||||
<template>
|
||||
<div class="status-bar">
|
||||
<UserInputLabel :inputMouse="'LMBDrag'">Drag Selected</UserInputLabel>
|
||||
<Separator :type="SeparatorType.Section" />
|
||||
<UserInputLabel :inputKeys="[['G']]">Grab Selected</UserInputLabel>
|
||||
<UserInputLabel :inputKeys="[['R']]">Rotate Selected</UserInputLabel>
|
||||
<UserInputLabel :inputKeys="[['S']]">Scale Selected</UserInputLabel>
|
||||
<Separator :type="SeparatorType.Section" />
|
||||
<UserInputLabel :inputMouse="'LMB'">Select Object</UserInputLabel>
|
||||
<span class="plus">+</span>
|
||||
<UserInputLabel :inputKeys="[['Ctrl']]">Innermost</UserInputLabel>
|
||||
<span class="plus">+</span>
|
||||
<UserInputLabel :inputKeys="[['⇧']]">Grow/Shrink Selection</UserInputLabel>
|
||||
<Separator :type="SeparatorType.Section" />
|
||||
<UserInputLabel :inputMouse="'LMBDrag'">Select Area</UserInputLabel>
|
||||
<span class="plus">+</span>
|
||||
<UserInputLabel :inputKeys="[['⇧']]">Grow/Shrink Selection</UserInputLabel>
|
||||
<Separator :type="SeparatorType.Section" />
|
||||
<UserInputLabel :inputKeys="[['↑'], ['→'], ['↓'], ['←']]">Nudge Selected</UserInputLabel>
|
||||
<span class="plus">+</span>
|
||||
<UserInputLabel :inputKeys="[['⇧']]">Big Increment Nudge</UserInputLabel>
|
||||
<Separator :type="SeparatorType.Section" />
|
||||
<UserInputLabel :inputKeys="[['Alt']]" :inputMouse="'LMBDrag'">Move Duplicate</UserInputLabel>
|
||||
<UserInputLabel :inputKeys="[['Ctrl', 'D']]">Duplicate</UserInputLabel>
|
||||
<template v-for="(hintGroup, index) in hintData" :key="hintGroup">
|
||||
<Separator :type="SeparatorType.Section" v-if="index !== 0" />
|
||||
<template v-for="hint in hintGroup" :key="hint">
|
||||
<span v-if="hint.plus" class="plus">+</span>
|
||||
<UserInputLabel :inputMouse="hint.mouse" :inputKeys="hint.key_groups">{{ hint.label }}</UserInputLabel>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.status-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 24px;
|
||||
margin: 0 -4px;
|
||||
// TODO: Use CSS grid to solve issue that makes overflowed items have inconsistent left padding on second row when overflowed
|
||||
|
||||
> * {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.separator.section {
|
||||
height: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -60,8 +39,10 @@ import { SeparatorType } from "@/components/widgets/widgets";
|
||||
|
||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||
import { HintData, UpdateInputHints } from "@/dispatcher/js-messages";
|
||||
|
||||
export default defineComponent({
|
||||
inject: ["editor"],
|
||||
components: {
|
||||
UserInputLabel,
|
||||
Separator,
|
||||
@@ -69,7 +50,17 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
SeparatorType,
|
||||
hintData: [] as HintData,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.editor.dispatcher.subscribeJsMessage(UpdateInputHints, (updateInputHints) => {
|
||||
this.hintData = updateInputHints.hint_data;
|
||||
});
|
||||
|
||||
// Switch away from, and back to, the Select Tool to make it display the correct hints in the status bar
|
||||
this.editor.instance.select_tool("Path");
|
||||
this.editor.instance.select_tool("Select");
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,7 +21,10 @@ export function createJsDispatcher() {
|
||||
const messageConstructor = messageConstructors[messageType];
|
||||
if (!messageConstructor) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Received a frontend message of type "${messageType}" but but was not able to parse the data.`);
|
||||
console.error(
|
||||
`Received a frontend message of type "${messageType}" but was not able to parse the data. ` +
|
||||
"(Perhaps this message parser isn't exported in `messageConstructors` at the bottom of `js-messages.ts`.)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,41 @@ export class JsMessage {
|
||||
static readonly jsMessageMarker = true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Add additional classes to replicate Rust's FrontendMessages and data structures below.
|
||||
//
|
||||
// Remember to add each message to the `messageConstructors` export at the bottom of the file.
|
||||
//
|
||||
// Read class-transformer docs at https://github.com/typestack/class-transformer#table-of-contents
|
||||
// for details about how to transform the JSON from wasm-bindgen into classes.
|
||||
// ============================================================================
|
||||
|
||||
export class UpdateOpenDocumentsList extends JsMessage {
|
||||
@Transform(({ value }) => value.map((tuple: [string, boolean]) => ({ name: tuple[0], isSaved: tuple[1] })))
|
||||
readonly open_documents!: { name: string; isSaved: boolean }[];
|
||||
}
|
||||
|
||||
export class UpdateInputHints extends JsMessage {
|
||||
@Type(() => HintInfo)
|
||||
readonly hint_data!: HintData;
|
||||
}
|
||||
|
||||
export class HintGroup extends Array<HintInfo> {}
|
||||
|
||||
export class HintData extends Array<HintGroup> {}
|
||||
|
||||
export class HintInfo {
|
||||
readonly keys!: string[];
|
||||
|
||||
readonly mouse!: KeysGroup | null;
|
||||
|
||||
readonly label!: string;
|
||||
|
||||
readonly plus!: boolean;
|
||||
}
|
||||
|
||||
export class KeysGroup extends Array<string> {}
|
||||
|
||||
const To255Scale = Transform(({ value }) => value * 255);
|
||||
export class Color {
|
||||
@To255Scale
|
||||
@@ -277,6 +307,7 @@ export const messageConstructors: Record<string, MessageMaker> = {
|
||||
SetActiveTool,
|
||||
SetActiveDocument,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdateInputHints,
|
||||
UpdateWorkingColors,
|
||||
SetCanvasZoom,
|
||||
SetCanvasRotation,
|
||||
|
||||
Reference in New Issue
Block a user