Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/debugger-at-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@noriginmedia/norigin-spatial-navigation-core': minor
---

Added `setDebug` and `setVisualDebug` methods to the `SpatialNavigation` to enable/disable at runtime the navigation decision logs and the canvas overlay showing bounding boxes and navigation paths.
40 changes: 40 additions & 0 deletions docs/api-reference/SpatialNavigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
setKeyMap,
setThrottle,
updateRtl,
setDebug,
setVisualDebug,
ROOT_FOCUS_KEY
} from '@noriginmedia/norigin-spatial-navigation-core';
```
Expand Down Expand Up @@ -310,6 +312,44 @@ See [RTL Support](../guides/rtl-support.md) for more.

---

## `setDebug(debug)`

Toggle debug mode at runtime. It logs navigation decisions to the browser console.

```typescript
setDebug(debug: boolean): void
```

```typescript
import { setDebug } from '@noriginmedia/norigin-spatial-navigation-react';

setDebug(true); // enable logging of navigation decisions
setDebug(false); // disable logging of navigation decisions
```

See [Debugging](../guides/debugging.md) for more.

---

## `setVisualDebug(visualDebug)`

Toggle visual debug mode at runtime. It draws a canvas overlay showing component bounding boxes and navigation paths.

```typescript
setVisualDebug(visualDebug: boolean): void
```

```typescript
import { setVisualDebug } from '@noriginmedia/norigin-spatial-navigation-react';

setVisualDebug(true); // enable visual debugger
setVisualDebug(false); // disable visual debugger
```

See [Debugging](../guides/debugging.md) for more.

---

## `ROOT_FOCUS_KEY`

A constant string (`'SN:ROOT'`) representing the root of the focus tree. Pass it to `setFocus` to boot navigation.
Expand Down
28 changes: 28 additions & 0 deletions docs/guides/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ init({ debug: true, visualDebug: false });

Disable this in production, it generates a significant volume of log output.

### Enabling Debug Logger at Runtime

Use `setDebug` to enable or disable logging navigation decisions to the browser console. This is useful when users can toggle debug mode while the application is running.

```typescript
import { setDebug } from '@noriginmedia/norigin-spatial-navigation-core';

// Turn on logging navigation decisions to the browser console
setDebug(true);

// Turn off logging navigation decisions to the browser console
setDebug(false);
```

---

## Visual Debugger
Expand All @@ -36,6 +50,20 @@ init({ debug: false, visualDebug: true });

The visual debugger is the fastest way to diagnose unexpected navigation behavior because you can see exactly what the library "sees" in terms of component positions.

### Enabling Visual Debugger at Runtime

Use `setVisualDebug` to enable or disable drawing a canvas overlay showing component bounding boxes and navigation paths. This is useful when users can toggle visual debugger while the application is running.

```typescript
import { setVisualDebug } from '@noriginmedia/norigin-spatial-navigation-core';

// Turn on drawing a canvas overlay
setVisualDebug(true);

