[Perspective] Highlight current view in Menu Bar

I don’t have this in a production system yet, but wanted to throw it out there in case it helps anyone. Instead of binding to every item in the Horizontal Menu and Menu Tree, I wrote a couple scripts that rely on the menu onItemClicked event and the event.path property within that event. The event.path is a list of item indexes that leads to the selected item.

First I created a custom session property for holding the current selected path (this is my main menu, so it’s always visible and it comes in handy to have that path available). I then added a message handler on the menu called menu-main-refresh-selection with the following script :

def onMessageReceived(self, payload):
	"""
	This method will be called when a message with the matching type code
	arrives at this component.

	Arguments:
		self: A reference to this component
		payload: The data object sent along with the message
	"""
	def items_loop(parent_ref, path):
		"""Recursively set menu item style since you do not know ahead of time how many layers the menu may have.
		
		Args:
			parent_ref (Component Reference): Reference to the items component property. This reference is used to recursively
				navigate the menu items.
			path (list, ints): From parent event and used to know which items in the path are part of the selection "route".
		"""
		
		for i in range(len(parent_ref)):
			# If item is in the selection path, set to active style, else inactive style.
			if len(path) and i == path[0]:
				parent_ref[i].style.classes = "Menu/Main/Item_Active"
			else:
				parent_ref[i].style.classes = "Menu/Main/Item_Inactive"
			
			# If there are nested menu items, recursive call to set the nested items accordingly.
			if len(parent_ref[i]):
				# If path is not empty and currently looking at the selected path item, pass in the path minus the first
				# item in the path so that we know what to properly highlight in the next layer of recursion. Else, pass 
				# an empty list to indicate everything should be set to an inactive style.
				if len(path) > 1 and i == path[0]:
					items_loop(parent_ref[i].items, path[1:])
				else:
					items_loop(parent_ref[i].items, [])
		return
	
	# If there is no path payload, set menu selection to first item				
	path = payload.get("path")
	if path is None or not len(path):
		path = [0]
	
	self.session.custom.menu_main_path = path
	items_loop(self.props.items, path)

Used a message handler so that I can refresh the selection from anywhere, easily (i.e. - on startup, etc). The menu then has an onItemClicked event script with:

# Set item style. Only want to change styles if the event resulted in navigating to a 
# new Perspective page (external pages do not start with "/", so I don't want to change 
# the selection if it is an external webpage)
if event.enabled and event.target not in ["", None] and event.target[0] == "/":
	system.perspective.sendMessage("menu-main-refresh-selection", {"path": event.path})

Nice thing is, you can add/remove items to the menu without having to update any bindings or scripts (works on as many nested menus as you want). It does loop through all items every time, so I’d imagine it wouldn’t be very performant on large menus. On normal sized menus, it has been plenty fast for my needs. I could prevent this from ever being an issue by only navigating through the current and new paths (should not have to worry about any other items outside of these two paths). It just hasn’t been a priority.

1 Like