import QtQuick import NomadInput 1.0 // NavigationManager // This thing's job is to keep track of the "physical" logcation of every UI element that can be navigated to, and allow for directional // navigation of the on-screen UI. It requires every selectable object on-screen to register itself so that we can keep track of what is // where, so much of it's functionality is registering and unregistering and reviewing that registry of items. QtObject { id: manager property Item rootItem: null // this is necesarry for the sake of having a global co-ordinate system by which to keep track of stuff property list items property var currentItem: null // set the focus on a particular item in the registry function focusItem(item) { if (!item) { return } currentItem = item // this is a little helper function that is called on anything that's focused (Assuming the object has such a function). if (typeof item.onNavigationFocused === "function") { item.onNavigationFocused() } } function registerItem(item) { if (!item) { return } // double check that we haven't registered this yet for (let i = 0; i < items.length; i++) { if (items[i] === item) { return } } items.push(item) } function unregisterItem(item) { if (!item) { return } for (let i = 0; i < items.length; i++) { if (items[i] === item) { items.splice(i, 1) if (currentItem === item) { currentItem = null } return; } } } function move(action) { // just to make sure everything is set up right if (!currentItem || !rootItem) { return } // this is our direction vector by which to move. // since I made it like this, I could probably put analog directions into this easily. // don't know if I would actually want to, but I probably could easily. let dx = 0 let dy = 0 // analog input aside, this just takes our cardinal input directiosn and sets the vector accordingly switch(action) { case InputManager.Left: dx = -1 break case InputManager.Right: dx = 1 break case InputManager.Up: dy = -1 break case InputManager.Down: dy = 1 break default: return } // we want the center point of the actual item, not just the coordinates it's set to. better for directionality. let currentCenter = currentItem.mapToItem(rootItem, currentItem.width / 2, currentItem.height / 2) let bestItem = null let bestScore = Infinity // sorts through all our items to see which is closest for (let i = 0; i < items.length; i++) { let candidate = items[i] // sanity checks if (!candidate) { continue } if (candidate === currentItem) { continue } if (!candidate.visible || !candidate.enabled) { continue } // we want the center point of the item, just like before let candidateCenter = candidate.mapToItem(rootItem, candidate.width / 2, candidate.height / 2) let differenceX = candidateCenter.x - currentCenter.x let differenceY = candidateCenter.y - currentCenter.y let forwardDistance let sidewaysDistance if (dx !== 0) { // Horizontal directions forwardDistance = differenceX * dx sidewaysDistance = Math.abs(differenceY) } else { // vectical directions forwardDistance = differenceY * dy sidewaysDistance = Math.abs(differenceX) } if (forwardDistance <= 0) { // if it's on the wrong side of the object just discard it continue } let score = forwardDistance + sidewaysDistance * 2 // this might need to be tuned later. prioritizes vertical alignment if (score < bestScore) { bestScore = score bestItem = candidate } } if (bestItem) { focusItem(bestItem) } } }