// Turn off drawing a canvas overlay
setVisualDebug(false);
```

---

## Common Problems and Solutions
Expand Down
76 changes: 56 additions & 20 deletions packages/core/src/SpatialNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ class SpatialNavigationService {

private visualDebugger: VisualDebugger;

private animationFrameRequestId: number;

/**
* Focus key of the currently focused element
*/
Expand Down Expand Up @@ -683,6 +685,7 @@ class SpatialNavigationService {

this.debug = false;
this.visualDebugger = null;
this.animationFrameRequestId = -1;

this.logIndex = 0;

Expand Down Expand Up @@ -722,31 +725,14 @@ class SpatialNavigationService {
customDistanceCalculationFunction;
this.onUtterText = onUtterText ?? undefined;

this.debug = debug;
this.setDebug(debug);

if (!this.nativeMode) {
if (Number.isInteger(throttleParam) && throttleParam > 0) {
this.throttle = throttleParam;
}
this.bindEventHandlers();
if (visualDebug) {
this.visualDebugger = new VisualDebugger(this.writingDirection);
const draw = () => {
requestAnimationFrame(() => {
this.visualDebugger.clearLayouts();
forOwn(this.focusableComponents, (component, focusKey) => {
this.visualDebugger.drawLayout(
component.layout,
focusKey,
component.parentFocusKey
);
});
draw();
});
};

draw();
}
this.setVisualDebug(visualDebug);
} else {
console.warn(
'nativeMode option is deprecated and will be removed in the next version.'
Expand Down Expand Up @@ -783,6 +769,7 @@ class SpatialNavigationService {
this.keyMap = DEFAULT_KEY_MAP;
this.onUtterText = undefined;

this.setVisualDebug(false);
this.unbindEventHandlers();
}
}
Expand Down Expand Up @@ -1898,6 +1885,53 @@ class SpatialNavigationService {
updateRtl(rtl: boolean) {
this.writingDirection = rtl ? WritingDirection.RTL : WritingDirection.LTR;
}

setDebug(debug: boolean) {
if (!this.enabled) {
return;
}

this.debug = debug;
}

setVisualDebug(visualDebug: boolean) {
if (!this.enabled) {
return;
}
if (visualDebug === !!this.visualDebugger) {
return;
}

if (visualDebug) {
this.visualDebugger = new VisualDebugger(this.writingDirection);
const draw = () => {
this.animationFrameRequestId = requestAnimationFrame(() => {
if (!this.visualDebugger) {
return;
}

this.visualDebugger.clearLayouts();
forOwn(this.focusableComponents, (component, focusKey) => {
this.visualDebugger.drawLayout(
component.layout,
focusKey,
component.parentFocusKey
);
});
draw();
});
};

draw();
} else {
if (this.animationFrameRequestId > -1) {
cancelAnimationFrame(this.animationFrameRequestId);
this.animationFrameRequestId = -1;
}
this.visualDebugger.destroy();
this.visualDebugger = null;
}
}
}

/**
Expand All @@ -1917,5 +1951,7 @@ export const {
updateAllLayouts,
getCurrentFocusKey,
doesFocusableExist,
updateRtl
updateRtl,
setDebug,
setVisualDebug
} = SpatialNavigation;
25 changes: 23 additions & 2 deletions packages/core/src/VisualDebugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ interface NodeLayout {
height: number;
}

const DEBUG_CANVAS_ID = 'sn-debug';
const LAYOUTS_CANVAS_ID = 'sn-layouts';

class VisualDebugger {
private debugCtx: CanvasRenderingContext2D;

Expand All @@ -25,12 +28,12 @@ class VisualDebugger {
constructor(writingDirection: WritingDirection) {
if (hasDOM) {
this.debugCtx = VisualDebugger.createCanvas(
'sn-debug',
DEBUG_CANVAS_ID,
'1010',
writingDirection
);
this.layoutsCtx = VisualDebugger.createCanvas(
'sn-layouts',
LAYOUTS_CANVAS_ID,
'1000',
writingDirection
);
Expand Down Expand Up @@ -83,6 +86,24 @@ class VisualDebugger {
this.layoutsCtx.clearRect(0, 0, WIDTH, HEIGHT);
}

destroy() {
this.clear();
this.clearLayouts();

this.debugCtx = null;
this.layoutsCtx = null;

const debugCanvas = document.getElementById(DEBUG_CANVAS_ID);
if (debugCanvas) {
debugCanvas.parentNode.removeChild(debugCanvas);
}

const layoutsCanvas = document.getElementById(LAYOUTS_CANVAS_ID);
if (layoutsCanvas) {
layoutsCanvas.parentNode.removeChild(layoutsCanvas);
}
}

drawLayout(layout: NodeLayout, focusKey: string, parentFocusKey: string) {
if (!hasDOM) {
return;
Expand Down
Loading