/* Copyright (c) 2018 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 "uitoolkit/UIContainer.h" #include #include #include #include #define stemobject_implementation UIContainer stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_entry(hitTest); stemobject_vtable_entry(hitTestList); stemobject_vtable_entry(mouseDown); stemobject_vtable_entry(mouseUp); stemobject_vtable_entry(mouseMoved); stemobject_vtable_entry(mouseDragged); stemobject_vtable_entry(scrollWheel); stemobject_vtable_entry(keyDown); stemobject_vtable_entry(keyUp); stemobject_vtable_entry(keyModifiersChanged); stemobject_vtable_entry(menuActionDown); stemobject_vtable_entry(menuActionUp); stemobject_vtable_entry(menuDirectionDown); stemobject_vtable_entry(menuDirectionUp); stemobject_vtable_entry(setFocusedElement); stemobject_vtable_entry(getFocusedElement); stemobject_vtable_entry(getRelativeOffset); stemobject_vtable_entry(ignoreClipForHitTest); stemobject_vtable_entry(getClipBounds); stemobject_vtable_entry(acceptsFocus); stemobject_vtable_entry(containsElement); stemobject_vtable_entry(getBounds); stemobject_vtable_entry(getFocusBounds); stemobject_vtable_entry(draw); stemobject_vtable_entry(listRenderables); stemobject_vtable_entry(needsRedraw); stemobject_vtable_entry(enumerateElements); stemobject_vtable_entry(getCursorAtPosition); stemobject_vtable_entry(addElement); stemobject_vtable_entry(removeElement); stemobject_vtable_entry(removeAllElements); stemobject_vtable_entry(getElementCountWithTag); stemobject_vtable_entry(getElementWithTagAtIndex); stemobject_vtable_entry(enumerateElementsWithTag); stemobject_vtable_entry(getContentOffset); stemobject_vtable_entry(resetMouseDownTargets); stemobject_vtable_end(); UIContainer * UIContainer_create(Vector2f position, Vector2f relativeOrigin, Vector2f size, bool clipContents, UIAppearance appearance) { stemobject_create_implementation(init, position, relativeOrigin, size, clipContents, appearance) } bool UIContainer_init(UIContainer * self, Vector2f position, Vector2f relativeOrigin, Vector2f size, bool clipContents, UIAppearance appearance) { call_super(initNoRenderable, self, position, relativeOrigin, appearance); self->size = size; self->clipContents = clipContents; self->hitTestSelf = false; self->elementCount = 0; self->private_ivar(elementAllocatedCount) = 8; self->elements = malloc(sizeof(*self->elements) * self->private_ivar(elementAllocatedCount)); self->focusedElementIndex = UICONTAINER_FOCUS_NONE; self->lastMousePosition = VECTOR2f_ZERO; for (unsigned int buttonIndex = 0; buttonIndex < UICONTAINER_MOUSE_BUTTON_NUMBER_RESPONSE_COUNT; buttonIndex++) { self->lastMouseDownTargets[buttonIndex] = NULL; } self->lastKeyDownTarget = NULL; for (unsigned int directionIndex = 0; directionIndex < UI_DIRECTION_COUNT; directionIndex++) { self->lastMenuDirectionTargets[directionIndex] = NULL; } for (unsigned int actionIndex = 0; actionIndex < UICONTAINER_MENU_ACTION_NUMBER_RESPONSE_COUNT; actionIndex++) { self->lastMenuActionTargets[actionIndex] = NULL; } return true; } void UIContainer_dispose(UIContainer * self) { for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].owned) { call_virtual(dispose, self->elements[elementIndex].element); self->elements[elementIndex].element = NULL; } } free(self->elements); call_super(dispose, self); } void UIContainer_addElement(UIContainer * self, compat_type(UIElement *) element, bool takeOwnership) { if (element == NULL) { #ifdef DEBUG fprintf(stderr, "Warning: Attempt to add a NULL UIElement to UIContainer %p\n", self); #endif return; } for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].element == element) { return; } } if (self->private_ivar(elementAllocatedCount) <= self->elementCount) { self->private_ivar(elementAllocatedCount) *= 2; self->elements = realloc(self->elements, sizeof(*self->elements) * self->private_ivar(elementAllocatedCount)); } self->elements[self->elementCount].element = element; self->elements[self->elementCount].element->parent = (UIElement *) self; self->elements[self->elementCount].owned = takeOwnership; self->dirty = true; ++self->elementCount; } static void severConnectionsCallback(UIElement * element, void * context) { UIElement * removedElement = context; for (unsigned int directionIndex = 0; directionIndex < UI_DIRECTION_COUNT; directionIndex++) { for (unsigned int elementIndex = 0; elementIndex < element->connections[directionIndex].elementCount; elementIndex++) { if (element->connections[directionIndex].elements[elementIndex] == removedElement) { UIElement_disconnect(element, 1 << directionIndex, removedElement, false); for (unsigned int elementIndex2 = 0; elementIndex2 < removedElement->connections[directionIndex].elementCount; elementIndex2++) { UIElement_connect(element, 1 << directionIndex, removedElement->connections[directionIndex].elements[elementIndex2], false); } } } } } void UIContainer_removeElement(UIContainer * self, compat_type(UIElement *) element) { // TODO: Potentially dangerous if this happens during iteration! Add safeguards just in case for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].element == element) { UIElement * topParent = self->elements[elementIndex].element->parent; if (topParent != NULL) { while (topParent->parent != NULL) { topParent = topParent->parent; } call_virtual(enumerateElements, topParent, severConnectionsCallback, element); } if (elementIndex == self->focusedElementIndex && elementIndex >= self->elementCount - 1) { UIElement * topParent = UIElement_getTopParent(self); if (call_virtual(getFocusedElement, topParent) == call_virtual(getFocusedElement, self)) { if (self->elementCount == 1) { self->focusedElementIndex = UICONTAINER_FOCUS_NONE; call_virtual(setFocusedElement, topParent, self, NULL, UI_NONE); } else { if (!call_virtual(setFocusedElement, topParent, self->elements[self->focusedElementIndex - 1].element, NULL, UI_NONE)) { call_virtual(setFocusedElement, topParent, self, NULL, UI_NONE); } do { self->focusedElementIndex--; } while (self->focusedElementIndex < UICONTAINER_FOCUS_NONE && !call_virtual(acceptsFocus, self->elements[self->focusedElementIndex].element)); } } else { if (self->elementCount == 1) { self->focusedElementIndex = UICONTAINER_FOCUS_NONE; } else { do { self->focusedElementIndex--; } while (self->focusedElementIndex < UICONTAINER_FOCUS_NONE && !call_virtual(acceptsFocus, self->elements[self->focusedElementIndex].element)); } } } else if (self->focusedElementIndex > elementIndex && self->focusedElementIndex != UICONTAINER_FOCUS_NONE) { do { self->focusedElementIndex--; } while (self->focusedElementIndex < UICONTAINER_FOCUS_NONE && !call_virtual(acceptsFocus, self->elements[self->focusedElementIndex].element)); } self->elements[elementIndex].element->parent = NULL; if (self->elements[elementIndex].owned) { call_virtual(dispose, self->elements[elementIndex].element); } --self->elementCount; for (; elementIndex < self->elementCount; elementIndex++) { self->elements[elementIndex] = self->elements[elementIndex + 1]; } self->dirty = true; break; } } for (unsigned int buttonIndex = 0; buttonIndex < UICONTAINER_MOUSE_BUTTON_NUMBER_RESPONSE_COUNT; buttonIndex++) { if (self->lastMouseDownTargets[buttonIndex] == element) { self->lastMouseDownTargets[buttonIndex] = NULL; } } for (unsigned int directionIndex = 0; directionIndex < UI_DIRECTION_COUNT; directionIndex++) { if (self->lastMenuDirectionTargets[directionIndex] == element) { self->lastMenuDirectionTargets[directionIndex] = NULL; } } for (unsigned int actionIndex = 0; actionIndex < UICONTAINER_MENU_ACTION_NUMBER_RESPONSE_COUNT; actionIndex++) { if (self->lastMenuActionTargets[actionIndex] == element) { self->lastMenuActionTargets[actionIndex] = NULL; } } if (element == self->lastKeyDownTarget) { self->lastKeyDownTarget = NULL; } } void UIContainer_removeAllElements(UIContainer * self) { while (self->elementCount > 0) { UIContainer_removeElement(self, self->elements[self->elementCount - 1].element); } } bool UIContainer_containsElement(UIContainer * self, compat_type(UIElement *) element) { for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].element == element || call_virtual(containsElement, self->elements[elementIndex].element, element)) { return true; } } return false; } void UIContainer_enumerateElements(UIContainer * self, UIElement_enumerationCallback callback, void * context) { callback((UIElement *) self, context); for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].element != NULL) { call_virtual(enumerateElements, self->elements[elementIndex].element, callback, context); } } } unsigned int UIContainer_getElementCountWithTag(UIContainer * self, int tag) { unsigned int elementCountWithTag = 0; for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].element->tag == tag) { elementCountWithTag++; } } return elementCountWithTag; } UIElement * UIContainer_getElementWithTagAtIndex(UIContainer * self, int tag, unsigned int index) { unsigned int elementCountWithTag = 0; for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].element->tag == tag) { if (elementCountWithTag == index) { return self->elements[elementIndex].element; } elementCountWithTag++; } } return NULL; } void UIContainer_enumerateElementsWithTag(UIContainer * self, int tag, bool (* callback)(UIElement * element, void * context), void * context) { for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (self->elements[elementIndex].element->tag == tag) { if (!callback(self->elements[elementIndex].element, context)) { break; } } } } static Rect4f getChildBoundsUnion(UIContainer * self) { Rect4f result = RECT4f_EMPTY; for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { result = Rect4f_union(result, call_virtual(getBounds, self->elements[elementIndex].element)); } return result; } Vector2f UIContainer_getContentOffset(UIContainer * self) { if (self->relativeOrigin.x == INFINITY) { return VECTOR2f_ZERO; } if (self->size.x == INFINITY) { Rect4f childBounds = getChildBoundsUnion(self); return VECTOR2f(-childBounds.xMin, -childBounds.yMin); } return call_virtual(getRelativeOffset, self); } void UIContainer_resetMouseDownTargets(UIContainer * self) { for (unsigned int buttonIndex = 0; buttonIndex < UICONTAINER_MOUSE_BUTTON_NUMBER_RESPONSE_COUNT; buttonIndex++) { self->lastMouseDownTargets[buttonIndex] = NULL; } } bool UIContainer_hitTest(UIContainer * self, float x, float y, UIHitTestType type, int * outPriority, bool * outForwardNext) { if (!self->visible || !self->hitTestSelf) { return NULL; } return call_super_virtual(hitTest, self, x, y, type, outPriority, outForwardNext); } void UIContainer_hitTestList(UIContainer * self, float x, float y, UIHitTestType type, int priorityOffset, UIHitTestResultIO * resultIO) { if (self->visible) { Vector2f offset = call_virtual(getRelativeOffset, self); x -= offset.x; y -= offset.y; for (unsigned int elementIndex = self->elementCount - 1; elementIndex < self->elementCount; elementIndex--) { call_virtual(hitTestList, self->elements[elementIndex].element, x, y, type, priorityOffset, resultIO); } if (self->hitTestSelf) { x += offset.x; y += offset.y; int priority = 0; bool forwardNext = false; if (call_virtual(hitTest, self, x, y, type, &priority, &forwardNext)) { call_virtual(addResult, resultIO, self, priority + priorityOffset, forwardNext); } } } } UIEventResponse UIContainer_mouseDown(UIContainer * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, bool isFinalTarget, double referenceTime) { if (!self->visible || buttonNumber >= UICONTAINER_MOUSE_BUTTON_NUMBER_RESPONSE_COUNT || isFinalTarget) { return RESPONSE_UNHANDLED; } self->lastMousePosition = VECTOR2f(x, y); if (buttonMask == 1u << buttonNumber) { self->mousePositionAtMouseDown = self->lastMousePosition; } UIHitTestResultIO * resultIO = UIHitTestResultIO_create(); call_virtual(hitTestList, self, x, y, HIT_TEST_MOUSE_DOWN, 0, resultIO); if (resultIO->resultCount == 0) { UIHitTestResultIO_dispose(resultIO); return RESPONSE_UNHANDLED; } UIHitTestResultIO_sortResults(resultIO); bool handled = false; Vector2f containerOffset = Vector2f_add(UIElement_getParentOffset(self), call_virtual(getContentOffset, self)); for (unsigned int resultIndex = 0; resultIndex < resultIO->resultCount; resultIndex++) { Vector2f offset = Vector2f_subtract(UIElement_getParentOffset(resultIO->results[resultIndex].element), containerOffset); UIEventResponse response = call_virtual(mouseDown, resultIO->results[resultIndex].element, buttonNumber, buttonMask, x - offset.x, y - offset.y, modifiers, true, referenceTime); switch (response) { case RESPONSE_UNHANDLED: break; case RESPONSE_HANDLED: self->lastMouseDownTargets[buttonNumber] = resultIO->results[resultIndex].element; return RESPONSE_HANDLED; case RESPONSE_HANDLED_FORWARD_NEXT: handled = true; break; case RESPONSE_IGNORE: return RESPONSE_IGNORE; } } UIHitTestResultIO_dispose(resultIO); return handled ? RESPONSE_HANDLED_FORWARD_NEXT : RESPONSE_UNHANDLED; } bool UIContainer_mouseUp(UIContainer * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime) { if (buttonNumber >= UICONTAINER_MOUSE_BUTTON_NUMBER_RESPONSE_COUNT || self->lastMouseDownTargets[buttonNumber] == NULL || !call_virtual(containsElement, self, self->lastMouseDownTargets[buttonNumber])) { return false; } bool mouseDeltaMode = Shell_getMouseDeltaMode(); self->lastMousePosition = VECTOR2f(x, y); UIElement * target = self->lastMouseDownTargets[buttonNumber]; Vector2f offset = Vector2f_subtract(UIElement_getParentOffset(target), UIElement_getParentOffset(self)); self->lastMouseDownTargets[buttonNumber] = NULL; bool result = call_virtual(mouseUp, target, buttonNumber, buttonMask, x - offset.x, y - offset.y, modifiers, referenceTime); if (buttonMask == 0 && self->parent == NULL && (x != self->mousePositionAtMouseDown.x || y != self->mousePositionAtMouseDown.y) && !mouseDeltaMode) { result |= call_virtual(mouseMoved, self, x, y, x - self->mousePositionAtMouseDown.x, y - self->mousePositionAtMouseDown.y, modifiers, referenceTime); } return result; } bool UIContainer_mouseMoved(UIContainer * self, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime) { bool handled = false; if (!self->visible) { return false; } self->lastMousePosition = VECTOR2f(x, y); Vector2f offset = call_virtual(getRelativeOffset, self); x -= offset.x; y -= offset.y; for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (call_virtual(mouseMoved, self->elements[elementIndex].element, x, y, deltaX, deltaY, modifiers, referenceTime)) { handled = true; } } return handled; } bool UIContainer_mouseDragged(UIContainer * self, unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime) { bool handled = false; if (!self->visible) { return false; } self->lastMousePosition = VECTOR2f(x, y); Vector2f containerOffset = Vector2f_add(UIElement_getParentOffset(self), call_virtual(getContentOffset, self)); for (unsigned int buttonIndex = 0; buttonIndex < UICONTAINER_MOUSE_BUTTON_NUMBER_RESPONSE_COUNT; buttonIndex++) { if ((buttonMask & 1 << buttonIndex) && self->lastMouseDownTargets[buttonIndex] != NULL && call_virtual(containsElement, self, self->lastMouseDownTargets[buttonIndex])) { bool alreadyCalled = false; for (unsigned int buttonIndex2 = 0; buttonIndex2 < buttonIndex; buttonIndex2++) { if ((buttonMask & 1 << buttonIndex2) && self->lastMouseDownTargets[buttonIndex2] != NULL && call_virtual(containsElement, self, self->lastMouseDownTargets[buttonIndex2])) { alreadyCalled = true; break; } } if (!alreadyCalled) { Vector2f offset = Vector2f_subtract(UIElement_getParentOffset(self->lastMouseDownTargets[buttonIndex]), containerOffset); if (call_virtual(mouseDragged, self->lastMouseDownTargets[buttonIndex], buttonMask, x - offset.x, y - offset.y, deltaX, deltaY, modifiers, referenceTime)) { handled = true; } } } } return handled; } UIEventResponse UIContainer_scrollWheel(UIContainer * self, float x, float y, int deltaX, int deltaY, unsigned int modifiers, bool isFinalTarget, double referenceTime) { if (!self->visible || isFinalTarget) { return RESPONSE_UNHANDLED; } UIHitTestResultIO * resultIO = UIHitTestResultIO_create(); call_virtual(hitTestList, self, x, y, HIT_TEST_SCROLL_WHEEL, 0, resultIO); if (resultIO->resultCount == 0) { UIHitTestResultIO_dispose(resultIO); return RESPONSE_UNHANDLED; } UIHitTestResultIO_sortResults(resultIO); bool handled = false; Vector2f containerOffset = Vector2f_add(UIElement_getParentOffset(self), call_virtual(getContentOffset, self)); for (unsigned int resultIndex = 0; resultIndex < resultIO->resultCount; resultIndex++) { Vector2f offset = Vector2f_subtract(UIElement_getParentOffset(resultIO->results[resultIndex].element), containerOffset); UIEventResponse response = call_virtual(scrollWheel, resultIO->results[resultIndex].element, x - offset.x, y - offset.y, deltaX, deltaY, modifiers, true, referenceTime); switch (response) { case RESPONSE_UNHANDLED: break; case RESPONSE_HANDLED: return RESPONSE_HANDLED; case RESPONSE_HANDLED_FORWARD_NEXT: handled = true; break; case RESPONSE_IGNORE: return RESPONSE_IGNORE; } } UIHitTestResultIO_dispose(resultIO); return handled ? RESPONSE_HANDLED_FORWARD_NEXT : RESPONSE_UNHANDLED; } UIEventResponse UIContainer_keyDown(UIContainer * self, unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, bool isFinalTarget, double referenceTime) { if (!self->visible || isFinalTarget) { return RESPONSE_UNHANDLED; } UIHitTestResultIO * resultIO = UIHitTestResultIO_create(); Vector2f position = Vector2f_subtract(self->lastMousePosition, call_virtual(getRelativeOffset, self)); call_virtual(hitTestList, self, position.x, position.y, HIT_TEST_KEY_DOWN, 0, resultIO); if (resultIO->resultCount == 0) { UIHitTestResultIO_dispose(resultIO); return RESPONSE_UNHANDLED; } UIHitTestResultIO_sortResults(resultIO); bool handled = false; for (unsigned int resultIndex = 0; resultIndex < resultIO->resultCount; resultIndex++) { self->lastKeyDownTarget = resultIO->results[resultIndex].element; UIEventResponse response = call_virtual(keyDown, resultIO->results[resultIndex].element, charCode, keyCode, modifiers, isRepeat, true, referenceTime); switch (response) { case RESPONSE_UNHANDLED: break; case RESPONSE_HANDLED: return RESPONSE_HANDLED; case RESPONSE_HANDLED_FORWARD_NEXT: handled = true; break; case RESPONSE_IGNORE: return RESPONSE_IGNORE; } } UIHitTestResultIO_dispose(resultIO); self->lastKeyDownTarget = NULL; return handled ? RESPONSE_HANDLED_FORWARD_NEXT : RESPONSE_UNHANDLED; } bool UIContainer_keyUp(UIContainer * self, unsigned int keyCode, unsigned int modifiers, double referenceTime) { bool handled = false; if (self->lastKeyDownTarget != NULL && call_virtual(containsElement, self, self->lastKeyDownTarget)) { handled = call_virtual(keyUp, self->lastKeyDownTarget, keyCode, modifiers, referenceTime); self->lastKeyDownTarget = NULL; } return handled; } bool UIContainer_keyModifiersChanged(UIContainer * self, unsigned int modifiers, unsigned int lastModifiers, double referenceTime) { bool handled = false; for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (call_virtual(keyModifiersChanged, self->elements[elementIndex].element, modifiers, lastModifiers, referenceTime)) { handled = true; } } return handled; } bool UIContainer_menuActionDown(UIContainer * self, unsigned int actionNumber, bool isRepeat, double referenceTime) { if (!self->visible) { return false; } if (actionNumber >= UICONTAINER_MENU_ACTION_NUMBER_RESPONSE_COUNT) { return false; } if (self->focusedElementIndex != UICONTAINER_FOCUS_NONE) { self->lastMenuActionTargets[actionNumber] = self->elements[self->focusedElementIndex].element; return call_virtual(menuActionDown, self->elements[self->focusedElementIndex].element, actionNumber, isRepeat, referenceTime); } return false; } bool UIContainer_menuActionUp(UIContainer * self, unsigned int actionNumber, double referenceTime) { UIElement * target; if (actionNumber >= UICONTAINER_MENU_ACTION_NUMBER_RESPONSE_COUNT || self->lastMenuActionTargets[actionNumber] == NULL || (self->lastMenuActionTargets[actionNumber] != (UIElement *) self && !call_virtual(containsElement, self, self->lastMenuActionTargets[actionNumber]))) { return false; } target = self->lastMenuActionTargets[actionNumber]; self->lastMenuActionTargets[actionNumber] = NULL; return call_virtual(menuActionUp, target, actionNumber, referenceTime); } bool UIContainer_menuDirectionDown(UIContainer * self, UINavigationDirection direction, bool isRepeat, double referenceTime) { if (!self->visible) { return false; } if (self->focusedElementIndex == UICONTAINER_FOCUS_NONE || call_virtual(getFocusedElement, self->elements[self->focusedElementIndex].element) == NULL) { UIElement * focus = UIElement_findNextFocusableElement(self, direction); if (focus != NULL) { UIElement * topParent = UIElement_getTopParent(self); UIElement * focusedElement = call_virtual(getFocusedElement, topParent); return call_virtual(setFocusedElement, topParent, focus, focusedElement, direction); } } else { for (unsigned int directionIndex = 0; directionIndex < UI_DIRECTION_COUNT; directionIndex++) { if (direction & 1 << directionIndex) { self->lastMenuDirectionTargets[directionIndex] = self->elements[self->focusedElementIndex].element; return call_virtual(menuDirectionDown, self->elements[self->focusedElementIndex].element, direction, isRepeat, referenceTime); } } } return false; } bool UIContainer_menuDirectionUp(UIContainer * self, UINavigationDirection direction, double referenceTime) { for (unsigned int directionIndex = 0; directionIndex < UI_DIRECTION_COUNT; directionIndex++) { if (direction & 1 << directionIndex) { UIElement * target = self->lastMenuDirectionTargets[directionIndex]; if (target != NULL && (target == (UIElement *) self || call_virtual(containsElement, self, target))) { self->lastMenuDirectionTargets[directionIndex] = NULL; return call_virtual(menuDirectionUp, target, direction, referenceTime); } } } return false; } bool UIContainer_setFocusedElement(UIContainer * self, compat_type(UIElement *) element, compat_type(UIElement *) fromElement, UINavigationDirection directionFromElement) { UIElement * focusedElement = NULL; if (self->parent == NULL) { focusedElement = call_virtual(getFocusedElement, self); } if (element == NULL) { self->focusedElementIndex = UICONTAINER_FOCUS_NONE; if (focusedElement != NULL) { call_virtual(focusLost, focusedElement); } return true; } if (element == self) { if (call_super(setFocusedElement, self, element, fromElement, directionFromElement)) { if (focusedElement != NULL && focusedElement != (UIElement *) self) { call_virtual(focusLost, focusedElement); } return true; } return false; } for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (call_virtual(setFocusedElement, self->elements[elementIndex].element, element, fromElement, directionFromElement)) { self->focusedElementIndex = elementIndex; if (focusedElement != NULL && focusedElement != element) { call_virtual(focusLost, focusedElement); } return true; } } return false; } UIElement * UIContainer_getFocusedElement(UIContainer * self) { if (self->focusedElementIndex == UICONTAINER_FOCUS_NONE || !self->elements[self->focusedElementIndex].element->visible || !call_virtual(acceptsFocus, self->elements[self->focusedElementIndex].element)) { return call_virtual(acceptsFocus, self) ? (UIElement *) self : NULL; } return call_virtual(getFocusedElement, self->elements[self->focusedElementIndex].element); } bool UIContainer_acceptsFocus(UIContainer * self) { for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (call_virtual(acceptsFocus, self->elements[elementIndex].element)) { return true; } } return false; } Rect4f UIContainer_getBounds(UIContainer * self) { if (self->relativeOrigin.x == INFINITY) { Rect4f result = getChildBoundsUnion(self); return Rect4f_offset(result, VECTOR2f(roundpositivef(self->position.x), roundpositivef(self->position.y))); } if (self->size.x == INFINITY) { Rect4f result = getChildBoundsUnion(self); return Rect4f_fromPositionAndSize(VECTOR2f(roundpositivef(self->position.x - (result.xMax - result.xMin) * self->relativeOrigin.x), roundpositivef(self->position.y - (result.yMax - result.yMin) * self->relativeOrigin.y)), VECTOR2f(result.xMax - result.xMin, result.yMax - result.yMin)); } return UIElement_boundsRectWithOrigin(self->position, self->relativeOrigin, self->size); } Rect4f UIContainer_getFocusBounds(UIContainer * self) { if (self->focusedElementIndex == UICONTAINER_FOCUS_NONE || !call_virtual(acceptsFocus, self->elements[self->focusedElementIndex].element)) { return RECT4f_EMPTY; } return call_virtual(getFocusBounds, self->elements[self->focusedElementIndex].element); } Vector2f UIContainer_getRelativeOffset(UIContainer * self) { if (self->relativeOrigin.x == INFINITY) { return self->position; } if (self->size.x == INFINITY) { Rect4f bounds = getChildBoundsUnion(self); return VECTOR2f(roundpositivef(self->position.x - bounds.xMin - (bounds.xMax - bounds.xMin) * self->relativeOrigin.x), roundpositivef(self->position.y - bounds.yMin - (bounds.yMax - bounds.yMin) * self->relativeOrigin.y)); } return call_super_virtual(getRelativeOffset, self); } bool UIContainer_ignoreClipForHitTest(UIContainer * self, UIHitTestType type) { // TODO: This is not quite the right solution; instead, hitTestList/hitTest should take clip bounds and make its own determination? for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (call_virtual(ignoreClipForHitTest, self->elements[elementIndex].element, type)) { return true; } } return false; } Rect4f UIContainer_getClipBounds(UIContainer * self) { if (self->clipContents) { return call_virtual(getBounds, self); } return RECT4f_EMPTY; } void UIContainer_draw(UIContainer * self, Vector2f offset, UIDrawingInterface * drawingInterface, VertexIO * vertexIO) { self->dirty = false; if (!self->visible) { return; } offset = Vector2f_add(offset, call_virtual(getRelativeOffset, self)); for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { call_virtual(draw, self->elements[elementIndex].element, offset, drawingInterface, vertexIO); } } void UIContainer_listRenderables(UIContainer * self, RenderableIO * renderableIO, int drawOrderOffset, Rect4i clipBounds) { self->dirty = false; if (!self->visible) { return; } if (self->renderable != NULL) { RenderableIO_addRenderable(renderableIO, self->renderable, drawOrderOffset, clipBounds); } clipBounds = UIElement_intersectClipBounds(clipBounds, call_virtual(getAbsoluteClipBounds, self)); for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { call_virtual(listRenderables, self->elements[elementIndex].element, renderableIO, drawOrderOffset, clipBounds); } } bool UIContainer_needsRedraw(UIContainer * self) { if (!self->visible) { return false; } for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (call_virtual(needsRedraw, self->elements[elementIndex].element)) { return true; } } return self->dirty; } ShellCursorID UIContainer_getCursorAtPosition(UIContainer * self, float x, float y) { Vector2f offset = call_virtual(getRelativeOffset, self); x -= offset.x; y -= offset.y; for (unsigned int buttonIndex = 0; buttonIndex < sizeof(self->lastMouseDownTargets) / sizeof(self->lastMouseDownTargets[0]); buttonIndex++) { if (self->lastMouseDownTargets[buttonIndex] != NULL && call_virtual(containsElement, self, self->lastMouseDownTargets[buttonIndex])) { return call_virtual(getCursorAtPosition, self->lastMouseDownTargets[buttonIndex], x, y); } } if (self->visible) { UIElement * hitTestResult = UIElement_hitTestSingle(self, x, y, HIT_TEST_MOUSE_OVER); if (hitTestResult != NULL && hitTestResult != (UIElement *) self) { Vector2f targetOffset = UIElement_getParentOffset(hitTestResult); return call_virtual(getCursorAtPosition, hitTestResult, x + offset.x - targetOffset.x, y + offset.y - targetOffset.y); } } return call_super_virtual(getCursorAtPosition, self, x + offset.x, y + offset.y); } void UIContainer_autoconnectElements(compat_type(UIContainer *) selfUntyped, bool connectAtEnds) { UIContainer * self = selfUntyped; UIElement * firstElement = NULL, * lastElement = NULL; for (unsigned int elementIndex = 0; elementIndex < self->elementCount; elementIndex++) { if (call_virtual(shouldAutoconnect, self->elements[elementIndex].element)) { if (lastElement != NULL) { UIElement_connect(lastElement, UI_NEXT, self->elements[elementIndex].element, true); } lastElement = self->elements[elementIndex].element; if (firstElement == NULL) { firstElement = lastElement; } } } if (connectAtEnds && firstElement != NULL && firstElement != lastElement) { UIElement_connect(lastElement, UI_NEXT, firstElement, true); } }