Skip to content

Commit 0887e56

Browse files
0HyperCubeKeavon
authored andcommitted
Fix nudge and textbox creation (#672)
* Fix nudge and textbox creation * Code review * Cleanup veriable names
1 parent a02b7a4 commit 0887e56

File tree

3 files changed

+35
-11
lines changed

3 files changed

+35
-11
lines changed

editor/src/input/input_preprocessor_message_handler.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,18 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
114114
}
115115

116116
impl InputPreprocessorMessageHandler {
117-
fn translate_mouse_event(&mut self, new_state: MouseState, allow_first_button_down: bool, responses: &mut VecDeque<Message>) {
117+
fn translate_mouse_event(&mut self, mut new_state: MouseState, allow_first_button_down: bool, responses: &mut VecDeque<Message>) {
118118
for (bit_flag, key) in [(MouseKeys::LEFT, Key::Lmb), (MouseKeys::RIGHT, Key::Rmb), (MouseKeys::MIDDLE, Key::Mmb)] {
119119
// Calculate the intersection between the two key states
120120
let old_down = self.mouse.mouse_keys & bit_flag == bit_flag;
121121
let new_down = new_state.mouse_keys & bit_flag == bit_flag;
122-
if !old_down && new_down && (allow_first_button_down || self.mouse.mouse_keys != MouseKeys::NONE) {
123-
responses.push_back(InputMapperMessage::KeyDown(key).into());
122+
if !old_down && new_down {
123+
if allow_first_button_down || self.mouse.mouse_keys != MouseKeys::NONE {
124+
responses.push_back(InputMapperMessage::KeyDown(key).into());
125+
} else {
126+
// Required to stop a keyup being emitted for a keydown outside canvas
127+
new_state.mouse_keys ^= bit_flag;
128+
}
124129
}
125130
if old_down && !new_down {
126131
responses.push_back(InputMapperMessage::KeyUp(key).into());

frontend/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<body>
2121
<noscript>JavaScript is required</noscript>
2222
<!-- tabindex is used to allow the app to have focus while inside of it -->
23-
<div id="app" tabindex="0"></div>
23+
<div data-app id="app" tabindex="0"></div>
2424
</body>
2525

2626
</html>

frontend/src/io-managers/input.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ type EventListenerTarget = {
1111
};
1212

1313
export function createInputManager(editor: Editor, container: HTMLElement, dialog: DialogState, document: PortfolioState, fullscreen: FullscreenState): () => void {
14+
const app = window.document.querySelector("[data-app]") as HTMLElement | undefined;
15+
app?.focus();
16+
1417
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1518
const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: boolean | AddEventListenerOptions }[] = [
1619
{ target: window, eventName: "resize", action: (): void => onWindowResize(container) },
@@ -27,10 +30,20 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
2730
{ target: window, eventName: "wheel", action: (e: WheelEvent): void => onMouseScroll(e), options: { passive: false } },
2831
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent): void => onModifyInputField(e) },
2932
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent): void => onPaste(e) },
33+
{
34+
target: app as EventListenerTarget,
35+
eventName: "blur",
36+
action: (): void => blurApp(),
37+
},
3038
];
3139

3240
let viewportPointerInteractionOngoing = false;
3341
let textInput = undefined as undefined | HTMLDivElement;
42+
let canvasFocused = true;
43+
44+
function blurApp(): void {
45+
canvasFocused = false;
46+
}
3447

3548
// Keyboard events
3649

@@ -66,8 +79,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
6679
if (e.ctrlKey && e.shiftKey && key === "j") return false;
6780

6881
// Don't redirect tab or enter if not in canvas (to allow navigating elements)
69-
const inCanvas = e.target instanceof Element && e.target.closest("[data-canvas]");
70-
if (!inCanvas && ["tab", "enter", " ", "arrowdown", "arrowup", "arrowleft", "arrowright"].includes(key.toLowerCase())) return false;
82+
if (!canvasFocused && ["tab", "enter", " ", "arrowdown", "arrowup", "arrowleft", "arrowright"].includes(key.toLowerCase())) return false;
7183

7284
// Redirect to the backend
7385
return true;
@@ -113,13 +125,20 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
113125
const inFloatingMenu = e.target instanceof Element && e.target.closest("[data-floating-menu-content]");
114126
if (!viewportPointerInteractionOngoing && inFloatingMenu) return;
115127

128+
const { target } = e;
129+
const newInCanvas = (target instanceof Element && target.closest("[data-canvas]")) instanceof Element && !targetIsTextField(window.document.activeElement);
130+
if (newInCanvas && !canvasFocused) {
131+
canvasFocused = true;
132+
app?.focus();
133+
}
134+
116135
const modifiers = makeKeyboardModifiersBitfield(e);
117136
editor.instance.on_mouse_move(e.clientX, e.clientY, e.buttons, modifiers);
118137
}
119138

120139
function onPointerDown(e: PointerEvent): void {
121140
const { target } = e;
122-
const inCanvas = target instanceof Element && target.closest("[data-canvas]");
141+
const isTargetingCanvas = target instanceof Element && target.closest("[data-canvas]");
123142
const inDialog = target instanceof Element && target.closest("[data-dialog-modal] [data-floating-menu-content]");
124143
const inTextInput = target === textInput;
125144

@@ -131,7 +150,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
131150

132151
if (!inTextInput) {
133152
if (textInput) editor.instance.on_change_text(textInputCleanup(textInput.innerText));
134-
else if (inCanvas) viewportPointerInteractionOngoing = true;
153+
else viewportPointerInteractionOngoing = isTargetingCanvas instanceof Element;
135154
}
136155

137156
if (viewportPointerInteractionOngoing) {
@@ -168,7 +187,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
168187

169188
function onMouseScroll(e: WheelEvent): void {
170189
const { target } = e;
171-
const inCanvas = target instanceof Element && target.closest("[data-canvas]");
190+
const isTargetingCanvas = target instanceof Element && target.closest("[data-canvas]");
172191

173192
// Redirect vertical scroll wheel movement into a horizontal scroll on a horizontally scrollable element
174193
// There seems to be no possible way to properly employ the browser's smooth scrolling interpolation
@@ -178,7 +197,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
178197
return;
179198
}
180199

181-
if (inCanvas) {
200+
if (isTargetingCanvas) {
182201
e.preventDefault();
183202
const modifiers = makeKeyboardModifiersBitfield(e);
184203
editor.instance.on_mouse_scroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
@@ -246,7 +265,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
246265
});
247266
}
248267

249-
function targetIsTextField(target: EventTarget | null): boolean {
268+
function targetIsTextField(target: EventTarget | HTMLElement | null): boolean {
250269
return target instanceof HTMLElement && (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable);
251270
}
252271

0 commit comments

Comments
 (0)