/* Copyright (c) 2021 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "gamemath/Scalar.h" #include "shell/ShellKeyCodes.h" #include "uitoolkit/UIMenuBar.h" #include "uitoolkit/UIToolkitAppearance.h" #include "uitoolkit/UIToolkitContext.h" #include "uitoolkit/UIToolkitDrawing.h" #include "utilities/IOUtilities.h" #include #include #include #include #define stemobject_implementation UIMenuBar v_begin(); v_func(dispose); v_func(hitTest); v_func(mouseDown); v_func(mouseUp); v_func(mouseMoved); v_func(mouseDragged); v_func(scrollWheel); v_func(keyDown); v_func(keyModifiersChanged); v_func(menuActionDown); v_func(menuActionUp); v_func(menuDirectionDown); v_func(acceptsFocus); v_func(getBounds); v_func(getFocusBounds); v_func(draw); v_func(listRenderables); v_end(); #define SEPARATOR_HEIGHT 1 #define MOUSE_UP_CLOSE_INTERVAL 0.5 #define SUBMENU_OPEN_DELAY 0.3 #define CONTEXT_MENU_OPEN_INDEX (UINT_MAX - 1) struct UIMenuContents { unsigned int itemCount; struct UIMenuItem_private * items; unsigned int highlightIndex; Vector2f topLeft; Vector2f size; bool validationDirty; bool layoutDirty; bool isSubmenu; float scrollPosition; float openTopOffset; }; struct UIMenuItem_private { String title; int identifier; bool enabled; bool checked; UIKeyShortcut * shortcut; String shortcutString; float offsetY; float titleWidth; UIAtlasEntry customIcon; Color4f textColor; bool animateWhenInvoked; unsigned int alternateTitleCount; struct { String title; unsigned int modifiers; } * alternateTitles; struct UIMenuContents contents; }; struct UIMenu_private { String title; int identifier; float offsetX; float titleWidth; struct UIMenuContents contents; }; UIMenuBar * UIMenuBar_create(unsigned int menuCount, UIMenu * menus, Vector2f position, Vector2f relativeOrigin, float width, UIMenuBar_actionCallback actionCallback, UIMenuBar_validateCallback validateCallback, UIMenuBar_contextMenuCallback contextMenuCallback, void * callbackContext, UIAppearance appearance) { stemobject_create_implementation(init, menuCount, menus, position, relativeOrigin, width, actionCallback, validateCallback, contextMenuCallback, callbackContext, appearance) } static String createKeyShortcutString(UIKeyShortcut * shortcut, bool localized) { if (shortcut == NULL) { return STR_NULL; } char shortcutString[40] = {0}; UIKeyShortcut_getHumanReadableString(shortcut, localized, false, shortcutString, sizeof(shortcutString)); return String_copy(STR(shortcutString)); } static struct UIMenuContents copyMenuContents(UIMenuBar * self, unsigned int itemCount, struct UIMenuItem * items, bool isSubmenu); static float measureItemTitleWidth(UIMenuBar * self, struct UIMenuItem_private item, UITypeface * typeface, Vector2f itemPadding, float shortcutPadding) { if (item.title.bytes == NULL) { return 0.0f; } float titleWidth = call_virtual(measureString, typeface, item.title); for (unsigned int alternateTitleIndex = 0; alternateTitleIndex < item.alternateTitleCount; alternateTitleIndex++) { float alternateTitleWidth = call_virtual(measureString, typeface, item.alternateTitles[alternateTitleIndex].title); if (alternateTitleWidth > titleWidth) { titleWidth = alternateTitleWidth; } } titleWidth += itemPadding.x * 4 + self->leftRightIconPadding * 2; if (item.shortcutString.bytes != NULL && item.contents.itemCount == 0) { titleWidth += call_virtual(measureString, typeface, item.shortcutString) - self->leftRightIconPadding + shortcutPadding; } return ceilf(titleWidth); } static struct UIMenuItem_private copyMenuItem(UIMenuBar * self, UIMenuItem item) { struct UIMenuItem_private copy; copy.title = String_copy(item.title); copy.identifier = item.identifier; copy.enabled = item.enabled; copy.checked = item.checked; copy.customIcon.bounds = RECT4f_EMPTY; copy.customIcon.size = VECTOR2f_ZERO; copy.textColor = getAppearanceColor4f(self->appearance, UIMenuBar_textColor); copy.shortcut = UIKeyShortcut_copy(item.shortcut); copy.shortcutString = createKeyShortcutString(copy.shortcut, self->useLocalizedKeyShortcuts); copy.alternateTitleCount = 0; copy.alternateTitles = NULL; copy.animateWhenInvoked = copy.shortcut != NULL && (copy.shortcut->modifiers & MODIFIER_PLATFORM_MENU_COMMAND_BIT); copy.contents = copyMenuContents(self, item.subitemCount, item.subitems, true); UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); copy.titleWidth = measureItemTitleWidth(self, copy, typeface, getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding), getAppearanceFloat(self->appearance, UIMenuBar_itemShortcutPadding)); return copy; } static struct UIMenuContents copyMenuContents(UIMenuBar * self, unsigned int itemCount, struct UIMenuItem * items, bool isSubmenu) { struct UIMenuContents contents; contents.itemCount = itemCount; contents.highlightIndex = UINT_MAX; contents.size = VECTOR2f_ZERO; contents.validationDirty = true; contents.layoutDirty = false; contents.isSubmenu = isSubmenu; contents.scrollPosition = 0.0f; contents.openTopOffset = 0.0f; if (itemCount == 0) { contents.items = NULL; } else { contents.items = malloc(itemCount * sizeof(*contents.items)); UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface); Vector2f itemPadding = getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding); for (unsigned int itemIndex = 0; itemIndex < itemCount; itemIndex++) { contents.items[itemIndex] = copyMenuItem(self, items[itemIndex]); contents.items[itemIndex].offsetY = -contents.size.y; if (contents.items[itemIndex].title.bytes == NULL) { contents.size.y += SEPARATOR_HEIGHT; } else { if (contents.items[itemIndex].titleWidth > contents.size.x) { contents.size.x = contents.items[itemIndex].titleWidth; } contents.size.y += lineHeight + itemPadding.y * 2; } } } return contents; } static void assignSubmenuPositions(UIMenuBar * self, struct UIMenuContents * contents, Vector2f topLeft, float menuTop) { contents->topLeft = topLeft; for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { if (contents->items[itemIndex].contents.itemCount > 0) { Vector2f submenuTopLeft = VECTOR2f(topLeft.x + contents->size.x, topLeft.y + contents->items[itemIndex].offsetY); assignSubmenuPositions(self, &contents->items[itemIndex].contents, submenuTopLeft, menuTop); } } } static Rect4f getMaxBounds(UIMenuBar * self) { return Rect4f_round(Rect4f_inset(getAppearanceRect4f(self->appearance, UIToolkit_uiBounds), V2f(6, 6))); } bool UIMenuBar_init(UIMenuBar * self, unsigned int menuCount, UIMenu * menus, Vector2f position, Vector2f relativeOrigin, float width, UIMenuBar_actionCallback actionCallback, UIMenuBar_validateCallback validateCallback, UIMenuBar_contextMenuCallback contextMenuCallback, void * callbackContext, UIAppearance appearance) { call_super(init, self, position, relativeOrigin, appearance); Vector2f submenuArrowSize = getAppearanceAtlasEntry(self->appearance, UIMenuBar_menuSubmenuArrow).size; Vector2f checkmarkSize = getAppearanceAtlasEntry(self->appearance, UIMenuBar_menuCheckmark).size; self->leftRightIconPadding = submenuArrowSize.x; if (checkmarkSize.x > self->leftRightIconPadding) { self->leftRightIconPadding = checkmarkSize.x; } self->useLocalizedKeyShortcuts = true; self->menuCount = menuCount; self->menus = malloc(self->menuCount * sizeof(*self->menus)); float offsetX = 0.0f; UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float menuPadding = getAppearanceFloat(self->appearance, UIMenuBar_menuPadding); float menuHeight = getAppearanceFloat(self->appearance, UIMenuBar_menuHeight); float menuTop = call_virtual(getBounds, self).yMax; for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { self->menus[menuIndex].title = String_copy(menus[menuIndex].title); self->menus[menuIndex].identifier = menus[menuIndex].identifier; self->menus[menuIndex].offsetX = offsetX; self->menus[menuIndex].titleWidth = ceilf(call_virtual(measureString, typeface, self->menus[menuIndex].title) + menuPadding * 2); self->menus[menuIndex].contents = copyMenuContents(self, menus[menuIndex].itemCount, menus[menuIndex].items, false); assignSubmenuPositions(self, &self->menus[menuIndex].contents, VECTOR2f(offsetX, -menuHeight), menuTop); offsetX += self->menus[menuIndex].titleWidth; } self->width = width; self->actionCallback = actionCallback; self->validateCallback = validateCallback; self->contextMenuCallback = contextMenuCallback; self->callbackContext = callbackContext; self->openItems[0] = UINT_MAX; self->openTime = 0.0; self->contextMenuContents = NULL; self->submenuToOpenAfterDelay = UINT_MAX; self->openingSubmenuDepth = UINT_MAX; self->lastSubmenuOpenMousePosition = V2f(-FLT_MAX, -FLT_MAX); self->submenuOpenTimerID = SHELL_TIMER_INVALID; self->actionAnimationEndTime = 0.0; self->pendingMenuIdentifier = self->pendingMenuItemIdentifier = INT_MIN; self->pendingMenuTimerID = SHELL_TIMER_INVALID; self->drawOrderOffset = 1000; self->scrollTimer = SHELL_TIMER_INVALID; self->lastModifiers = 0; return true; } static void disposeMenuContents(struct UIMenuContents * contents); static void disposeMenuItem(struct UIMenuItem_private item) { String_free(item.title); String_free(item.shortcutString); UIKeyShortcut_free(item.shortcut); for (unsigned int alternateTitleIndex = 0; alternateTitleIndex < item.alternateTitleCount; alternateTitleIndex++) { String_free(item.alternateTitles[alternateTitleIndex].title); } free(item.alternateTitles); disposeMenuContents(&item.contents); } static void disposeMenuContents(struct UIMenuContents * contents) { for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { disposeMenuItem(contents->items[itemIndex]); } free(contents->items); contents->itemCount = 0; contents->items = NULL; } void UIMenuBar_dispose(UIMenuBar * self) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { String_free(self->menus[menuIndex].title); disposeMenuContents(&self->menus[menuIndex].contents); } free(self->menus); if (self->contextMenuContents != NULL) { disposeMenuContents(self->contextMenuContents); free(self->contextMenuContents); } Shell_cancelTimer(self->submenuOpenTimerID); Shell_cancelTimer(self->pendingMenuTimerID); Shell_cancelTimer(self->scrollTimer); call_super_virtual(dispose, self); } bool UIMenuBar_hitTest(UIMenuBar * self, float x, float y, UIHitTestType type, int * outPriority, bool * outForwardNext) { if (self->openItems[0] != UINT_MAX) { *outPriority = MENU_BAR_OPEN_HIT_TEST_PRIORITY; return true; } if (type == HIT_TEST_KEY_DOWN) { *outPriority = MENU_BAR_KEY_DOWN_PRIORITY; return true; } bool result = call_super_virtual(hitTest, self, x, y, type, outPriority, outForwardNext); if (result) { *outPriority = MENU_BAR_CLOSED_HIT_TEST_PRIORITY; } return result; } static Rect4f getMenuContentsBounds(UIMenuBar * self, struct UIMenuContents * contents, Rect4f maxBounds) { Rect4f bounds = call_virtual(getAbsoluteBounds, self); Rect4f menuBounds; menuBounds.xMin = bounds.xMin + contents->topLeft.x; menuBounds.xMax = menuBounds.xMin + contents->size.x; menuBounds.yMax = bounds.yMax + contents->topLeft.y + contents->openTopOffset; menuBounds.yMin = menuBounds.yMax - contents->size.y; if (menuBounds.xMax > maxBounds.xMax) { float newXMax = maxBounds.xMax; if (newXMax - (menuBounds.xMax - menuBounds.xMin) < maxBounds.xMin) { newXMax = maxBounds.xMin + (menuBounds.xMax - menuBounds.xMin); } menuBounds.xMin = newXMax - (menuBounds.xMax - menuBounds.xMin); menuBounds.xMax = newXMax; } if (menuBounds.yMin < maxBounds.yMin) { menuBounds.yMin = maxBounds.yMin; if (contents->isSubmenu) { menuBounds.yMax = menuBounds.yMin + contents->size.y; } } if (menuBounds.yMax > maxBounds.yMax) { menuBounds.yMax = maxBounds.yMax; } return menuBounds; } // Absolute coordinates only static struct UIMenuItem_private * getMenuItemAtPosition(UIMenuBar * self, float x, float y, struct UIMenuContents ** outContents, unsigned int * outItemIndex, unsigned int * outDepth) { unsigned int depth; for (depth = 0; depth < MENU_DEPTH_MAX; depth++) { if (self->openItems[depth] == UINT_MAX) { break; } } if (depth == 0) { return NULL; } struct UIMenuContents * openContents[depth]; openContents[0] = &self->menus[self->openItems[0]].contents; for (unsigned int openDepth = 1; openDepth < depth; openDepth++) { if (self->openItems[openDepth] == CONTEXT_MENU_OPEN_INDEX) { openContents[openDepth] = self->contextMenuContents; } else { openContents[openDepth] = &openContents[openDepth - 1]->items[self->openItems[openDepth]].contents; } } Rect4f maxBounds = getMaxBounds(self); do { depth--; Rect4f menuBounds = getMenuContentsBounds(self, openContents[depth], maxBounds); if (Rect4f_containsVector2f(menuBounds, V2f(x, y))) { float offsetYFromTopOfMenu = menuBounds.yMax + openContents[depth]->scrollPosition - y; for (unsigned int itemIndex = openContents[depth]->itemCount - 1; itemIndex < openContents[depth]->itemCount; itemIndex--) { if (offsetYFromTopOfMenu >= -openContents[depth]->items[itemIndex].offsetY) { if (outContents != NULL) { *outContents = openContents[depth]; } if (outItemIndex != NULL) { *outItemIndex = itemIndex; } if (outDepth != NULL) { *outDepth = depth; } return &openContents[depth]->items[itemIndex]; } } } if (openContents[depth] == self->contextMenuContents) { break; } } while (depth > 0); return NULL; } static void resetMenuContentsValidation(UIMenuBar * self, struct UIMenuContents * contents) { contents->validationDirty = true; for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { resetMenuContentsValidation(self, &contents->items[itemIndex].contents); } } static void resetValidation(UIMenuBar * self) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { resetMenuContentsValidation(self, &self->menus[menuIndex].contents); } } static void validateMenuItem(UIMenuBar * self, int menuIdentifier, struct UIMenuItem_private * item, struct UIMenuContents * parentContents) { UIMenuItemProperties properties; properties.enabled = item->enabled; properties.checked = item->checked; String_strncpy_utf8(properties.title, item->title, sizeof(properties.title)); properties.textColor = item->textColor; properties.customIcon = item->customIcon; self->validateCallback(self, menuIdentifier, item->identifier, &properties, self->callbackContext); item->enabled = properties.enabled; item->checked = properties.checked; item->textColor = properties.textColor; item->customIcon = properties.customIcon; if (String_strcmp_utf8(item->title, properties.title)) { String_free(item->title); item->title = String_copy(STR(properties.title)); parentContents->layoutDirty = true; } } static void validateMenuContents(UIMenuBar * self, int menuIdentifier, struct UIMenuContents * contents) { if (self->validateCallback != NULL && contents->validationDirty) { for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { if (contents->items[itemIndex].title.bytes != NULL) { validateMenuItem(self, menuIdentifier, &contents->items[itemIndex], contents); } } contents->validationDirty = false; } if (contents->layoutDirty) { contents->size.x = 0; UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); Vector2f itemPadding = getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding); float shortcutPadding = getAppearanceFloat(self->appearance, UIMenuBar_itemShortcutPadding); for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { if (contents->items[itemIndex].title.bytes != NULL) { contents->items[itemIndex].titleWidth = measureItemTitleWidth(self, contents->items[itemIndex], typeface, itemPadding, shortcutPadding); if (contents->items[itemIndex].titleWidth > contents->size.x) { contents->size.x = contents->items[itemIndex].titleWidth; } } } contents->layoutDirty = false; } float menuTop = call_virtual(getBounds, self).yMax; assignSubmenuPositions(self, contents, contents->topLeft, menuTop); } static void openSubmenu(UIMenuBar * self, unsigned int itemIndex, unsigned int depth, float openTopOffset) { if (self->openItems[0] == UINT_MAX || depth >= MENU_DEPTH_MAX) { return; } self->openItems[depth + 1] = itemIndex; if (depth + 2 < MENU_DEPTH_MAX) { self->openItems[depth + 2] = UINT_MAX; } struct UIMenuContents * contents = &self->menus[self->openItems[0]].contents; for (unsigned int submenuDepth = 1; submenuDepth <= depth; submenuDepth++) { contents = &contents[submenuDepth - 1].items[self->openItems[submenuDepth]].contents; } contents->items[itemIndex].contents.scrollPosition = 0.0f; contents->items[itemIndex].contents.openTopOffset = openTopOffset; validateMenuContents(self, self->menus[self->openItems[0]].identifier, &contents->items[itemIndex].contents); self->submenuToOpenAfterDelay = UINT_MAX; self->openingSubmenuDepth = UINT_MAX; Shell_cancelTimer(self->submenuOpenTimerID); self->submenuOpenTimerID = SHELL_TIMER_INVALID; Shell_redisplay(); } static void displayContextMenuCallback(unsigned int itemCount, UIMenuItem * items, void * menuBarContext) { UIMenuBar * self = menuBarContext; if (self->contextMenuContents == NULL) { self->contextMenuContents = malloc(sizeof(*self->contextMenuContents)); } else { disposeMenuContents(self->contextMenuContents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; } *self->contextMenuContents = copyMenuContents(self, itemCount, items, true); } UIEventResponse UIMenuBar_mouseDown(UIMenuBar * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, bool isFinalTarget, double referenceTime) { if (buttonNumber == 0) { struct UIMenuContents * contents; unsigned int itemIndex, itemDepth; Vector2f absolutePosition = UIElement_localToRootVector(self, V2f(x, y)); struct UIMenuItem_private * item = getMenuItemAtPosition(self, absolutePosition.x, absolutePosition.y, &contents, &itemIndex, &itemDepth); if (item != NULL && item->title.bytes != NULL && item->enabled && item->contents.itemCount > 0) { self->lastSubmenuOpenMousePosition = absolutePosition; contents->highlightIndex = itemIndex; openSubmenu(self, itemIndex, itemDepth, contents->scrollPosition + contents->openTopOffset); } Rect4f bounds = call_virtual(getBounds, self); if (self->openItems[0] != UINT_MAX) { self->openTime = DBL_MAX; } else if (self->openItems[0] == UINT_MAX && y >= bounds.yMin && y <= bounds.yMax) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (x >= bounds.xMin + self->menus[menuIndex].offsetX && x <= bounds.xMin + self->menus[menuIndex].offsetX + self->menus[menuIndex].titleWidth) { resetValidation(self); self->openItems[0] = menuIndex; self->menus[menuIndex].contents.highlightIndex = UINT_MAX; self->menus[menuIndex].contents.scrollPosition = 0.0f; self->openItems[1] = UINT_MAX; self->openTime = referenceTime; validateMenuContents(self, self->menus[menuIndex].identifier, &self->menus[menuIndex].contents); break; } } } return RESPONSE_HANDLED; } if (buttonNumber == 1 && (self->contextMenuContents == NULL || self->contextMenuContents->itemCount == 0)) { unsigned int itemIndex, itemDepth; Vector2f absolutePosition = UIElement_localToRootVector(self, V2f(x, y)); struct UIMenuItem_private * item = getMenuItemAtPosition(self, absolutePosition.x, absolutePosition.y, NULL, &itemIndex, &itemDepth); if (item != NULL && item->title.bytes != NULL && item->enabled && self->contextMenuCallback != NULL) { if (self->contextMenuContents != NULL) { disposeMenuContents(self->contextMenuContents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; } self->contextMenuCallback(self, self->menus[self->openItems[0]].identifier, item->identifier, displayContextMenuCallback, self, self->callbackContext); if (self->contextMenuContents != NULL && self->contextMenuContents->itemCount > 0 && itemDepth + 2 < MENU_DEPTH_MAX) { Rect4f bounds = call_virtual(getBounds, self); self->contextMenuContents->topLeft = VECTOR2f(x - bounds.xMin, y - bounds.yMax); self->openItems[itemDepth + 1] = CONTEXT_MENU_OPEN_INDEX; self->openItems[itemDepth + 2] = UINT_MAX; return RESPONSE_HANDLED; } } } return RESPONSE_HANDLED; } #define MOUSE_SCROLL_INTERVAL 0.05 static void clampScrollPosition(struct UIMenuContents * contents, float displayedHeight) { if (contents->scrollPosition < 0.0f) { contents->scrollPosition = 0.0f; } else if (contents->scrollPosition > contents->size.y - displayedHeight) { contents->scrollPosition = contents->size.y - displayedHeight; } } static void scrollTimerCallback(ShellTimer timerID, void * context) { UIMenuBar * self = context; struct UIMenuContents * contents = self->scrollingMenuContents; Rect4f maxBounds = getMaxBounds(self); Rect4f menuBounds = getMenuContentsBounds(self, contents, maxBounds); if (menuBounds.yMax - menuBounds.yMin < contents->size.y) { float lastScrollPosition = contents->scrollPosition; UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface) + getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding).y * 2; contents->scrollPosition += self->mouseScrollDirection * lineHeight; clampScrollPosition(contents, menuBounds.yMax - menuBounds.yMin); if (contents->scrollPosition != lastScrollPosition) { Shell_redisplay(); } else { Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; } } } static void openSubmenuTimerCallback(ShellTimer timerID, void * context) { UIMenuBar * self = context; openSubmenu(self, self->submenuToOpenAfterDelay, self->openingSubmenuDepth, self->openingSubmenuParentScrollPosition); } static bool mouseMovedOrDragged(UIMenuBar * self, float x, float y) { Rect4f bounds = call_virtual(getAbsoluteBounds, self); if (self->openItems[0] != UINT_MAX) { struct UIMenuContents * contents; unsigned int itemIndex, depth; Vector2f absolutePosition = UIElement_localToRootVector(self, V2f(x, y)); struct UIMenuItem_private * item = getMenuItemAtPosition(self, absolutePosition.x, absolutePosition.y, &contents, &itemIndex, &depth); UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface); if (item != NULL && self->lastSubmenuOpenMousePosition.x != -FLT_MAX && itemIndex != contents->highlightIndex) { if ((depth == self->openingSubmenuDepth || (depth + 1 < MENU_DEPTH_MAX && self->openItems[depth + 1] != UINT_MAX)) && absolutePosition.x >= self->lastSubmenuOpenMousePosition.x && absolutePosition.y <= self->lastSubmenuOpenMousePosition.y + lineHeight / 2 && absolutePosition.x - self->lastSubmenuOpenMousePosition.x > self->lastSubmenuOpenMousePosition.y - absolutePosition.y) { item = getMenuItemAtPosition(self, self->lastSubmenuOpenMousePosition.x, self->lastSubmenuOpenMousePosition.y, &contents, &itemIndex, &depth); } else if (self->openingSubmenuDepth == UINT_MAX) { self->lastSubmenuOpenMousePosition = V2f(-FLT_MAX, -FLT_MAX); } } if (item != NULL) { Rect4f maxBounds = getMaxBounds(self); Rect4f menuBounds = getMenuContentsBounds(self, contents, maxBounds); if (menuBounds.yMax - menuBounds.yMin < contents->size.y) { UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float itemHeight = call_virtual(getLineHeight, typeface) + getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding).y * 2; if (contents->scrollPosition > 0.0f && absolutePosition.y > menuBounds.yMax - itemHeight) { if (self->scrollTimer == SHELL_TIMER_INVALID) { contents->scrollPosition -= itemHeight; self->scrollingMenuContents = contents; self->mouseScrollDirection = -1; clampScrollPosition(contents, menuBounds.yMax - menuBounds.yMin); self->scrollTimer = Shell_setTimer(MOUSE_SCROLL_INTERVAL, true, scrollTimerCallback, self); } return true; } if (contents->scrollPosition < contents->size.y - (menuBounds.yMax - menuBounds.yMin) && absolutePosition.y < menuBounds.yMin + itemHeight) { if (self->scrollTimer == SHELL_TIMER_INVALID) { contents->scrollPosition += itemHeight; self->scrollingMenuContents = contents; self->mouseScrollDirection = 1; clampScrollPosition(contents, menuBounds.yMax - menuBounds.yMin); self->scrollTimer = Shell_setTimer(MOUSE_SCROLL_INTERVAL, true, scrollTimerCallback, self); } return true; } Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; } unsigned int lastHighlightIndex = contents->highlightIndex; if (item->title.bytes != NULL && item->enabled) { contents->highlightIndex = itemIndex; } else { contents->highlightIndex = UINT_MAX; } if (depth + 1 < MENU_DEPTH_MAX && self->openItems[depth + 1] != itemIndex && (self->submenuToOpenAfterDelay != itemIndex || self->openingSubmenuDepth != depth)) { if (contents->items[itemIndex].contents.itemCount > 0 && item->enabled) { self->openItems[depth + 1] = UINT_MAX; self->openingSubmenuDepth = depth; self->submenuToOpenAfterDelay = itemIndex; self->openingSubmenuParentScrollPosition = contents->scrollPosition + contents->openTopOffset; self->submenuOpenTimerID = Shell_setTimer(SUBMENU_OPEN_DELAY, false, openSubmenuTimerCallback, self); self->lastSubmenuOpenMousePosition = absolutePosition; contents->items[itemIndex].contents.highlightIndex = UINT_MAX; } else { Shell_cancelTimer(self->submenuOpenTimerID); self->openingSubmenuDepth = UINT_MAX; self->submenuToOpenAfterDelay = UINT_MAX; self->openItems[depth + 1] = UINT_MAX; } return true; } return contents->highlightIndex != lastHighlightIndex; } Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; if (absolutePosition.y >= bounds.yMin && absolutePosition.y <= bounds.yMax) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (absolutePosition.x >= bounds.xMin + self->menus[menuIndex].offsetX && absolutePosition.x <= bounds.xMin + self->menus[menuIndex].offsetX + self->menus[menuIndex].titleWidth && menuIndex != self->openItems[0]) { self->openItems[0] = menuIndex; self->menus[menuIndex].contents.highlightIndex = UINT_MAX; self->menus[menuIndex].contents.scrollPosition = 0.0f; self->openItems[1] = UINT_MAX; validateMenuContents(self, self->menus[menuIndex].identifier, &self->menus[menuIndex].contents); return true; } } } bool changed = false; if (self->contextMenuContents == NULL || self->contextMenuContents->itemCount == 0) { contents = &self->menus[self->openItems[0]].contents; if (contents->highlightIndex != UINT_MAX) { contents->highlightIndex = UINT_MAX; changed = true; } for (unsigned int depth = 1; depth < MENU_DEPTH_MAX && self->openItems[depth] != UINT_MAX; depth++) { contents = &contents->items[self->openItems[depth]].contents; if (contents->highlightIndex != UINT_MAX) { contents->highlightIndex = UINT_MAX; changed = true; } } Shell_cancelTimer(self->submenuOpenTimerID); self->submenuOpenTimerID = SHELL_TIMER_INVALID; self->openingSubmenuDepth = UINT_MAX; self->submenuToOpenAfterDelay = UINT_MAX; self->lastSubmenuOpenMousePosition = V2f(-FLT_MAX, -FLT_MAX); } else if (self->contextMenuContents->highlightIndex != UINT_MAX) { self->contextMenuContents->highlightIndex = UINT_MAX; changed = true; } return changed; } return false; } bool UIMenuBar_mouseUp(UIMenuBar * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime) { if (buttonNumber == 0 && self->openItems[0] != UINT_MAX) { unsigned int depth; Vector2f absolutePosition = UIElement_localToRootVector(self, V2f(x, y)); struct UIMenuItem_private * item = getMenuItemAtPosition(self, absolutePosition.x, absolutePosition.y, NULL, NULL, &depth); if (item != NULL) { if (item->title.bytes != NULL && item->enabled && item->contents.itemCount == 0) { self->pendingMenuIdentifier = self->menus[self->openItems[0]].identifier; self->pendingMenuItemIdentifier = item->identifier; self->pendingMenuModifiers = modifiers; self->pendingMenuReferenceTime = referenceTime; if (self->openItems[depth] == CONTEXT_MENU_OPEN_INDEX) { self->openItems[depth] = UINT_MAX; disposeMenuContents(self->contextMenuContents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; } else { self->openItems[0] = UINT_MAX; } } return true; } if (self->contextMenuContents != NULL && self->contextMenuContents->itemCount > 0) { for (unsigned int depth = 0; depth < MENU_DEPTH_MAX && self->openItems[depth] != UINT_MAX; depth++) { if (self->openItems[depth] == CONTEXT_MENU_OPEN_INDEX) { self->openItems[depth] = UINT_MAX; disposeMenuContents(self->contextMenuContents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; mouseMovedOrDragged(self, x, y); return true; } } } if (self->openTime == DBL_MAX || self->openTime + MOUSE_UP_CLOSE_INTERVAL < referenceTime) { UIMenuBar_close(self); return true; } } return false; } bool UIMenuBar_mouseMoved(UIMenuBar * self, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime) { return mouseMovedOrDragged(self, x, y); } bool UIMenuBar_mouseDragged(UIMenuBar * self, unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime) { return mouseMovedOrDragged(self, x, y); } UIEventResponse UIMenuBar_scrollWheel(UIMenuBar * self, float x, float y, int scrollDeltaX, int scrollDeltaY, unsigned int buttonMask, unsigned int modifiers, bool isFinalTarget, double referenceTime) { if (self->openItems[0] != UINT_MAX) { struct UIMenuContents * contents; Vector2f absolutePosition = UIElement_localToRootVector(self, V2f(x, y)); struct UIMenuItem_private * item = getMenuItemAtPosition(self, absolutePosition.x, absolutePosition.y, &contents, NULL, NULL); if (item != NULL) { Rect4f maxBounds = getMaxBounds(self); Rect4f menuBounds = getMenuContentsBounds(self, contents, maxBounds); if (menuBounds.yMax - menuBounds.yMin < contents->size.y) { float lastScrollPosition = contents->scrollPosition; UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface) + getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding).y * 2; contents->scrollPosition += scrollDeltaY * lineHeight; clampScrollPosition(contents, menuBounds.yMax - menuBounds.yMin); if (contents->scrollPosition != lastScrollPosition) { Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; return RESPONSE_HANDLED; } } } return RESPONSE_IGNORE; } return RESPONSE_UNHANDLED; } static bool handleMenuContentsKeyDown(UIMenuBar * self, unsigned int menuIndex, struct UIMenuContents * contents, unsigned int keyCode, unsigned int modifiers, double referenceTime); static bool handleMenuItemKeyDown(UIMenuBar * self, unsigned int menuIndex, struct UIMenuItem_private * item, struct UIMenuContents * parentContents, unsigned int keyCode, unsigned int modifiers, double referenceTime) { if (item->contents.itemCount > 0) { return handleMenuContentsKeyDown(self, menuIndex, &item->contents, keyCode, modifiers, referenceTime); } if (UIKeyShortcut_isMatch(item->shortcut, keyCode, modifiers, NULL)) { validateMenuItem(self, self->menus[menuIndex].identifier, item, parentContents); if (item->enabled) { bool animateWhenInvoked = item->animateWhenInvoked; // If actionCallback changes this menu's contents, item might no longer exist self->actionCallback(self, self->menus[menuIndex].identifier, item->identifier, modifiers, referenceTime, self->callbackContext); if (animateWhenInvoked) { self->actionAnimationEndTime = referenceTime + getAppearanceDouble(self->appearance, UIToolkit_actionAnimationDuration); self->actionAnimationMenuIndex = menuIndex; } return true; } } return false; } static bool handleMenuContentsKeyDown(UIMenuBar * self, unsigned int menuIndex, struct UIMenuContents * contents, unsigned int keyCode, unsigned int modifiers, double referenceTime) { for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { if (contents->items[itemIndex].title.bytes != NULL && handleMenuItemKeyDown(self, menuIndex, &contents->items[itemIndex], contents, keyCode, modifiers, referenceTime)) { return true; } } return false; } UIEventResponse UIMenuBar_keyDown(UIMenuBar * self, unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, bool isFinalTarget, double referenceTime) { if (self->openItems[0] != UINT_MAX) { // TODO: Full keyboard controls for menu navigation; escape should only close innermost submenu if (keyCode == KEY_CODE_ESCAPE) { if (self->contextMenuContents != NULL && self->contextMenuContents->itemCount > 0) { for (unsigned int depth = 0; depth < MENU_DEPTH_MAX && self->openItems[depth] != UINT_MAX; depth++) { if (self->openItems[depth] == CONTEXT_MENU_OPEN_INDEX) { self->openItems[depth] = UINT_MAX; disposeMenuContents(self->contextMenuContents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; return RESPONSE_HANDLED; } } } UIMenuBar_close(self); } return RESPONSE_HANDLED; } for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (handleMenuContentsKeyDown(self, menuIndex, &self->menus[menuIndex].contents, keyCode, modifiers, referenceTime)) { return RESPONSE_HANDLED; } } return RESPONSE_UNHANDLED; } bool UIMenuBar_keyModifiersChanged(UIMenuBar * self, unsigned int modifiers, unsigned int lastModifiers, double referenceTime) { unsigned int lastLastModifiers = self->lastModifiers; self->lastModifiers = modifiers; if (self->lastModifiers & MODIFIER_ALTGR_BIT) { self->lastModifiers |= MODIFIER_ALT_BIT; } self->lastModifiers &= MODIFIER_SHIFT_BIT | MODIFIER_ALT_BIT | MODIFIER_CONTROL_BIT | MODIFIER_COMMAND_BIT; return self->lastModifiers != lastLastModifiers; } bool UIMenuBar_menuActionDown(UIMenuBar * self, unsigned int actionNumber, bool isRepeat, double referenceTime) { // TODO return false; } bool UIMenuBar_menuActionUp(UIMenuBar * self, unsigned int actionNumber, double referenceTime) { // TODO return false; } bool UIMenuBar_menuDirectionDown(UIMenuBar * self, UINavigationDirection direction, bool isRepeat, double referenceTime) { // TODO return false; } bool UIMenuBar_acceptsFocus(UIMenuBar * self, bool finalTargetOnly) { return true; } Rect4f UIMenuBar_getBounds(UIMenuBar * self) { return UIElement_boundsRectWithOrigin(self->position, self->relativeOrigin, VECTOR2f(self->width, getAppearanceFloat(self->appearance, UIMenuBar_menuHeight))); } Rect4f UIMenuBar_getFocusBounds(UIMenuBar * self) { return call_super_virtual(getFocusBounds, self); } static void pendingMenuTimerCallback(ShellTimer timerID, void * context) { UIMenuBar * self = context; self->actionCallback(self, self->pendingMenuIdentifier, self->pendingMenuItemIdentifier, self->pendingMenuModifiers, self->pendingMenuReferenceTime, self->callbackContext); self->pendingMenuIdentifier = self->pendingMenuItemIdentifier = INT_MIN; self->pendingMenuTimerID = SHELL_TIMER_INVALID; Shell_redisplay(); } static void drawMenuContents(UIMenuBar * self, struct UIMenuContents * contents, unsigned int depth, Vector2f offset, Rect4f maxBounds, UIDrawingInterface * drawingInterface, VertexIO * vertexIO) { Rect4f menuBounds = getMenuContentsBounds(self, contents, maxBounds); float dropShadowOutset = getAppearanceFloat(self->appearance, UIMenuBar_dropShadowOutset); Vector2f dropShadowOffset = getAppearanceVector2f(self->appearance, UIMenuBar_dropShadowOffset); Rect4f whiteEntry = getAppearanceAtlasEntry(self->appearance, UIToolkit_white).bounds; call_virtual(drawSlicedQuad3x3, drawingInterface, Rect4f_offset(Rect4f_inset(menuBounds, VECTOR2f(-dropShadowOutset, -dropShadowOutset)), dropShadowOffset), UIAtlasEntry_boundsForScale(getAppearanceAtlasEntry(self->appearance, UIMenuBar_menuDropShadow), drawingInterface->scaleFactor), getAppearanceSliceGrid3x3(self->appearance, UIMenuBar_dropShadowSlices), getAppearanceColor4f(self->appearance, UIMenuBar_dropShadowColor), vertexIO); call_virtual(drawQuad, drawingInterface, Rect4f_inset(menuBounds, VECTOR2f(-1, -1)), whiteEntry, getAppearanceColor4f(self->appearance, UIMenuBar_outlineColor), vertexIO); call_virtual(drawQuad, drawingInterface, menuBounds, whiteEntry, getAppearanceColor4f(self->appearance, UIMenuBar_backgroundColor), vertexIO); UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface); Vector2f itemPadding = getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding); Color4f separatorColor = getAppearanceColor4f(self->appearance, UIMenuBar_separatorColor); Color4f highlightColor = getAppearanceColor4f(self->appearance, UIMenuBar_highlightColor); Color4f highlightTextColor = getAppearanceColor4f(self->appearance, UIMenuBar_highlightTextColor); Color4f disabledTextColor = getAppearanceColor4f(self->appearance, UIMenuBar_disabledTextColor); UIAtlasEntry checkmarkEntry = getAppearanceAtlasEntry(self->appearance, UIMenuBar_menuCheckmark); UIAtlasEntry submenuArrowEntry = getAppearanceAtlasEntry(self->appearance, UIMenuBar_menuSubmenuArrow); float itemHeight = lineHeight + itemPadding.y * 2; clampScrollPosition(contents, menuBounds.yMax - menuBounds.yMin); Rect4f innerBounds = menuBounds; if (menuBounds.yMax - menuBounds.yMin < contents->size.y) { UIAtlasEntry arrowAtlasEntry = getAppearanceAtlasEntry(self->appearance, UIMenuBar_scrollArrow); Rect4f arrowAtlasEntryBounds = UIAtlasEntry_boundsForScale(arrowAtlasEntry, drawingInterface->scaleFactor); Rect4f arrowBounds; arrowBounds.xMin = roundpositivef(menuBounds.xMin + (menuBounds.xMax - menuBounds.xMin - arrowAtlasEntry.size.x) / 2); arrowBounds.xMax = arrowBounds.xMin + arrowAtlasEntry.size.x; if (contents->scrollPosition > 0.0f) { Rect4f arrowAtlasEntryFlipped = arrowAtlasEntryBounds; arrowAtlasEntryFlipped.yMax = arrowAtlasEntryBounds.yMin; arrowAtlasEntryFlipped.yMin = arrowAtlasEntryBounds.yMax; arrowBounds.yMax = roundpositivef(menuBounds.yMax - (itemHeight - arrowAtlasEntry.size.y) / 2 - 1); arrowBounds.yMin = arrowBounds.yMax - arrowAtlasEntry.size.y; call_virtual(drawQuad, drawingInterface, arrowBounds, arrowAtlasEntryFlipped, COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), vertexIO); innerBounds.yMax -= itemHeight; } if (contents->scrollPosition < contents->size.y - (menuBounds.yMax - menuBounds.yMin)) { arrowBounds.yMin = roundpositivef(menuBounds.yMin + (itemHeight - arrowAtlasEntry.size.y) / 2 + 1); arrowBounds.yMax = arrowBounds.yMin + arrowAtlasEntry.size.y; call_virtual(drawQuad, drawingInterface, arrowBounds, arrowAtlasEntryBounds, COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), vertexIO); innerBounds.yMin += itemHeight; } } else { contents->scrollPosition = 0.0f; } unsigned int lastIndex = vertexIO->indexCount; for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { float offsetY = contents->items[itemIndex].offsetY + contents->scrollPosition; if (contents->items[itemIndex].title.bytes == NULL) { call_virtual(drawQuad, drawingInterface, RECT4f(menuBounds.xMin, menuBounds.xMax, menuBounds.yMax + offsetY - SEPARATOR_HEIGHT, menuBounds.yMax + offsetY), whiteEntry, separatorColor, vertexIO); } else { Color4f textColor; if (!contents->items[itemIndex].enabled) { textColor = disabledTextColor; } else if (contents->highlightIndex == itemIndex || self->openItems[depth] == itemIndex) { call_virtual(drawQuad, drawingInterface, RECT4f(menuBounds.xMin, menuBounds.xMax, menuBounds.yMax + offsetY - itemHeight, menuBounds.yMax + offsetY), whiteEntry, highlightColor, vertexIO); textColor = highlightTextColor; } else { textColor = contents->items[itemIndex].textColor; } String displayedItemTitle = contents->items[itemIndex].title; for (unsigned int alternateTitleIndex = 0; alternateTitleIndex < contents->items[itemIndex].alternateTitleCount; alternateTitleIndex++) { if (self->lastModifiers == contents->items[itemIndex].alternateTitles[alternateTitleIndex].modifiers) { displayedItemTitle = contents->items[itemIndex].alternateTitles[alternateTitleIndex].title; break; } } call_virtual(drawString, drawingInterface, typeface, displayedItemTitle, VECTOR2f(menuBounds.xMin + 1 + itemPadding.x * 2 + checkmarkEntry.size.x, menuBounds.yMax + offsetY - itemHeight / 2), VECTOR2f(0.0f, 0.5f), 1.0f, textColor, vertexIO); if (contents->items[itemIndex].checked) { call_virtual(drawQuad, drawingInterface, UIElement_boundsRectWithOrigin(VECTOR2f(menuBounds.xMin + itemPadding.x + self->leftRightIconPadding / 2, menuBounds.yMax + offsetY - itemHeight / 2), VECTOR2f(0.5f, 0.5f), checkmarkEntry.size), UIAtlasEntry_boundsForScale(checkmarkEntry, drawingInterface->scaleFactor), textColor, vertexIO); } else if (!Vector2f_isZero(contents->items[itemIndex].customIcon.size)) { call_virtual(drawQuad, drawingInterface, UIElement_boundsRectWithOrigin(VECTOR2f(menuBounds.xMin + itemPadding.x + self->leftRightIconPadding / 2, menuBounds.yMax + offsetY - itemHeight / 2), VECTOR2f(0.5f, 0.5f), contents->items[itemIndex].customIcon.size), UIAtlasEntry_boundsForScale(contents->items[itemIndex].customIcon, drawingInterface->scaleFactor), textColor, vertexIO); } if (contents->items[itemIndex].contents.itemCount > 0) { call_virtual(drawQuad, drawingInterface, UIElement_boundsRectWithOrigin(VECTOR2f(menuBounds.xMax - itemPadding.x - self->leftRightIconPadding / 2, menuBounds.yMax + offsetY - itemHeight / 2), VECTOR2f(0.5f, 0.5f), submenuArrowEntry.size), UIAtlasEntry_boundsForScale(submenuArrowEntry, drawingInterface->scaleFactor), textColor, vertexIO); } else if (contents->items[itemIndex].shortcutString.bytes != NULL) { call_virtual(drawString, drawingInterface, typeface, contents->items[itemIndex].shortcutString, VECTOR2f(menuBounds.xMax - itemPadding.x, menuBounds.yMax + offsetY - itemHeight / 2), VECTOR2f(1.0f, 0.5f), 1.0f, textColor, vertexIO); } } } clipVerticesInsideRect(lastIndex, vertexIO->indexCount - lastIndex, innerBounds, vertexIO); if (depth < MENU_DEPTH_MAX) { if (self->openItems[depth] == CONTEXT_MENU_OPEN_INDEX) { drawMenuContents(self, self->contextMenuContents, depth + 1, offset, maxBounds, drawingInterface, vertexIO); } else if (self->openItems[depth] < contents->itemCount) { drawMenuContents(self, &contents->items[self->openItems[depth]].contents, depth + 1, offset, maxBounds, drawingInterface, vertexIO); } } } void UIMenuBar_draw(UIMenuBar * self, Vector2f offset, UIDrawingInterface * drawingInterface, VertexIO * vertexIO) { Rect4f bounds = Rect4f_offset(call_virtual(getBounds, self), offset); Rect4f whiteEntry = getAppearanceAtlasEntry(self->appearance, UIToolkit_white).bounds; call_virtual(drawQuad, drawingInterface, bounds, whiteEntry, getAppearanceColor4f(self->appearance, UIMenuBar_outlineColor), vertexIO); call_virtual(drawQuad, drawingInterface, Rect4f_inset(bounds, VECTOR2f(1, 1)), whiteEntry, getAppearanceColor4f(self->appearance, UIMenuBar_backgroundColor), vertexIO); Color4f menuHighlightColor = getAppearanceColor4f(self->appearance, UIMenuBar_highlightColor); Color4f menuHighlightTextColor = getAppearanceColor4f(self->appearance, UIMenuBar_highlightTextColor); Color4f menuTextColor = getAppearanceColor4f(self->appearance, UIMenuBar_textColor); UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, drawingInterface); float menuPadding = getAppearanceFloat(self->appearance, UIMenuBar_menuPadding); Rect4f maxBounds = getMaxBounds(self); for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { Color4f textColor; if (self->openItems[0] == menuIndex || (self->actionAnimationEndTime > vertexIO->referenceTime && menuIndex == self->actionAnimationMenuIndex)) { call_virtual(drawQuad, drawingInterface, RECT4f(bounds.xMin + self->menus[menuIndex].offsetX, bounds.xMin + self->menus[menuIndex].offsetX + self->menus[menuIndex].titleWidth, bounds.yMin, bounds.yMax), whiteEntry, menuHighlightColor, vertexIO); textColor = menuHighlightTextColor; } else { textColor = menuTextColor; } call_virtual(drawString, drawingInterface, typeface, self->menus[menuIndex].title, VECTOR2f(bounds.xMin + self->menus[menuIndex].offsetX + menuPadding, bounds.yMin + (bounds.yMax - bounds.yMin) / 2), VECTOR2f(0.0f, 0.5f), 1.0f, textColor, vertexIO); if (self->openItems[0] == menuIndex) { drawMenuContents(self, &self->menus[menuIndex].contents, 1, VECTOR2f(bounds.xMin, bounds.yMax), maxBounds, drawingInterface, vertexIO); } } self->dirty = self->actionAnimationEndTime > vertexIO->referenceTime; if (self->pendingMenuIdentifier != INT_MIN && self->pendingMenuItemIdentifier != INT_MIN && self->pendingMenuTimerID == SHELL_TIMER_INVALID) { self->pendingMenuTimerID = Shell_setTimer(0.0, false, pendingMenuTimerCallback, self); self->dirty = true; } } void UIMenuBar_listRenderables(UIMenuBar * self, RenderableIO * renderableIO, int drawOrderOffset, Rect4i clipBounds) { self->dirty = false; if (!self->visible) { return; } clipBounds = UIElement_intersectClipBounds(clipBounds, call_virtual(getAbsoluteClipBounds, self)); RenderableIO_addRenderable(renderableIO, self->renderable, drawOrderOffset + self->drawOrderOffset + (self->openItems[0] != UINT_MAX ? 1000 : 0), clipBounds); } bool UIMenuBar_isOpen(UIMenuBar * self) { return self->openItems[0] != UINT_MAX; } void UIMenuBar_close(UIMenuBar * self) { self->openItems[0] = UINT_MAX; self->pendingMenuIdentifier = self->pendingMenuItemIdentifier = INT_MIN; Shell_cancelTimer(self->pendingMenuTimerID); self->pendingMenuTimerID = SHELL_TIMER_INVALID; if (self->contextMenuContents != NULL) { disposeMenuContents(self->contextMenuContents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; } } void UIMenuBar_playKeyShortcutAnimation(UIMenuBar * self, int menuIdentifier) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (self->menus[menuIndex].identifier == menuIdentifier) { self->actionAnimationEndTime = Shell_getCurrentTime() + getAppearanceDouble(self->appearance, UIToolkit_actionAnimationDuration); self->actionAnimationMenuIndex = menuIndex; break; } } } void UIMenuBar_addMenu(UIMenuBar * self, UIMenu * menu) { UIMenuBar_insertMenu(self, menu, self->menuCount); } void UIMenuBar_insertMenu(UIMenuBar * self, UIMenu * menu, unsigned int menuIndex) { if (menuIndex > self->menuCount) { menuIndex = self->menuCount; } self->menus = realloc(self->menus, (self->menuCount + 1) * sizeof(*self->menus)); for (unsigned int menuIndex2 = self->menuCount; menuIndex2 > menuIndex; menuIndex2--) { self->menus[menuIndex2] = self->menus[menuIndex2 - 1]; } self->menus[menuIndex].title = String_copy(menu->title); self->menus[menuIndex].identifier = menu->identifier; UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); self->menus[menuIndex].titleWidth = ceilf(call_virtual(measureString, typeface, menu->title) + getAppearanceFloat(self->appearance, UIMenuBar_menuPadding) * 2); self->menus[menuIndex].contents = copyMenuContents(self, menu->itemCount, menu->items, false); self->menuCount++; float offsetX = 0.0f; if (menuIndex > 0) { offsetX = self->menus[menuIndex - 1].offsetX + self->menus[menuIndex - 1].titleWidth; } float menuHeight = getAppearanceFloat(self->appearance, UIMenuBar_menuHeight); float menuTop = call_virtual(getBounds, self).yMax; for (unsigned int menuIndex2 = menuIndex; menuIndex2 < self->menuCount; menuIndex2++) { self->menus[menuIndex2].offsetX = offsetX; assignSubmenuPositions(self, &self->menus[menuIndex2].contents, VECTOR2f(offsetX, -menuHeight), menuTop); offsetX += self->menus[menuIndex2].titleWidth; } } void UIMenuBar_removeMenu(UIMenuBar * self, int menuIdentifier) { float offsetX = 0.0f; float menuHeight = getAppearanceFloat(self->appearance, UIMenuBar_menuHeight); for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (self->menus[menuIndex].identifier == menuIdentifier) { String_free(self->menus[menuIndex].title); disposeMenuContents(&self->menus[menuIndex].contents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; self->menuCount--; float menuTop = call_virtual(getBounds, self).yMax; for (; menuIndex < self->menuCount; menuIndex++) { self->menus[menuIndex] = self->menus[menuIndex + 1]; self->menus[menuIndex].offsetX = offsetX; assignSubmenuPositions(self, &self->menus[menuIndex].contents, VECTOR2f(offsetX, -menuHeight), menuTop); offsetX += self->menus[menuIndex].titleWidth; } return; } offsetX += self->menus[menuIndex].titleWidth; } #ifdef DEBUG fprintf(stderr, "Warning: UIMenuBar_removeMenu() couldn't find menu with identifier %d\n", menuIdentifier); #endif } static struct UIMenu_private * getMenu(UIMenuBar * self, int menuIdentifier) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (self->menus[menuIndex].identifier == menuIdentifier) { return &self->menus[menuIndex]; } } return NULL; } static struct UIMenuItem_private * getMenuItemWithContents(UIMenuBar * self, struct UIMenuContents * contents, int menuItemIdentifier, struct UIMenuContents ** outParentContents) { for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { if (contents->items[itemIndex].identifier == menuItemIdentifier) { if (outParentContents != NULL) { *outParentContents = contents; } return &contents->items[itemIndex]; } if (contents->items[itemIndex].contents.itemCount > 0) { struct UIMenuItem_private * item = getMenuItemWithContents(self, &contents->items[itemIndex].contents, menuItemIdentifier, outParentContents); if (item != NULL) { return item; } } } return NULL; } static struct UIMenuItem_private * getMenuItem(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, struct UIMenuContents ** outParentContents) { struct UIMenu_private * menu = getMenu(self, menuIdentifier); if (menu != NULL) { return getMenuItemWithContents(self, &menu->contents, menuItemIdentifier, outParentContents); } return NULL; } void UIMenuBar_setMenuTitle(UIMenuBar * self, int menuIdentifier, String title) { float offsetX = 0.0f; UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float menuHeight = getAppearanceFloat(self->appearance, UIMenuBar_menuHeight); float menuPadding = getAppearanceFloat(self->appearance, UIMenuBar_menuPadding); for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (self->menus[menuIndex].identifier == menuIdentifier) { String_free(self->menus[menuIndex].title); self->menus[menuIndex].title = String_copy(title); self->menus[menuIndex].titleWidth = ceilf(call_virtual(measureString, typeface, self->menus[menuIndex].title) + menuPadding * 2); float menuTop = call_virtual(getBounds, self).yMax; for (menuIndex++; menuIndex < self->menuCount; menuIndex++) { offsetX += self->menus[menuIndex - 1].titleWidth; self->menus[menuIndex].offsetX = offsetX; assignSubmenuPositions(self, &self->menus[menuIndex].contents, VECTOR2f(offsetX, -menuHeight), menuTop); } break; } offsetX += self->menus[menuIndex].titleWidth; } } static bool isMenuOpen(UIMenuBar * self, struct UIMenuContents * contents) { if (self->openItems[0] == UINT_MAX) { return false; } struct UIMenuContents * contentsAtDepth = &self->menus[self->openItems[0]].contents; if (contentsAtDepth == contents) { return true; } for (unsigned int depth = 1; depth < MENU_DEPTH_MAX && self->openItems[depth] != UINT_MAX; depth++) { if (self->openItems[depth] == CONTEXT_MENU_OPEN_INDEX) { contentsAtDepth = self->contextMenuContents; } else { contentsAtDepth = &contentsAtDepth->items[self->openItems[depth]].contents; } if (contentsAtDepth == contents) { return true; } } return false; } void UIMenuBar_setMenuContents(UIMenuBar * self, int menuIdentifier, unsigned int itemCount, UIMenuItem * items) { struct UIMenu_private * menu = getMenu(self, menuIdentifier); if (menu != NULL) { disposeMenuContents(&menu->contents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; menu->contents = copyMenuContents(self, itemCount, items, false); float menuTop = call_virtual(getBounds, self).yMax; assignSubmenuPositions(self, &menu->contents, VECTOR2f(menu->offsetX, -getAppearanceFloat(self->appearance, UIMenuBar_menuHeight)), menuTop); if (isMenuOpen(self, &menu->contents)) { validateMenuContents(self, self->menus[self->openItems[0]].identifier, &menu->contents); } } } void UIMenuBar_setSubmenuContents(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, unsigned int itemCount, UIMenuItem * items) { struct UIMenuContents * contents; struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, &contents); if (item != NULL) { disposeMenuContents(&item->contents); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; item->contents = copyMenuContents(self, itemCount, items, true); float menuTop = call_virtual(getBounds, self).yMax; assignSubmenuPositions(self, &item->contents, VECTOR2f(contents->topLeft.x + contents->size.x, contents->topLeft.y + item->offsetY), menuTop); if (isMenuOpen(self, &item->contents)) { validateMenuContents(self, self->menus[self->openItems[0]].identifier, &item->contents); } } } void UIMenuBar_setItemChecked(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, bool checked) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { item->checked = checked; } } bool UIMenuBar_getItemChecked(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { return item->checked; } return false; } void UIMenuBar_setItemEnabled(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, bool enabled) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { item->enabled = enabled; } } bool UIMenuBar_getItemEnabled(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { return item->enabled; } return false; } void UIMenuBar_setItemTitle(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, String title) { struct UIMenuContents * contents; struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, &contents); if (item != NULL) { String_free(item->title); item->title = String_copy(title); contents->layoutDirty = true; } } String UIMenuBar_getItemTitle(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { return item->title; } return STR_NULL; } static bool getMenuContentsItemWithShortcutInternal(struct UIMenuContents * contents, unsigned int keyCode, unsigned int modifiers, int * outMenuItemIdentifier); static bool getItemWithShortcutInternal(struct UIMenuItem_private * item, unsigned int keyCode, unsigned int modifiers, int * outMenuItemIdentifier) { if (item->contents.itemCount > 0) { return getMenuContentsItemWithShortcutInternal(&item->contents, keyCode, modifiers, outMenuItemIdentifier); } if (UIKeyShortcut_isMatch(item->shortcut, keyCode, modifiers, NULL)) { if (outMenuItemIdentifier != NULL) { *outMenuItemIdentifier = item->identifier; } return true; } return false; } static bool getMenuContentsItemWithShortcutInternal(struct UIMenuContents * contents, unsigned int keyCode, unsigned int modifiers, int * outMenuItemIdentifier) { for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { if (contents->items[itemIndex].title.bytes != NULL && getItemWithShortcutInternal(&contents->items[itemIndex], keyCode, modifiers, outMenuItemIdentifier)) { return true; } } return false; } bool UIMenuBar_getItemMatchingShortcut(UIMenuBar * self, unsigned int keyCode, unsigned int modifiers, int * outMenuIdentifier, int * outMenuItemIdentifier) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { if (getMenuContentsItemWithShortcutInternal(&self->menus[menuIndex].contents, keyCode, modifiers, outMenuItemIdentifier)) { if (outMenuIdentifier != NULL) { *outMenuIdentifier = self->menus[menuIndex].identifier; } return true; } } return false; } UIKeyShortcut * UIMenuBar_getItemShortcut(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier) { struct UIMenuContents * contents; struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, &contents); if (item != NULL) { return item->shortcut; } return NULL; } void UIMenuBar_setItemShortcut(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, UIKeyShortcut * shortcut) { struct UIMenuContents * contents; struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, &contents); if (item != NULL) { UIKeyShortcut_free(item->shortcut); item->shortcut = UIKeyShortcut_copy(shortcut); String_free(item->shortcutString); item->shortcutString = createKeyShortcutString(item->shortcut, self->useLocalizedKeyShortcuts); contents->layoutDirty = true; } } void UIMenuBar_setItemShortcutString(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, String shortcutString) { struct UIMenuContents * contents; struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, &contents); if (item != NULL) { String_free(item->shortcutString); item->shortcutString = String_copy(shortcutString); contents->layoutDirty = true; } } void UIMenuBar_setItemCustomIcon(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, UIAtlasEntry atlasEntry) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { item->customIcon = atlasEntry; } } void UIMenuBar_setItemTextColor(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, Color4f textColor) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { item->textColor = textColor; } } void UIMenuBar_setItemAlternateDisplayString(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, String alternateDisplayString, unsigned int modifiers) { struct UIMenuContents * menuContents; struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, &menuContents); if (item != NULL) { item->alternateTitles = realloc(item->alternateTitles, (item->alternateTitleCount + 1) * sizeof(*item->alternateTitles)); item->alternateTitles[item->alternateTitleCount].title = String_copy(alternateDisplayString); item->alternateTitles[item->alternateTitleCount].modifiers = modifiers; item->alternateTitleCount++; menuContents->layoutDirty = true; } } void UIMenuBar_setItemAnimatesMenuWhenInvoked(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, bool animate) { struct UIMenuItem_private * item = getMenuItem(self, menuIdentifier, menuItemIdentifier, NULL); if (item != NULL) { item->animateWhenInvoked = animate; } } void UIMenuBar_addMenuItem(UIMenuBar * self, int menuIdentifier, UIMenuItem item) { UIMenuBar_insertMenuItem(self, menuIdentifier, UINT_MAX, item); } void UIMenuBar_insertMenuItem(UIMenuBar * self, int menuIdentifier, unsigned int itemIndex, UIMenuItem item) { struct UIMenu_private * menu = getMenu(self, menuIdentifier); if (menu == NULL) { #ifdef DEBUG fprintf(stderr, "Warning: UIMenuBar_insertMenuItem() couldn't find menu with identifier %d\n", menuIdentifier); #endif } else { UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface); Vector2f itemPadding = getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding); if (itemIndex > menu->contents.itemCount) { itemIndex = menu->contents.itemCount; } float newItemHeight; if (item.title.bytes == NULL) { newItemHeight = SEPARATOR_HEIGHT; } else { newItemHeight = lineHeight + itemPadding.y * 2; } float newItemOffsetY; if (itemIndex > 0) { if (itemIndex < menu->contents.itemCount) { newItemOffsetY = menu->contents.items[itemIndex].offsetY; } else { newItemOffsetY = -menu->contents.size.y; } } else { newItemOffsetY = 0.0f; } menu->contents.items = realloc(menu->contents.items, (menu->contents.itemCount + 1) * sizeof(*menu->contents.items)); for (unsigned int itemIndex2 = menu->contents.itemCount; itemIndex2 > itemIndex; itemIndex2--) { menu->contents.items[itemIndex2] = menu->contents.items[itemIndex2 - 1]; menu->contents.items[itemIndex2].offsetY -= newItemHeight; } menu->contents.items[itemIndex] = copyMenuItem(self, item); menu->contents.items[itemIndex].offsetY = newItemOffsetY; if (menu->contents.items[itemIndex].title.bytes != NULL && menu->contents.items[itemIndex].titleWidth > menu->contents.size.x) { menu->contents.size.x = menu->contents.items[itemIndex].titleWidth; } menu->contents.size.y += newItemHeight; menu->contents.layoutDirty = true; menu->contents.itemCount++; } } void UIMenuBar_removeMenuItem(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier) { struct UIMenu_private * menu = getMenu(self, menuIdentifier); if (menu == NULL) { #ifdef DEBUG fprintf(stderr, "Warning: UIMenuBar_removeMenuItem() couldn't find menu with identifier %d\n", menuIdentifier); #endif } else { UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface); Vector2f itemPadding = getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding); for (unsigned int itemIndex = 0; itemIndex < menu->contents.itemCount; itemIndex++) { if (menu->contents.items[itemIndex].identifier == menuItemIdentifier) { float removedItemHeight; if (menu->contents.items[itemIndex].title.bytes == NULL) { removedItemHeight = SEPARATOR_HEIGHT; } else { removedItemHeight = lineHeight + itemPadding.y * 2; } disposeMenuItem(menu->contents.items[itemIndex]); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; menu->contents.itemCount--; for (; itemIndex < menu->contents.itemCount; itemIndex++) { menu->contents.items[itemIndex] = menu->contents.items[itemIndex + 1]; menu->contents.items[itemIndex].offsetY += removedItemHeight; } menu->contents.size.y -= removedItemHeight; return; } } #ifdef DEBUG fprintf(stderr, "Warning: UIMenuBar_removeMenuItem() couldn't find a menu item with identifier %d in menu %d\n", menuItemIdentifier, menuIdentifier); #endif } } void UIMenuBar_removeMenuItemAtIndex(UIMenuBar * self, int menuIdentifier, unsigned int itemIndex) { struct UIMenu_private * menu = getMenu(self, menuIdentifier); if (menu == NULL) { #ifdef DEBUG fprintf(stderr, "Warning: UIMenuBar_removeMenuItemAtIndex() couldn't find menu with identifier %d\n", menuIdentifier); #endif } else { float removedItemHeight; if (menu->contents.items[itemIndex].title.bytes == NULL) { removedItemHeight = SEPARATOR_HEIGHT; } else { UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface); Vector2f itemPadding = getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding); removedItemHeight = lineHeight + itemPadding.y * 2; } disposeMenuItem(menu->contents.items[itemIndex]); Shell_cancelTimer(self->scrollTimer); self->scrollTimer = SHELL_TIMER_INVALID; menu->contents.itemCount--; for (; itemIndex < menu->contents.itemCount; itemIndex++) { menu->contents.items[itemIndex] = menu->contents.items[itemIndex + 1]; menu->contents.items[itemIndex].offsetY += removedItemHeight; } menu->contents.size.y -= removedItemHeight; } } void UIMenuBar_addSubmenuItem(UIMenuBar * self, int menuIdentifier, int parentMenuItemIdentifier, UIMenuItem item) { UIMenuBar_insertSubmenuItem(self, menuIdentifier, parentMenuItemIdentifier, UINT_MAX, item); } void UIMenuBar_insertSubmenuItem(UIMenuBar * self, int menuIdentifier, int parentMenuItemIdentifier, unsigned int itemIndex, UIMenuItem item) { struct UIMenuItem_private * parentSubmenu = getMenuItem(self, menuIdentifier, parentMenuItemIdentifier, NULL); if (parentSubmenu == NULL) { #ifdef DEBUG fprintf(stderr, "Warning: UIMenuBar_insertSubmenuItem() couldn't find submenu with identifier %d in menu %d\n", parentMenuItemIdentifier, menuIdentifier); #endif } else { if (itemIndex > parentSubmenu->contents.itemCount) { itemIndex = parentSubmenu->contents.itemCount; } parentSubmenu->contents.items = realloc(parentSubmenu->contents.items, (parentSubmenu->contents.itemCount + 1) * sizeof(*parentSubmenu->contents.items)); for (unsigned int itemIndex2 = parentSubmenu->contents.itemCount; itemIndex2 > itemIndex; itemIndex2--) { parentSubmenu->contents.items[itemIndex2] = parentSubmenu->contents.items[itemIndex2 - 1]; } parentSubmenu->contents.items[itemIndex] = copyMenuItem(self, item); parentSubmenu->contents.items[itemIndex].offsetY = -parentSubmenu->contents.size.y; if (parentSubmenu->contents.items[itemIndex].title.bytes == NULL) { parentSubmenu->contents.size.y += SEPARATOR_HEIGHT; } else { if (parentSubmenu->contents.items[itemIndex].titleWidth > parentSubmenu->contents.size.x) { parentSubmenu->contents.size.x = parentSubmenu->contents.items[itemIndex].titleWidth; } UITypeface * typeface = UIToolkit_getUITypeface(self->appearance, UIToolkit_currentContext()->drawingInterface); float lineHeight = call_virtual(getLineHeight, typeface); Vector2f itemPadding = getAppearanceVector2f(self->appearance, UIMenuBar_itemPadding); parentSubmenu->contents.size.y += lineHeight + itemPadding.y * 2; } parentSubmenu->contents.itemCount++; } } static unsigned int getHierachicalItemTitleInternal(struct UIMenuContents * contents, int menuItemIdentifier, unsigned int depth, String * titleStrings, unsigned int titleStringCountMax) { if (depth >= titleStringCountMax) { return 0; } for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { if (contents->items[itemIndex].identifier == menuItemIdentifier) { titleStrings[depth] = contents->items[itemIndex].title; return depth; } if (contents->items[itemIndex].contents.itemCount > 0) { unsigned int submenuDepth = getHierachicalItemTitleInternal(&contents->items[itemIndex].contents, menuItemIdentifier, depth + 1, titleStrings, titleStringCountMax); if (submenuDepth > 0) { titleStrings[depth] = contents->items[itemIndex].title; return submenuDepth; } } } return 0; } unsigned int UIMenuBar_getHierachicalItemTitle(UIMenuBar * self, int menuIdentifier, int menuItemIdentifier, String * titleStrings, unsigned int titleStringCountMax) { struct UIMenu_private * menu = getMenu(self, menuIdentifier); if (menu == NULL) { return 0; } unsigned int depth = getHierachicalItemTitleInternal(&menu->contents, menuItemIdentifier, 1, titleStrings, titleStringCountMax); if (depth > 0) { titleStrings[0] = menu->title; } return depth + 1; } static void refreshShortcutStringsInternal(UIMenuBar * self, struct UIMenuContents * contents); static void refreshItemShortcutString(UIMenuBar * self, struct UIMenuItem_private * item) { if (item->contents.itemCount > 0) { refreshShortcutStringsInternal(self, &item->contents); } String_free(item->shortcutString); item->shortcutString = createKeyShortcutString(item->shortcut, self->useLocalizedKeyShortcuts); } static void refreshShortcutStringsInternal(UIMenuBar * self, struct UIMenuContents * contents) { for (unsigned int itemIndex = 0; itemIndex < contents->itemCount; itemIndex++) { refreshItemShortcutString(self, &contents->items[itemIndex]); } } void UIMenuBar_refreshAllShortcutStrings(UIMenuBar * self) { for (unsigned int menuIndex = 0; menuIndex < self->menuCount; menuIndex++) { refreshShortcutStringsInternal(self, &self->menus[menuIndex].contents); } }