/* 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 */ #ifndef __UIElement_H__ #define __UIElement_H__ #ifdef __cplusplus extern "C" { #endif typedef struct UIElement UIElement; #define UIElement_superclass StemObject typedef void (* UIElement_enumerationCallback)(UIElement * element, void * context); #include "gamemath/Rect4f.h" #include "gamemath/Vector2f.h" #include "renderer/Renderable.h" #include "renderer/RenderableIO.h" #include "renderer/VertexTypes.h" #include "shell/Shell.h" #include "stemobject/StemObject.h" #include "uitoolkit/UIAppearance.h" #include "uitoolkit/UIDrawingInterface.h" #include "uitoolkit/UIHitTestResultIO.h" #include "uitoolkit/UITypes.h" struct UIElement_connectionList { unsigned int elementCount; UIElement ** elements; UIElement * lastUsedConnection; }; #define UIElement_ivars \ StemObject_ivars \ \ Vector2f position; \ Vector2f relativeOrigin; \ UIElement * parent; \ UIAppearance appearance; \ bool visible; \ bool dirty; \ int tag; \ bool private_ivar(cycleMark); \ struct UIElement_connectionList connections[UI_DIRECTION_COUNT]; \ String tooltipString; \ Renderable * renderable; #define UIElement_vtable(self_type) \ StemObject_vtable(self_type) \ \ bool (* hitTest)(self_type * self, float x, float y, UIHitTestType type, int * outPriority, bool * outForwardNext); \ void (* hitTestList)(self_type * self, float x, float y, UIHitTestType type, int priorityOffset, UIHitTestResultIO * resultIO); \ UIEventResponse (* mouseDown)(self_type * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, bool isFinalTarget, double referenceTime); \ bool (* mouseUp)(self_type * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime); \ bool (* mouseMoved)(self_type * self, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime); \ bool (* mouseDragged)(self_type * self, unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime); \ UIEventResponse (* scrollWheel)(self_type * self, float x, float y, int deltaX, int deltaY, unsigned int modifiers, bool isFinalTarget, double referenceTime); \ UIEventResponse (* keyDown)(self_type * self, unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, bool isFinalTarget, double referenceTime); \ bool (* keyUp)(self_type * self, unsigned int keyCode, unsigned int modifiers, double referenceTime); \ bool (* keyModifiersChanged)(self_type * self, unsigned int modifiers, unsigned int lastModifiers, double referenceTime); \ bool (* menuActionDown)(self_type * self, unsigned int actionNumber, bool isRepeat, double referenceTime); \ bool (* menuActionUp)(self_type * self, unsigned int actionNumber, double referenceTime); \ bool (* menuDirectionDown)(self_type * self, UINavigationDirection direction, bool isRepeat, double referenceTime); \ bool (* menuDirectionUp)(self_type * self, UINavigationDirection direction, double referenceTime); \ bool (* setFocusedElement)(self_type * self, compat_type(UIElement *) element, compat_type(UIElement *) fromElement, UINavigationDirection directionFromElement); \ UIElement * (* getFocusedElement)(self_type * self); \ bool (* acceptsFocus)(self_type * self); \ void (* focusLost)(self_type * self); \ bool (* containsElement)(self_type * self, compat_type(UIElement *) element); \ Rect4f (* getBounds)(self_type * self); \ Rect4f (* getAbsoluteBounds)(self_type * self); \ Rect4f (* getFocusBounds)(self_type * self); \ Rect4f (* getClipBounds)(self_type * self); \ Rect4i (* getAbsoluteClipBounds)(self_type * self); \ bool (* ignoreClipForHitTest)(self_type * self, UIHitTestType type); \ Vector2f (* getRelativeOffset)(self_type * self); \ void (* draw)(self_type * self, Vector2f offset, UIDrawingInterface * drawingInterface, VertexIO * vertexIO); \ void (* listRenderables)(self_type * self, RenderableIO * renderableIO, int drawOrderOffset, Rect4i clipBounds); \ bool (* needsRedraw)(self_type * self); \ void (* enumerateElements)(self_type * self, UIElement_enumerationCallback callback, void * context); \ ShellCursorID (* getCursorAtPosition)(self_type * self, float x, float y); \ UITooltip (* getTooltipAtPosition)(self_type * self, float x, float y); \ void (* setTooltipString)(self_type * self, String tooltipString); \ void (* setVisible)(self_type * self, bool visible); \ bool (* shouldAutoconnect)(self_type * self); stemobject_declare(UIElement) // By default, UIElement_init will create a PRIMITIVE_TRIANGLES renderable that uses the current UIToolkitContext's // defaultRenderPipelineState and defaultShaderConfiguration. Call initNoRenderable to leave it uninitialized and provide // your own with different settings. // Takes ownership of appearance bool UIElement_init(UIElement * self, Vector2f position, Vector2f relativeOrigin, UIAppearance appearance); bool UIElement_initNoRenderable(UIElement * self, Vector2f position, Vector2f relativeOrigin, UIAppearance appearance); void UIElement_dispose(UIElement * self); // hitTest should return true if the specified event type should be targeted to this object for a mouse location specified by // x and y. outPriority can optionally be set when returning true. Higher numbers will get events before lower ones. // If returning true in a state where the corresponding mouseDown, scrollWheel, or keyDown would return // REPONSE_HANDLED_FORWARD_NEXT, outForwardNext can be set to true to allow hitTestSingle to return a later element. // outForwardNext defaults to false, so it's not necessary to assign it a value unless that value is true. // When calling UIElement_hitTest(), outPriority and outForwardNext must not be NULL; therefore, when implementing // hitTest(), it's not necessary to check for NULL before writing values to them. bool UIElement_hitTest(UIElement * self, float x, float y, UIHitTestType type, int * outPriority, bool * outForwardNext); // hitTestList should only be overridden for container objects that may need to return multiple child elements, and usually // doesn't need to be called directly. Call addResult() on resultIO once per element that passes hit testing. void UIElement_hitTestList(UIElement * self, float x, float y, UIHitTestType type, int priorityOffset, UIHitTestResultIO * resultIO); // hitTestSingle returns the highest-priority and frontmost child element that returns true from its hitTest function, unless // outForwardNext is also set to true, in which case it continues to the next element, if any, that doesn't set outForwardNext. // If every element successfully hit tested sets outForwardNext to true, hitTestSingle returns NULL. UIElement * UIElement_hitTestSingle(compat_type(UIElement *) self, float x, float y, UIHitTestType type); // In addition to the standard mouse event parameters, mouseDown should always be externally called with isFinalTarget set to // false. When not the final target, an element will hit test its children to determine event routing before calling mouseDown // on the best candidates in priority order with isFinalTarget set to true, halting when one returns RESPONSE_HANDLED. A return // value of RESPONSE_HANDLED_FORWARD_NEXT will be reflected in mouseDown's return value, but will otherwise continue event // processing the same as RESPONSE_UNHANDLED. Only the element that received a call to mouseDown with isFinalTarget set to true // and returned RESPONSE_HANDLED will receive the followup mouseDragged and mouseUp events. UIEventResponse UIElement_mouseDown(UIElement * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, bool isFinalTarget, double referenceTime); bool UIElement_mouseUp(UIElement * self, unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime); bool UIElement_mouseDragged(UIElement * self, unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime); // mouseMoved is typically called on all elements regardless of hit testing and priority. bool UIElement_mouseMoved(UIElement * self, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime); // scrollWheel performs a hit test in a similar way to mouseDown, using HIT_TEST_SCROLL_WHEEL rather than HIT_TEST_MOUSE_DOWN. UIEventResponse UIElement_scrollWheel(UIElement * self, float x, float y, int deltaX, int deltaY, unsigned int modifiers, bool isFinalTarget, double referenceTime); // keyDown performs a hit test in a similar way to mouseDown, using HIT_TEST_KEY_DOWN rather than HIT_TEST_MOUSE_DOWN. Return // values have the same semantic meaning, and the followup keyUp event will only be sent to the element that received a call // to keyDown with isFinalTarget set to true and returned RESPONSE_HANDLED. UIEventResponse UIElement_keyDown(UIElement * self, unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, bool isFinalTarget, double referenceTime); bool UIElement_keyUp(UIElement * self, unsigned int keyCode, unsigned int modifiers, double referenceTime); // keyModifiersChanged is typically called on all elements regardless of hit testing, focus, and priority. bool UIElement_keyModifiersChanged(UIElement * self, unsigned int modifiers, unsigned int lastModifiers, double referenceTime); // Caller conventions: During keyboard and gamepad UI navigation, call this when the "action" key or button is pressed // or released. If multiple types of action are possible while pointing at a single element, use actionNumber to distinguish // which action is being taken. The standard UIElement subclasses only recognize action 0, and handle it like a mouse click. // Receiver conventions: Perform an interaction with this element, generally analogous to a mouse click. actionNumber // is like a mouse button number. Return true if the action was handled, and false if it has no effect. bool UIElement_menuActionDown(UIElement * self, unsigned int actionNumber, bool isRepeat, double referenceTime); bool UIElement_menuActionUp(UIElement * self, unsigned int actionNumber, double referenceTime); // Caller conventions: During keyboard and gamepad UI navigation, call this when a direction key or button is pressed // or released. // Receiver conventions: The default implementation of these methods moves focus to the next connection in the specified // direction on menuDirectionDown. Only override them if you specifically need different behavior, such as if you're // implementing a compound element with multiple focus zones. Return true if the directional input resulted in a visible // change in the receiving element, or a change in focus. bool UIElement_menuDirectionDown(UIElement * self, UINavigationDirection direction, bool isRepeat, double referenceTime); bool UIElement_menuDirectionUp(UIElement * self, UINavigationDirection direction, double referenceTime); // Return true if the specified element was found and focus has been set to it. Clears focus if called with NULL. // Non-container elements should return true if element == self and they currently can accept focus. // The default implementation does the above, so only override if you're implementing a compound element or need to // take a specific action when gaining focus. bool UIElement_setFocusedElement(UIElement * self, compat_type(UIElement *) element, compat_type(UIElement *) fromElement, UINavigationDirection directionFromElement); // Return the deepest element in the focus hierarchy, or NULL if no element is focused. Non-container elements that return // true from acceptsFocus should return self from getFocusedElement. // The default implementation does the above, so only override if you're implementing a compound element. UIElement * UIElement_getFocusedElement(UIElement * self); // Return true if this element can accept focus; as in, whether a call to keyDown, menuAction, or menuDirection would be // processed in some meaningful way. // The default implementation returns false. Override this and return true to be able to accept focus. bool UIElement_acceptsFocus(UIElement * self); // Called when focus changes away from this element. Override if an action needs to be taken on focus loss. void UIElement_focusLost(UIElement * self); // Returns true if element exists as a child of self bool UIElement_containsElement(UIElement * self, compat_type(UIElement *) element); // Returns bounds relative to enclosing container Rect4f UIElement_getBounds(UIElement * self); // Returns the absolute bounds of the focused subelement, if any. In most cases, getBounds() should be // overridden, and getAbsoluteBounds() should not. Rect4f UIElement_getAbsoluteBounds(UIElement * self); // Returns bounds relative to world coordinates of the top-level container Rect4f UIElement_getFocusBounds(UIElement * self); // Returns a rectangle in local coordinates which, by default, will be set as the scissor region for this // element's renderable. The default value is RECT4f_EMPTY, which specifies no clipping. Rect4f UIElement_getClipBounds(UIElement * self); // Returns this element's clip bounds in window pixel space, suitable for RenderableIO. The default // implementation transforms the return value of getClipBounds() into window pixel space. In most cases, // getClipBounds() should be overridden, and getAbsoluteClipBounds() should not. Rect4i UIElement_getAbsoluteClipBounds(UIElement * self); // Return true if hit testing should proceed regardless of clipping even if the hit tested location is // outside clip bounds bool UIElement_ignoreClipForHitTest(UIElement * self, UIHitTestType type); // Returns drawing origin relative to parent. Depending on relativeOrigin and contents, this may not correspond to position or getBounds().left/bottom. Vector2f UIElement_getRelativeOffset(UIElement * self); // Returns the absolute offset of this element by evaluating the offsets of all of its parents. The result is the difference in offset between the result of getBounds() and getAbsoluteBounds(). Vector2f UIElement_getParentOffset(compat_type(UIElement *) self); void UIElement_draw(UIElement * self, Vector2f offset, UIDrawingInterface * drawingInterface, VertexIO * vertexIO); void UIElement_listRenderables(UIElement * self, RenderableIO * renderableIO, int drawOrderOffset, Rect4i clipBounds); bool UIElement_needsRedraw(UIElement * self); // Calls callback once per child element, including containers themselves. void UIElement_enumerateElements(UIElement * self, UIElement_enumerationCallback callback, void * context); // Returns the appropriate mouse cursor to display at the specified local coordinates ShellCursorID UIElement_getCursorAtPosition(UIElement * self, float x, float y); // Returns a record of the tooltip to be displayed at the specified local coordinates UITooltip UIElement_getTooltipAtPosition(UIElement * self, float x, float y); // To customize further, override getTooltipAtPosition() void UIElement_setTooltipString(UIElement * self, String tooltipString); // Sets whether this element draws and receives events. Default is true for most elements. The visible struct field // should be treated as read-only. void UIElement_setVisible(UIElement * self, bool visible); // Return true if this is an element that should be included in the tab chain when calling // UIContainer_autoconnectElements(), or false if it should be omitted. The default implementation returns false. bool UIElement_shouldAutoconnect(UIElement * self); // Make a connection to be used during menu navigation with menuDirectionDown. If bidirectional, target will also be // connected to self in the opposite direction(s). Directions can be combined with bitwise OR. void UIElement_connect(compat_type(UIElement *) self, UINavigationDirection direction, compat_type(UIElement *) target, bool bidirectional); // Shortcut for multiple calls to UIElement_connect(), connecting all additional UIElement arguments in sequence. // If connectAtEnds is true, the final element in the chain will be connected to the first one. void UIElement_connectSequence(UINavigationDirection direction, bool bidirectional, bool connectAtEnds, compat_type(UIElement *) firstElement, ...) __attribute__((sentinel)); // Removes a previously established UI connection. If no such connection exists, does nothing. If bidirectional, // the connection to self will also be removed from target. Directions can be combined with bitwise OR. void UIElement_disconnect(compat_type(UIElement *) self, UINavigationDirection direction, compat_type(UIElement *) target, bool bidirectional); // Removes all connections in the specified direction(s). void UIElement_resetConnections(compat_type(UIElement *) self, UINavigationDirection direction); // Removes all connections in all directions. #define UIElement_resetAllConnections(self) UIElement_resetConnections(self, UI_LEFT | UI_RIGHT | UI_UP | UI_DOWN | UI_PREVIOUS | UI_NEXT) // Traverses the connections chain in direction until an element that returns true from acceptFocus is found. Returns // NULL if a cycle is detected, or the chain ends before finding an element that accepts focus. UIElement * UIElement_findNextFocusableElement(compat_type(UIElement *) self, UINavigationDirection direction); // Returns the highest-level non-NULL element in the parent chain UIElement * UIElement_getTopParent(compat_type(UIElement *) self); // If this element is in focus, walks to the top of the element hierarchy and unfocuses it void UIElement_unfocus(compat_type(UIElement *) self); // Returns true if this element is focused according to its top parent bool UIElement_isFocused(compat_type(UIElement *) self); // Returns true if this element or one of its children is focused according to its top parent bool UIElement_isInFocusChain(compat_type(UIElement *) self); // Returns true if the parent chain of child includes the specified parent bool UIElement_isChildOfElement(compat_type(UIElement *) child, compat_type(UIElement *) parent); Renderable * createUIElementRenderableWithDefaultSettings(compat_type(UIElement *) element, PrimitiveType primitiveType); void UIElement_getElementRenderableVertices(Renderable * renderable, VertexIO * vertexIO, void * context); // Returns a rect suitable for returning from a getBounds() implementation, pixel snapped and adjusted for relativeOrigin Rect4f UIElement_boundsRectWithOrigin(Vector2f position, Vector2f relativeOrigin, Vector2f size); // Translates between local coordinates (offset by parent containers) and window coordinates at the root level Vector2f UIElement_rootToLocalVector(compat_type(UIElement *) self, Vector2f position); Vector2f UIElement_localToRootVector(compat_type(UIElement *) self, Vector2f position); Rect4f UIElement_rootToLocalRect(compat_type(UIElement *) self, Rect4f position); Rect4f UIElement_localToRootRect(compat_type(UIElement *) self, Rect4f position); // Returns a value suitable for passing as clipBounds to RenderableIO_addRenderable(), assuming parentClipBounds // is the clipBounds argument passed to listRenderables(), and ownClipBounds is the return value of the element's // getAbsoluteClipBounds(). In contrast to Rect4i_intersection(), which returns an empty rectangle if either of its // inputs is empty, this function returns the non-empty rectangle if one of them is not empty. Rect4i UIElement_intersectClipBounds(Rect4i parentClipBounds, Rect4i ownClipBounds); // Returns true if a mouseDown at clickPosition at clickTime should be considered to be the second click of a double // click action. lastClickPosition and lastClickTime should be the position and time of the most recent click that // occurred within the double-clickable element being tested. bool UIToolkit_isValidDoubleClick(Vector2f clickPosition, Vector2f lastClickPosition, double clickTime, double lastClickTime, UIAppearance appearance); #ifdef __cplusplus } #endif #endif