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:
Keavon Chambers
2021-12-24 01:46:03 -08:00
parent 3500160bf7
commit d2b0411295
38 changed files with 1070 additions and 212 deletions
@@ -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";