HorizontalMenu Selected Tab Highlighting

I'm working with the Horizontal Menu component and im trying to get something similar to what is on the tab containers where when you select a tab it will highlight. I've gotten a script working that does this for are top level of tabs but if I have more then one layer of tabs the others don't get highlighted and Im looking for some help making it so that they will. I've made a for loop on a test script and was able to get one more layer of those tabs to highlight but Im hopping there is a way to make it dynamic so I don't need to add a for loop each time we add another layer to are menu. If anyone has already made a script for the menu that does what Im talking about.

(Bellow is the script i made that was able to get the first two layers of the menu to be highlighted but things fell apart when i tried adding the 3rd layer)

	# look through the entire menu tree
	for i in range(len(self.props.items)):
		# if the current index is the index we are looking for
		if i == currentValue.value[0].value:
			# change background color to yellow
			self.props.items[i].style.classes = "Components/HorizontalMenu/HorizontalMenu_Selected"
			# if we have a sub index tree selected
			if self.props.items[i].items is not None:
				# perform the same operation as above
				for j in range(len(self.props.items[i].items)):
					if j == currentValue.value[1].value:
						self.props.items[i].items[j].style.classes = "Components/HorizontalMenu/HorizontalMenu_Selected"
						if self.props.items[i].items[j].items is not None:
							# perform the same operation as above
							for t in range(len(self.props.items[i].items[j].items)):
								if t == currentValue.value[1].value:
									self.props.items[i].items[j].items[t].style.classes = "Components/HorizontalMenu/HorizontalMenu_Selected"
								else:
									self.props.items[i].items[j].items[t].style.classes = "Components/HorizontalMenu/HorizontalMenu_Deselected"
					# deselect the previous selected tabs
					else:
						self.props.items[i].items[j].style.classes = "Components/HorizontalMenu/HorizontalMenu_Deselected"
		else:
			# change the background color to black
			self.props.items[i].style.classes = "Components/HorizontalMenu/HorizontalMenu_Deselected"
			if self.props.items[i].items is not None:
				# perform the same operation as above
				for j in range(len(self.props.items[i].items)):
					self.props.items[i].items[j].style.classes = "Components/HorizontalMenu/HorizontalMenu_Deselected"
					if self.props.items[i].items[j].items is not None:
						# perform the same operation as above
						for t in range(len(self.props.items[i].items[j].items)):
							self.props.items[i].items[j].items[t].style.classes = "Components/HorizontalMenu/HorizontalMenu_Deselected"

