// zlib open source license // // Copyright (c) 2019 David Forsgren Piuva // // 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. #ifndef DFPSR_API_GUI #define DFPSR_API_GUI #include "../base/Handle.h" #include "../api/stringAPI.h" #include "../implementation/image/Image.h" #include "../implementation/gui/InputEvent.h" #include "../implementation/gui/VisualTheme.h" // createBackendWindow should be implemented outside of the core framework // Choose one of the window backends in SDK/native to compile and link with your application. // Handle createBackendWindow(const dsr::String& title, int width, int height); // Constness on handles doesn't propagate to any inner types // "const Comopnent&" only means that the writable Component handle can be created from a sub-expression // because the location where the handle is stored cannot be overwritten. // This allow getting a component by name and using it as an argument without being stored in a variable. namespace dsr { enum class ReturnCode { Good, KeyNotFound, ParsingFailure }; // A handle to a window. // The Window wraps itself around native window backends to abstract away platform specific details. // It also makes it easy to load and use a graphical interface using the optional component system. class DsrWindow; using Window = Handle; // A handle to a GUI component. // Components are an abstraction for graphical user interfaces, which might not always be powerful enough. // * If you're making something advanced that components cannot do, // you can also use draw calls and input events directly against the window without using Component. class VisualComponent; using Component = Handle; // Window Construction // A portable window will be wrapped around a native window backend supplied from a call to createBackendWindow. Window window_create(const dsr::String& title, int32_t width, int32_t height); // If the game starts in full screen, this constructor should be used instead. // Otherwise the canvas may ask for the window's dimensions while the system still keeps the old dimensions due to delays. Window window_create_fullscreen(const dsr::String& title); // Returns true iff the window exists. bool window_exists(const Window& window); // Set window title // Assigns the window's title. void window_setTitle(Window& window, const dsr::String& title); // Returns the window's title. String window_getTitle(Window& window); // Clipboard // Most operating systems require that a window is associated with each request to access the clipboard. // This allow anti-virus software to see the user consent for accessing the clipboard, // because a key combination commonly associated with pasting text was pressed before the request was made. // Returns content from an internal or extenal clipboard. // The internal clipboard within the application is used when the system specific backend has not implemented clipboard access. String window_loadFromClipboard(Window& window, double timeoutInSeconds); // Stores the text argument to an internal or extenal clipboard. // The internal clipboard within the application is used when the system specific backend has not implemented clipboard access. void window_saveToClipboard(Window& window, const ReadableString &text, double timeoutInSeconds); // Layout files // Loading an interface by parsing a layout file's content, with any external resources loaded relative to fromPath. // Embedded images do not count as external resources, but file paths need fromPath in order to know from where they will be loaded. // Raises an exception if window doesn't exist. void window_loadInterfaceFromString(const Window& window, const dsr::String& content, const ReadableString &fromPath); // Loading an interface by parsing a layout file's content, with any external resources loaded relative to the current directory. // Useful when your application has already assigned the current directory, or when all resources are embedded into the layout. // Raises an exception if window doesn't exist. void window_loadInterfaceFromString(const Window& window, const dsr::String& content); // Loading an interface by parsing a layout file loaded by filename. // Raises an exception if window doesn't exist. void window_loadInterfaceFromFile(const Window& window, const dsr::ReadableString& filename); // Store the interface back into a layout file. // Raises an exception if window doesn't exist. String window_saveInterfaceToString(const Window& window); // Find a component // Get the component being stored directly in the window // Raises an exception if window doesn't exist. // There should always exist a root component where more components can be added recursively Component window_getRoot(const Window& window); // Returns a handle to the first matching component of the name in window. // For consistent behavior, make sure that only one component has the name, or use window_findComponentByNameAndIndex after giving each a unique index. // If mustExist is true, not finding any component with the name throws an exception. // Raises an exception if window doesn't exist. // Component names are case sensitive to reduce the risk of accidental naming conflicts among many components. Component window_findComponentByName(const Window& window, const ReadableString& name, bool mustExist = true); // Returns a handle to the first matching component of the name and index in window. // If mustExist is true, not finding any component with the name throws an exception. // Raises an exception if window doesn't exist. // Component names are case sensitive to reduce the risk of accidental naming conflicts among many components. Component window_findComponentByNameAndIndex(const Window& window, const ReadableString& name, int index, bool mustExist = true); // Calls back with the component handle and index for each match of the name. // Can be used to count the number of components by incrementing a counter for each match. // Can be used to detach all the matching components and have them automatically garbage collected by reference counting. // The index can be used as a filter or to look up information. // To allow detaching components while iterating over the list of children, order is reversed for child components. // Raises an exception if window doesn't exist. // Component names are case sensitive to reduce the risk of accidental naming conflicts among many components. void window_findAllComponentsByName(const Window& window, const ReadableString& name, std::function callback); // The three main events to run in a loop at the end of the main function // If the window's event queue contained any resize of the window, the canvas and the depth buffer will be replaced during this call. // New calls to window_getCanvas and window_getDepthBuffer are required after this call, because the window could be given a new size. // Returns true iff any event were processed. // By calling window_executeEvents in a loop while returning false, one can wait for input. // Sleeping for 10 milliseconds is quite responsive while saving lots of battery. // Only redrawing the regions that has changed (dirty rectangles et cetera) can further save power. // Example: // while (!window_executeEvents(window)) { // time_sleepSeconds(0.01); // } // window_drawComponents(window); // window_showCanvas(window); bool window_executeEvents(const Window& window); // Draw the root component and its children to the canvas. // Raises an exception if window doesn't exist. void window_drawComponents(const Window& window); // Executes deferred actions without having to call window_drawComponents. // So if you just called component_detachFromParent to remove a child component, this will trigger it instantly // so that calls to component_getChildCount will show the new count without having to wait for window_drawComponents. // Make sure not to call window_flushDeferredActions from inside of a comonent's event, because that would remove the whole point of deferring actions. // If an event is triggered from within another event, it can easily cause bugs in the program by not executing transactions one after another. // Raises an exception if window doesn't exist. void window_flushDeferredActions(const Window& window); // Show the canvas. // Raises an exception if window doesn't exist. void window_showCanvas(const Window& window); // Pixel upscaling // The pixel-scale is the width and height of each canvas pixel when displayed on the window. // The color and depth buffers from window_getCanvas and window_getDepthBuffer will shrink to fit each pixel within the window. // Partial pixels at right and bottom sides are replaced with black padding, // so that mouse coordinates can be divided and multiplied evenly during scale conversion. // If using a higher value than the default 1, upscaling will be done during the call to window_showCanvas. // The backend window will receive the upscaled image to display over the whole window. // Gets the current pixel scale. // Raises an exception if window doesn't exist. int window_getPixelScale(const Window& window); // Assigns a new pixel scale. // Raises an exception if window doesn't exist. // Just like when handling a window resize, this will replace the canvas and depth buffer. // Any old handles to canvas and depth buffer will become useless, so fetch new image handles from the window to avoid black flickering. void window_setPixelScale(const Window& window, int scale); // Cursor // Sets the cursor visibility for window, hiding if visible is false and showing if visible is true. // Returns true on success and false on failure. bool window_setCursorVisibility(const Window& window, bool visible); // Returns true iff the cursor is allowed to be displayed over window. bool window_getCursorVisibility(const Window& window); // Tries move the cursor so that its active point is at (x, y) within window. // To get back the new cursor location and see if it worked, you must wait for the triggered mouse move event. // It is impossible to know if the operation succeeded while returning, due to absolute position input devices overwriting the side-effect. // It is recommended to avoid this method if you can, but some games require capturing relative mouse movements. // If you use this then make sure to test it carefully on all targeted operating systems and both relative and absolute input devices. // Pre-conditions: // 0 <= x < width // 0 <= y < height // Moving the mouse outside of the window's region may cause undefined behavior between platforms. // Post-condition: // Returns true on success and false on failure. // WARNING! // Only set the cursor location if you know for sure that the cursor is controlled using relative input devices, such as a mouse or track-pad. // If an absolute input method is used, such as a stylus-pen, touch-screen or eye-tracker, the cursor will shake quickly. // The solution is to let the user select between modes for relative and absolute inputs. // You can for example let absolute input mode move the cursor freely within a dead zone and hover over edges // to rotate the camera itself, when pattern detection noticed a stylus pen being used instead of a mouse. // WARNING! // On MS-Windows the cursor will always move, but on X11 the cursor must be within the window's region. // Only use this in full-screen mode to prevent getting stuck outside of the window. // WARNING! // Due to potential race-conditions, you can not assume that the next mouse move event is generated by this call. bool window_setCursorPosition(const Window& window, int x, int y); // Full screen void window_setFullScreen(const Window& window, bool enabled); bool window_isFullScreen(const Window& window); // Fetch the window's surfaces // Always get the canvas (and any depth buffer) after calls to window_executeEvents or window_setPixelScale, // because these may replace the canvas with a new size. // TODO: Prevent the backend window from freeing the memory while the canvas is still being used. // Get the canvas/color-buffer. // Raises an exception if window doesn't exist. // The canvas size will be smaller when pixelScale is larger, because the canvas has to fit inside the window. AlignedImageRgbaU8 window_getCanvas(const Window& window); // Get the depth buffer allocated on demand. // Raises an exception if window doesn't exist. // If you never call this method, no depth buffer will be allocated. // If you call it at the same time as window_getCanvas, it will have the same size as the canvas. AlignedImageF32 window_getDepthBuffer(const Window& window); // The low-resolution canvas and depth buffer dimensions are relative to mouse events given to components. // Because component are drawn to the canvas and affected by upscaling. // Returns the width of the canvas. // Raises an exception if window doesn't exist. int window_getCanvasWidth(const Window& window); // Returns the height of the canvas. // Raises an exception if window doesn't exist. int window_getCanvasHeight(const Window& window); // The window's inner dimensions are relative to mouse events received directly from the window at full pixel resolution. // Returns the inner width of the window. // Raises an exception if window doesn't exist. int window_getInnerWidth(const Window& window); // Returns the inner height of the window. // Raises an exception if window doesn't exist. int window_getInnerHeight(const Window& window); // Direct window events // Listen to window mouse events. // Raises an exception if window doesn't exist. // event.mouseEventType gives the type of mouse event. // event.key gives the key being used. void window_setMouseEvent(const Window& window, const MouseCallback& mouseEvent); // Listen to window keyboard events. // Raises an exception if window doesn't exist. // event.keyboardEventType gives the type of keyboard event. // event.dsrKey gives the key being used. void window_setKeyboardEvent(const Window& window, const KeyboardCallback& keyboardEvent); // Listen to the window close event. // Raises an exception if window doesn't exist. void window_setCloseEvent(const Window& window, const EmptyCallback& closeEvent); // Components // Create a new component belonging to parent. // Returns a handle to the component or an empty handle if className have not been registered. // Can use component_exists on the result to check if the operation was a success. // className must be the name of a registered persistent class inheriting from VisualComponent. // identifierName is used to find the component. // index can be used to identify multiple components with the same name without having to generate numbered names. Component component_create(const Component& parent, const ReadableString& className, const ReadableString& identifierName, int index = 0); // An alternative to window_loadInterfaceFromString when you want the interface loaded into a panel instead of the whole window. // Useful for dynamically creating and destroying interfaces to save memory and load the application faster. // Loading an interface by parsing a layout file's content, with any external resources loaded relative to fromPath. // Embedded images do not count as external resources, but file paths need fromPath in order to know from where they will be loaded. // Raises an exception if parent doesn't exist. Component component_createWithInterfaceFromString(Component& parent, const String& content, const ReadableString &fromPath); // With fromPath implicitly being the current path. Component component_createWithInterfaceFromString(Component& parent, const String& content); // Loading the interface from a file and loading resources relative to filename's parent folder. Component component_createWithInterfaceFromFile(Component& parent, const String& filename); // Returns true iff the component exists. bool component_exists(const Component& component); // Marks component as detached, so that it will be safely removed from the parent's list next time window_drawComponents or window_flushDeferredActions is called. // Deferring the removal makes sure that no event's callback is called from inside of another event. void component_detachFromParent(const Component& component); // Returns the number of direct (non-recursive) child components attached to parent, or -1 if parent is a null handle. int component_getChildCount(const Component& parent); // Returns the child at childIndex from parent, or an empty handle if parent does not exist or childIndex is out of bound. // Child indices go from 0 to count - 1. // The child index refers to the parent's list of children, not the child's index attribute. Component component_getChild(const Component& parent, int childIndex); // Returns a handle to the first matching component of the name in parent recursively. // Can be used instead of window_findComponentByName to further reduce the risk of name collisions, by only looking for recursive child components of a specific parent component. // If mustExist is true, not finding any component with the name within parent throws an exception. // Raises an exception if parent doesn't exist. // Component names are case sensitive to reduce the risk of accidental naming conflicts among many components. Component component_findChildByName(const Component& parent, const ReadableString& name, bool mustExist = true); // Returns a handle to the first matching component of the name and index in parent recursively. // If mustExist is true, not finding any component with the name within parent throws an exception. // Raises an exception if parent doesn't exist. // Component names are case sensitive to reduce the risk of accidental naming conflicts among many components. Component component_findChildByNameAndIndex(const Component& parent, const ReadableString& name, int index, bool mustExist = true); // Returns true iff propertyName exists in component. // Property names are case insensitive, to give more flexibility for the few property names. bool component_hasProperty(const Component& component, const ReadableString& propertyName); // Sets a property found using propertyName in component to the value serialized in value. // Raises an exception if component doesn't exist. // Matching of propertyName is case insensitive, to give more flexibility for the few property names. // Returns ReturnCode::Good if assigned. // Unless mustAssign forces an exception. // Returns ReturnCode::KeyNotFound if propertyName wasn't found in component. // Unless mustAssign forces an exception. // Returns ReturnCode::ParsingFailure if propertyName was found but value couldn't be converted to its type. ReturnCode component_setProperty(const Component& component, const ReadableString& propertyName, const ReadableString& value, bool mustAssign = true); // An advanced overload allowing loading of resources from a specific path using the fromPath argument. ReturnCode component_setProperty(const Component& component, const ReadableString& propertyName, const ReadableString& value, const ReadableString& fromPath, bool mustAssign = true); // A version for setting integers and booleans. // For integers: // Just set value to whatever you want assigned directly. // This is faster than using component_setProperty by not parsing the value from any string. // For booleans: // Use 1 (or another non-zero value) for true and 0 for false. // Boolean properties will convery any non-zero values into ones. ReturnCode component_setProperty_integer(const Component& component, const ReadableString& propertyName, int64_t value, bool mustAssign = true); // A version for setting images. ReturnCode component_setProperty_image(const Component& component, const ReadableString& propertyName, const OrderedImageRgbaU8& value, bool mustAssign = true); // A version optimized for basic strings to bypass quote mangling // The new value is given as it is without unmangling. ReturnCode component_setProperty_string(const Component& component, const ReadableString& propertyName, const ReadableString& value, bool mustAssign = true); // Returns a property found using propertyName in component. // Raises an exception if component doesn't exist. // Matching of propertyName is case insensitive, to give more flexibility for the few property names. // If mustExist is true // Raises an exception when propertyName isn't found. // If mustExist is false // Returns an empty string when propertyName isn't found. String component_getProperty(const Component& component, const ReadableString& propertyName, bool mustExist = true); // A version for getting integers and booleans. // Returns defaultValue on failure, which should use a value that is never actually used. int64_t component_getProperty_integer(const Component& component, const ReadableString& propertyName, bool mustExist = true, int64_t defaultValue = 0); // A version for getting images OrderedImageRgbaU8 component_getProperty_image(const Component& component, const ReadableString& propertyName, bool mustExist = true); // A version optimized for basic strings to bypass quote mangling. // Returns the result without adding any quote signs or escape characters. String component_getProperty_string(const Component& component, const ReadableString& propertyName, bool mustExist = true); // Call a named method in the component using optional text arguments. // Matching of methodName is case insensitive, to give more flexibility for the few method names. String component_call(const Component& component, const ReadableString& methodName); String component_call(const Component& component, const ReadableString& methodName, const ReadableString& arguments); // Component events // The main activation of clickable components. // The pressed callback doesn't take any arguments, because it should be possible to generate from multiple input methods. void component_setPressedEvent(const Component& component, const EmptyCallback& event); // Called before the component and it's child components are destructed. // The event is called when there are no more handles to component. // Be careful not to forget any extra handles to the component if a memory leak would significantly change the behavior. void component_setDestroyEvent(const Component& component, const EmptyCallback& event); // Mouse-down activates when any mouse button is pressed down within the component // Raises an exception if component doesn't exist. // The component itself decides if the mouse is inside, which allow rounded components to act as their true shape. void component_setMouseDownEvent(const Component& component, const MouseCallback& mouseEvent); // Mouse-up should eventually follow after a mouse-down event, to ensure basic transaction safety. // Raises an exception if component doesn't exist. // * Even if the mouse is dragged outside of the component or window before being lifted. // * Even if the component is removed from the window while the button is pressed, // the button press will keep it alive long enough to receive the mouse-up event before being freed. void component_setMouseUpEvent(const Component& component, const MouseCallback& mouseEvent); // Mouse-move is triggered when the mouse moves over the component. // Raises an exception if component doesn't exist. // * When pressed down inside of the component, dragging outside the component or even window will // continue to give mouse-move events to the callback. // * If dragging left of or above the window, event.position may contain negative coordinates. void component_setMouseMoveEvent(const Component& component, const MouseCallback& mouseEvent); // Mouse-scroll is triggered by scrolling in any direction. // Raises an exception if component doesn't exist. // Currently only supporting MouseKeyEnum::ScrollUp and MouseKeyEnum::ScrollDown as values in event.key. void component_setMouseScrollEvent(const Component& component, const MouseCallback& mouseEvent); // Key-down only comes when a button is pressed down. (No repeat) // Raises an exception if component doesn't exist. // The backend window is responsible to filter away any false positives for down events caused by repetition. void component_setKeyDownEvent(const Component& component, const KeyboardCallback& keyboardEvent); // Key-up only comes when a button is lifted after being pressed. (No repeat) // Raises an exception if component doesn't exist. void component_setKeyUpEvent(const Component& component, const KeyboardCallback& keyboardEvent); // Key-type comes both when a key is pressed, and then repeatedly without having to lift the key. // Raises an exception if component doesn't exist. // There's usually a second's delay before quickly repeating. void component_setKeyTypeEvent(const Component& component, const KeyboardCallback& keyboardEvent); // Select events are sent when the selected index of something has changed. // Used in Listbox to cover updates from all different ways the selection may change. void component_setSelectEvent(const Component& component, const IndexCallback& selectEvent); // Theme // Apply the given theme recursively to all components in the window's interface. // Raises an exception if window or component doesn't exist. // Components will gather what they can from the theme and save it for later. // Changing a theme while being used by an interface or adding new components, // should apply the theme again to ensure that all changes are applied. // TODO: Automate this process by storing a reference to the theme in each component and checking for updates before drawing. void window_applyTheme(const Window& window, const VisualTheme& theme); } #endif