feat(buttonNavigation): add blocked paths to avoid diagonal navigtion in menus
This commit is contained in:
@@ -69,12 +69,12 @@ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
|
|||||||
right: "optionsGbaMode",
|
right: "optionsGbaMode",
|
||||||
},
|
},
|
||||||
optionsGbaMode: {
|
optionsGbaMode: {
|
||||||
down: "options",
|
down: ["options", false],
|
||||||
left: "optionsLanguage",
|
left: "optionsLanguage",
|
||||||
up: "optionsStartUp",
|
up: ["optionsStartUp", false],
|
||||||
},
|
},
|
||||||
optionsStartUp: {
|
optionsStartUp: {
|
||||||
right: "optionsGbaMode",
|
right: ["optionsGbaMode", false],
|
||||||
down: "optionsLanguage",
|
down: "optionsLanguage",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -89,12 +89,12 @@ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
|
|||||||
right: "clockTime",
|
right: "clockTime",
|
||||||
},
|
},
|
||||||
clockTime: {
|
clockTime: {
|
||||||
down: "clock",
|
down: ["clock", false],
|
||||||
left: "clockAlarm",
|
left: "clockAlarm",
|
||||||
up: "clockDate",
|
up: ["clockDate", false],
|
||||||
},
|
},
|
||||||
clockDate: {
|
clockDate: {
|
||||||
right: "clockTime",
|
right: ["clockTime", false],
|
||||||
down: "clockAlarm",
|
down: "clockAlarm",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -104,8 +104,8 @@ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
|
|||||||
up: "userName",
|
up: "userName",
|
||||||
},
|
},
|
||||||
userBirthday: {
|
userBirthday: {
|
||||||
down: "user",
|
down: ["user", false],
|
||||||
up: "userColor",
|
up: ["userColor", false],
|
||||||
right: "userName",
|
right: "userName",
|
||||||
},
|
},
|
||||||
userName: {
|
userName: {
|
||||||
@@ -115,13 +115,13 @@ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
|
|||||||
up: "userColor",
|
up: "userColor",
|
||||||
},
|
},
|
||||||
userMessage: {
|
userMessage: {
|
||||||
down: "user",
|
down: ["user", false],
|
||||||
left: "userName",
|
left: "userName",
|
||||||
up: "userColor",
|
up: ["userColor", false],
|
||||||
},
|
},
|
||||||
userColor: {
|
userColor: {
|
||||||
left: "userBirthday",
|
left: ["userBirthday", false],
|
||||||
right: "userMessage",
|
right: ["userMessage", false],
|
||||||
down: "userName",
|
down: "userName",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
navigation: Record<
|
navigation: Record<
|
||||||
keyof T,
|
keyof T,
|
||||||
{
|
{
|
||||||
up?: keyof T | "last";
|
up?: keyof T | "last" | [buttonName: keyof T, blocked: boolean];
|
||||||
down?: keyof T | "last";
|
down?: keyof T | "last" | [buttonName: keyof T, blocked: boolean];
|
||||||
left?: keyof T;
|
left?: keyof T | [buttonName: keyof T, blocked: boolean];
|
||||||
right?: keyof T;
|
right?: keyof T | [buttonName: keyof T, blocked: boolean];
|
||||||
horizontalMode?: "navigate" | "preview";
|
horizontalMode?: "navigate" | "preview";
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
@@ -30,6 +30,16 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
const selectorPosition = ref<Rect>(buttons[initialButton]!);
|
const selectorPosition = ref<Rect>(buttons[initialButton]!);
|
||||||
const nextButton = ref<Entry | undefined>();
|
const nextButton = ref<Entry | undefined>();
|
||||||
|
|
||||||
|
const getNavigationTarget = (
|
||||||
|
value: Entry | [buttonName: Entry, blocked: boolean] | undefined,
|
||||||
|
): { target: Entry | "last"; blocked: boolean } | null => {
|
||||||
|
if (!value) return null;
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return { target: value[0]!, blocked: value[1] === false };
|
||||||
|
}
|
||||||
|
return { target: value, blocked: false };
|
||||||
|
};
|
||||||
|
|
||||||
const buildNavigationGraph = (
|
const buildNavigationGraph = (
|
||||||
nav: typeof navigation,
|
nav: typeof navigation,
|
||||||
): Map<Entry, Set<Entry>> => {
|
): Map<Entry, Set<Entry>> => {
|
||||||
@@ -43,31 +53,42 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
edges.set(button, new Set());
|
edges.set(button, new Set());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navConfig.up && navConfig.up !== "last") {
|
const up = getNavigationTarget(navConfig.up);
|
||||||
edges.get(button)!.add(navConfig.up);
|
const down = getNavigationTarget(navConfig.down);
|
||||||
|
const left = getNavigationTarget(navConfig.left);
|
||||||
|
const right = getNavigationTarget(navConfig.right);
|
||||||
|
|
||||||
|
// handle blocked paths
|
||||||
|
if (up && up.target !== "last" && !up.blocked) {
|
||||||
|
edges.get(button)!.add(up.target);
|
||||||
}
|
}
|
||||||
if (navConfig.down && navConfig.down !== "last") {
|
if (down && down.target !== "last" && !down.blocked) {
|
||||||
edges.get(button)!.add(navConfig.down);
|
edges.get(button)!.add(down.target);
|
||||||
}
|
}
|
||||||
if (navConfig.left) {
|
if (left && !left.blocked) {
|
||||||
edges.get(button)!.add(navConfig.left);
|
edges.get(button)!.add(left.target);
|
||||||
}
|
}
|
||||||
if (navConfig.right) {
|
if (right && !right.blocked) {
|
||||||
edges.get(button)!.add(navConfig.right);
|
edges.get(button)!.add(right.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navConfig.up === "last" || navConfig.down === "last") {
|
if (up?.target === "last" || down?.target === "last") {
|
||||||
for (const [otherButton, otherNav] of Object.entries(nav) as [
|
for (const [otherButton, otherNav] of Object.entries(nav) as [
|
||||||
Entry,
|
Entry,
|
||||||
(typeof nav)[Entry],
|
(typeof nav)[Entry],
|
||||||
][]) {
|
][]) {
|
||||||
if (otherButton === button) continue;
|
if (otherButton === button) continue;
|
||||||
|
|
||||||
|
const otherUp = getNavigationTarget(otherNav.up);
|
||||||
|
const otherDown = getNavigationTarget(otherNav.down);
|
||||||
|
const otherLeft = getNavigationTarget(otherNav.left);
|
||||||
|
const otherRight = getNavigationTarget(otherNav.right);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
otherNav.up === button ||
|
(otherUp?.target === button && !otherUp.blocked) ||
|
||||||
otherNav.down === button ||
|
(otherDown?.target === button && !otherDown.blocked) ||
|
||||||
otherNav.left === button ||
|
(otherLeft?.target === button && !otherLeft.blocked) ||
|
||||||
otherNav.right === button
|
(otherRight?.target === button && !otherRight.blocked)
|
||||||
) {
|
) {
|
||||||
edges.get(button)!.add(otherButton);
|
edges.get(button)!.add(otherButton);
|
||||||
}
|
}
|
||||||
@@ -203,72 +224,90 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
let targetButton: Entry | undefined;
|
let targetButton: Entry | undefined;
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "NDS_UP":
|
case "NDS_UP": {
|
||||||
if (!currentNav.up) return;
|
const upConfig = getNavigationTarget(currentNav.up);
|
||||||
|
if (!upConfig) return;
|
||||||
|
|
||||||
if (currentNav.up === "last") {
|
if (upConfig.target === "last") {
|
||||||
if (nextButton.value) {
|
if (nextButton.value) {
|
||||||
targetButton = nextButton.value;
|
targetButton = nextButton.value;
|
||||||
} else {
|
} else {
|
||||||
targetButton = currentNav.left ?? currentNav.right;
|
const leftConfig = getNavigationTarget(currentNav.left);
|
||||||
|
const rightConfig = getNavigationTarget(currentNav.right);
|
||||||
|
targetButton = leftConfig?.target ?? rightConfig?.target;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (navigation[currentNav.up].down === "last") {
|
const targetNav = navigation[upConfig.target];
|
||||||
|
const targetDownConfig = getNavigationTarget(targetNav?.down);
|
||||||
|
if (targetDownConfig?.target === "last") {
|
||||||
nextButton.value = selectedButton.value as Entry;
|
nextButton.value = selectedButton.value as Entry;
|
||||||
}
|
}
|
||||||
targetButton = currentNav.up;
|
targetButton = upConfig.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "NDS_DOWN":
|
case "NDS_DOWN": {
|
||||||
if (!currentNav.down) return;
|
const downConfig = getNavigationTarget(currentNav.down);
|
||||||
|
if (!downConfig) return;
|
||||||
|
|
||||||
if (currentNav.down === "last") {
|
if (downConfig.target === "last") {
|
||||||
if (nextButton.value) {
|
if (nextButton.value) {
|
||||||
targetButton = nextButton.value;
|
targetButton = nextButton.value;
|
||||||
} else {
|
} else {
|
||||||
targetButton = currentNav.left ?? currentNav.right;
|
const leftConfig = getNavigationTarget(currentNav.left);
|
||||||
|
const rightConfig = getNavigationTarget(currentNav.right);
|
||||||
|
targetButton = leftConfig?.target ?? rightConfig?.target;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (navigation[currentNav.down].up === "last") {
|
const targetNav = navigation[downConfig.target];
|
||||||
|
const targetUpConfig = getNavigationTarget(targetNav?.up);
|
||||||
|
if (targetUpConfig?.target === "last") {
|
||||||
nextButton.value = selectedButton.value as Entry;
|
nextButton.value = selectedButton.value as Entry;
|
||||||
}
|
}
|
||||||
targetButton = currentNav.down;
|
targetButton = downConfig.target;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "NDS_LEFT":
|
case "NDS_LEFT": {
|
||||||
if (!currentNav.left) return;
|
const leftConfig = getNavigationTarget(currentNav.left);
|
||||||
|
if (!leftConfig) return;
|
||||||
|
|
||||||
if (currentNav.horizontalMode === "preview") {
|
if (currentNav.horizontalMode === "preview") {
|
||||||
nextButton.value = currentNav.left;
|
nextButton.value = leftConfig.target;
|
||||||
} else {
|
} else {
|
||||||
targetButton = currentNav.left;
|
targetButton = leftConfig.target;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "NDS_RIGHT":
|
case "NDS_RIGHT": {
|
||||||
if (!currentNav.right) return;
|
const rightConfig = getNavigationTarget(currentNav.right);
|
||||||
|
if (!rightConfig) return;
|
||||||
|
|
||||||
if (currentNav.horizontalMode === "preview") {
|
if (currentNav.horizontalMode === "preview") {
|
||||||
nextButton.value = currentNav.right;
|
nextButton.value = rightConfig.target;
|
||||||
} else {
|
} else {
|
||||||
targetButton = currentNav.right;
|
targetButton = rightConfig.target;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "NDS_START":
|
case "NDS_START":
|
||||||
case "NDS_A":
|
case "NDS_A": {
|
||||||
onButtonClick?.(selectedButton.value);
|
onButtonClick?.(selectedButton.value);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetButton) {
|
if (targetButton) {
|
||||||
animateToButton(targetButton, null);
|
const path = findPath(graph, selectedButton.value, targetButton);
|
||||||
|
animateToButton(targetButton, path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user