(Bellow is another attempt at the script above but is slightly different and doesn't have a for loop implemented yet.)

	if len(currentValue.value) > 0:
	    # grab index out of the list for top-level item
	    itemIndex = currentValue.value[0].value
#	    # highlight the corresponding menu item's style
	    self.props.items[itemIndex]["style"]["classes"] = "Components/HorizontalMenu/HorizontalMenu_Selected"
	
	# now, do similar logic for unhighlighting
	if len(previousValue.value) > 0:
	    itemIndex = previousValue.value[0].value
	    self.props.items[itemIndex]["style"]["classes"] = "Components/HorizontalMenu/HorizontalMenu_Deselected"

Best bet is to make a recursive function. Entirely untested bash of code that I use for highlighting searched terms in a tree display (which has the same item format as the menu):

SELECTED_CLASS = "Components/HorizontalMenu/HorizontalMenu_Selected"
UNSELECTED_CLASS = "Components/HorizontalMenu/HorizontalMenu_Deselected"

def recursiveSetHighlight(items, selection, idx=0):
	"""
	Recursively searches through a menu structure and highlights selected item and parent items
	"""
	highlightParent = False
	for itemIdx, item in enumerate(items):
		if idx < len(selection) and itemIdx == selection[idx]:
			item['style']['classes'] = SELECTED_CLASS
			highlightParent = True

		# Remove leading spaces left over from last search pass
		else:
			item['style']['classes'] = UNSELECTED_CLASS

		if len(item['items']):
			item['items'], childHasHighlight = recursiveSetHighlight(item['items'], selection, idx+1)
			# If any child has a highlight, mark that the parent of this item needs to be highlighted
			if childHasHighlight:
				item['style']['classes'] = SELECTED_CLASS
				highlightParent = True

	return items, highlightParent

selection is expecting a list of node indexes, like [0,1,2,3,0]. Based on your code you could probably do [item.value for item in currentValue.value] to convert your selection data to the expected format.

The expected use of this function is

selection = [item.value for item in currentValue.value]
cleanedStructure = project.util.sanitizeIgnitionObject(self.props.items)
highlightedStructure = project.menu.recursiveSetHighlight(cleanedStructure, selection)[0]
self.props.items = highlightedStructure

Util Function:

def sanitizeIgnitionObject(element):
	if hasattr(element, '__iter__'):
		if hasattr(element, 'keys'):
			return dict((k, sanitizeIgnitionObject(element[k])) for k in element.keys())
		else:
			return list(sanitizeIgnitionObject(x) for x in element)
	return element
1 Like

I will give this a try thanks for the response.

Do yourself a quick favor before testing this code, copy your menu items to another custom property somewhere as a backup in case the function has issues.

If for some reason it mangles your menu items then you can just copy/paste from the backup instead of rebuilding your menu by hand

Good idea thanks

Ok I implemented the code you had given and it seem to work in selecting and deselecting but there is a problem if you have two tabs that can then go a 3rd layer deep they will both be highlighted along with what ever tab you select under on the respective numbered one under the other will also be highlighted.



Ah, I messed up. My code is only looking that the current level item index matches the number in the selection list at the same level, without checking if the parent is selected.

Originally the code was for looking for text in each item in a tree, so it would highlight multiple things in every level. I just took that and modified it for my example code. Whoops.

Fixed code, also smaller:

def recursiveSetHighlight(items, selection, parentSelected=False, idx=0):
	"""
	Recursively searches through a menu structure and highlights selected item and its parents
	"""
	selected = False
	for itemIdx, item in enumerate(items):
		# Allow item highlight if this is first level of menu or
		# item index matches number at same level in selection list
		if (parentSelected or idx == 0) and idx < len(selection) and itemIdx == selection[idx]:
			item['style']['classes'] = SELECTED_CLASS
			selected = True

		# If this is not the item we want, unselect it
		else:
			item['style']['classes'] = UNSELECTED_CLASS
			selected = False

		# Apply Highlighting to child items
		if len(item['items']):
			item['items'] = recursiveSetHighlight(item['items'], selection, selected, idx+1)

	return items

Remove the [0] after recursiveSetHighlight in your call to use the function:

selection = [item.value for item in currentValue.value]
cleanedStructure = project.util.sanitizeIgnitionObject(self.props.items)
highlightedStructure = project.menu.recursiveSetHighlight(cleanedStructure, selection)
self.props.items = highlightedStructure

Edit: added missing selected = False in else statement

1 Like

Prefect thanks for the help the only thing that i noticed was it was setting some on the 3rd level of it at the same time but if you just add a

selected = False

in the else it starts to work as intended. thanks for the help

def recursiveSetHighlight(items, selection, parentSelected=False, idx=0):
		"""
		Recursively searches through a menu structure and highlights selected item and its parents
		"""
		SELECTED_CLASS = "Components/HorizontalMenu/HorizontalMenu_Selected"
		UNSELECTED_CLASS = "Components/HorizontalMenu/HorizontalMenu_Deselected"
		selected = False
		for itemIdx, item in enumerate(items):
			# Allow item highlight if this is first level of menu or
			# item index matches number at same level in selection list
			if (parentSelected or idx == 0) and idx < len(selection) and itemIdx == selection[idx]:
				item['style']['classes'] = SELECTED_CLASS
				selected = True
	
			# If this is not the item we want, unselect it
			else:
				item['style']['classes'] = UNSELECTED_CLASS
				selected = False
	
			# Apply Highlighting to child items
			if len(item['items']):
				item['items'] = recursiveSetHighlight(item['items'], selection, selected, idx+1)
	
		return items
1 Like