| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- // zlib open source license
- //
- // Copyright (c) 2018 to 2023 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_GUI_VISUALCOMPONENT
- #define DFPSR_GUI_VISUALCOMPONENT
- #include "../persistent/includePersistent.h"
- #include "BackendWindow.h"
- #include "FlexRegion.h"
- #include "InputEvent.h"
- #include "componentStates.h"
- #include "VisualTheme.h"
- #include "../api/imageAPI.h"
- #include "../api/drawAPI.h"
- namespace dsr {
- // A reusable method for calling the media machine that allow providing additional variables as style flags.
- MediaResult component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed = 0, int focused = 0, int hovered = 0);
- class VisualComponent : public Persistent {
- PERSISTENT_DECLARATION(VisualComponent)
- public: // Relations
- // Handle to the backend window.
- Handle<BackendWindow> window;
- // TODO: Should a weak handle be implemented to safely avoid cycles, or is this safe enough?
- // Parent component
- VisualComponent *parent = nullptr;
- IRect givenSpace; // Remembering the local region that was reserved inside of the parent component.
- bool regionAccessed = false; // If someone requested access to the region, remember to update layout in case of new settings.
- // Child components
- List<Handle<VisualComponent>> children;
- // Remember the component used for a drag event.
- // Ensures that mouse down events are followed by mouse up events on the same component.
- int holdCount = 0;
- // Marked for removal from the parent when set to true.
- bool detach = false;
- // Remember the pressed component for sending mouse move events outside of its region.
- Handle<VisualComponent> dragComponent;
- private: // States
- // Use methods to set the current state, then have it copied to previousState after calling updateStateEvent in sendNotifications.
- ComponentState currentState = 0;
- ComponentState previousState = 0;
- private: // State updates
- // TODO: Can a faster update be made using a limited traversal from changed component to root, while reusing the same code for the update?
- // Called after changing direct states.
- void updateIndirectStates();
- // Looking for recent state changes and sending notifications through updateStateEvent for each components that had a state change.
- // Deferring update notifications using this makes sure that events that trigger updates get to finish before the next one starts.
- // This reduces the risk of dead-locks, race-conditions, pointer errors...
- // Also checking which components are marked for removal and detaching them, so that the object is not deleted while a memeber method is being called.
- void sendNotifications();
- // Remove the zeroes in addMask from ones in the component and all child components.
- void applyStateAndMask(ComponentState keepMask);
- // Clears directStates from all other components sharing the root, iff unique is true.
- // Adds directStates to the component.
- // Updates indirect states based on direct states.
- void addStateBits(ComponentState directStates, bool unique);
- // Removes directStates to the component.
- // Updates indirect states based on direct states.
- void removeStateBits(ComponentState directStates);
- public: // Focus is reset when a new component is focused.
- // It was clicked directly last time.
- inline bool isFocused() { return (this->currentState & componentState_focusDirect) != 0; }
- // One of its recursive children was clicked last time.
- inline bool ownsFocus() { return (this->currentState & (componentState_focusDirect | componentState_focusIndirect)) != 0; }
- // Remove focus from all of the component's children.
- void defocusChildren();
- // Give focus to a trail from root to the component.
- void makeFocused();
- public: // Hover is reset before assigned again using a bit mask.
- // The cursor hovered within this component without being occluded.
- bool isHovered() { return (this->currentState & componentState_hoverDirect) != 0; }
- // The cursor hovered within its region, but one of its recursive children got the direct hover state.
- bool ownsHover() { return (this->currentState & (componentState_hoverDirect | componentState_hoverIndirect)) != 0; }
- // Make the component directly hovered and its parents indirectly hovered.
- void hover();
- // Remove the hover effect.
- void leave();
- public: // Showing overlay
- inline bool showingOverlay() { return (this->currentState & componentState_showingOverlayDirect) != 0; }
- inline bool ownsOverlay() { return (this->currentState & componentState_showingOverlay) != 0; }
- void showOverlay();
- void hideOverlay();
- public:
- // Saved properties
- FlexRegion region;
- PersistentString name;
- PersistentInteger index;
- PersistentBoolean visible = PersistentBoolean(true);
- void declareAttributes(StructureDefinition &target) const override;
- public:
- Persistent* findAttribute(const ReadableString &name) override;
- public:
- // Generated automatically from region in applyLayout
- IRect location;
- void setLocation(const IRect &newLocation);
- // Applied reqursively while selecting the correct theme
- VisualTheme theme = theme_getDefault();
- public:
- void applyTheme(VisualTheme theme);
- VisualTheme getTheme() const;
- public:
- // Constructor
- VisualComponent();
- // Destructor
- virtual ~VisualComponent();
- public:
- virtual bool isContainer() const;
- IRect getLocation();
- void setRegion(const FlexRegion &newRegion);
- FlexRegion getRegion() const;
- void setVisible(bool visible);
- bool getVisible() const;
- void setName(const String& newName);
- String getName() const;
- void setIndex(int index);
- int getIndex() const;
- public: // Internal events that component classes override to know when something has changed.
- // Called after the component has been created, moved or resized.
- virtual void updateLocationEvent(const IRect& oldLocation, const IRect& newLocation);
- // Called after a component's state changed, when it is relatively safe to do so.
- // All state changes will be sent at the same time, because state changes are often used to trigger other changes.
- // Changes to the state made within the notification will not trigger new notifications, because the old state is saved after the call is finished.
- virtual void updateStateEvent(ComponentState oldState, ComponentState newState);
- public: // Callbacks that the application use by assigning lambdas to specific components in the interface.
- DECLARE_CALLBACK(pressedEvent, emptyCallback);
- DECLARE_CALLBACK(destroyEvent, emptyCallback);
- DECLARE_CALLBACK(mouseDownEvent, mouseCallback);
- DECLARE_CALLBACK(mouseUpEvent, mouseCallback);
- DECLARE_CALLBACK(mouseMoveEvent, mouseCallback);
- DECLARE_CALLBACK(mouseScrollEvent, mouseCallback);
- DECLARE_CALLBACK(keyDownEvent, keyboardCallback);
- DECLARE_CALLBACK(keyUpEvent, keyboardCallback);
- DECLARE_CALLBACK(keyTypeEvent, keyboardCallback);
- DECLARE_CALLBACK(selectEvent, indexCallback);
- public:
- // Returning a shader pointer to the topmost direct visible child that contains pixelPosition.
- // The pixelPosition is relative to the called component's upper left corner.
- Handle<VisualComponent> getDirectChild(const IVector2D& pixelPosition);
- // Returning a shared pointer to itself.
- // Currently not working for the root component because of limitations in C++.
- Handle<VisualComponent> getHandle();
- public:
- // Draw the component
- // The component is responsible for drawing the component at this->location + offset.
- // The caller is responsible for drawing the background for any pixels in the component that might not be fully opaque.
- // If drawing out of bound, the pixels that are outside should be skipped without any warning nor crash.
- // To clip the drawing of a component when calling this, give a sub-image and adjust for the new coordinate system using offset.
- // If not implemented, a rectangle will mark the region where the component will be drawn as a reference.
- // targetImage is the image being drawn to.
- // offset is the upper left corner of the parent container relative to the image.
- // Clipping will affect the offset by being relative to the new sub-image.
- void draw(ImageRgbaU8& targetImage, const IVector2D& offset);
- // Draw the component itself to targetImage at relativeLocation.
- // The method is responsible for clipping without a warning when bound is outside of targetImage.
- virtual void drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation);
- // Draw the component's overlays on top of other components in the window.
- // Overlays are drawn using absolute positions on the canvas.
- // The absoluteOffset is the location of the component's upper left corner relative to the whole window's canvas.
- // Use for anything that needs to be drawn on top of other components without being clipped by any parent components.
- virtual void drawOverlay(ImageRgbaU8& targetImage, const IVector2D &absoluteOffset);
- // Draw the component while skipping pixels outside of clipRegion
- // Multiple calls with non-overlapping clip regions should be equivalent to one call with the union of all clip regions.
- // This means that the draw methods should handle border clipping so that no extra borderlines or rounded edges appear from nowhere.
- // Example:
- // drawClipped(i, o, IRect(0, 0, 20, 20)) // Full region
- // <=>
- // drawClipped(i, o, IRect(0, 0, 10, 20)) // Left half
- // drawClipped(i, o, IRect(10, 0, 10, 20)) // Right half
- // Drawing with the whole target image as a clip region should be equivalent to a corresponding call to draw with the same targetImage and offset.
- // draw(i, o) <=> drawClipped(i, o, IRect(0, 0, i.width(), i.height()))
- void drawClipped(ImageRgbaU8 targetImage, const IVector2D& offset, const IRect& clipRegion);
- // Add a child component
- // Preconditions:
- // The parent's component type is a container.
- // The child does not already have a parent.
- void addChildComponent(Handle<VisualComponent> child);
- // Called with any persistent type when constructing child components from text
- bool addChild(Handle<Persistent> child) override;
- // Called when saving to text
- int getChildCount() const override;
- Handle<Persistent> getChild(int index) const override;
- // Returns true iff child is a member of the component
- // Searches recursively
- bool hasChild(VisualComponent *child) const;
- bool hasChild(Handle<VisualComponent> child) const;
- // Find the first child component with the requested name using a case sensitive match.
- // Returns: A shared pointer to the child or null if not found.
- Handle<VisualComponent> findChildByName(ReadableString name) const;
- Handle<VisualComponent> findChildByNameAndIndex(ReadableString name, int index) const;
- // Detach the component from any parent
- void detachFromParent();
- // Adapt the location based on the space given by the parent.
- // The given space is usually a rectangle starting at the origin with the same dimensions as the parent component.
- // If the parent has decorations around the child components, the region may include some padding from which the flexible regions calculate the locations from in percents.
- // For example: A given space from 10 to 90 pixels will have 0% at 10 and 100% at 90.
- // A toolbar may give non-overlapping spaces that are assigned automatically to simplify the process of maintaining the layout while adding and removing child components.
- // TODO: How can internal changes to inner dimensions be detected by the parent to run this method again?
- virtual void applyLayout(const IRect& givenSpace);
- // Update layout when the component moved but the parent has the same dimensions
- void updateLayout();
- // TODO: Remake desiredDimensions into a private variable, so that one can update it when attributes are changed and notify parents that they need to change size and redraw images.
- // Parent components that place child components automatically can ask them what their minimum useful dimensions are in pixels, so that their text will be visible.
- // The component can still be resized to less than these dimensions, because the outer components can't give more space than what is given by the window.
- virtual IVector2D getDesiredDimensions();
- // Return true to turn off automatic drawing of and interaction with child components.
- virtual bool managesChildren();
- // Get the component's absolute position relative to the window's client region.
- //IVector2D getAbsolutePosition();
- // Calling updateLocationEvent without changing the location, to be used when a child component changed its desired dimensions from altering attributes.
- bool childChanged = false;
- // Called before rendering or getting mouse input in case that a child component changed desired dimensions.
- void updateChildLocations();
- // Returns true iff the pixel relative to the parent container's upper left corner is inside of the component.
- // By default, it returns true when pixelPosition is within the component's location, because most component are solid.
- // The caller is responsible for checking if the component is visible (this->visible.value), so this method would return true if the pixelPosition is inside of an invisible component.
- virtual bool pointIsInside(const IVector2D& pixelPosition);
- // Returns true iff the pixelPosition relative to the parent container's upper left corner is inside of the component's overlay.
- // The caller is responsible for checking if the component is showing an overlay (this->showOverlay).
- virtual bool pointIsInsideOfOverlay(const IVector2D& pixelPosition);
- // Return true iff the pixelPosition relative to the parent container's upper left corner is inside of the region causing a hover effect.
- virtual bool pointIsInsideOfHover(const IVector2D& pixelPosition);
- // Send a mouse down event to the component
- // pixelPosition is relative to the component's own upper left corner. TODO: Does this make sense, or should it use parent coordinates?
- // The component is reponsible for bound checking, which can be used to either block the signal or pass to components below.
- // If recursive is true, notifications will be supressed to prevent duplicate events when called from within receiveMouseEvent.
- void sendMouseEvent(const MouseEvent& event, bool recursive = false);
- void sendKeyboardEvent(const KeyboardEvent& event);
- // Defines what the component does when it has received an event that didn't hit any sub components on the way.
- // pixelPosition is relative to the parent's (this->parent) upper left corner.
- // This is not a callback event, but a way for the component to handle events.
- virtual void receiveMouseEvent(const MouseEvent& event);
- virtual void receiveKeyboardEvent(const KeyboardEvent& event);
- // Notifies when the theme has been changed, so that temporary data depending on the theme can be replaced.
- virtual void changedTheme(VisualTheme newTheme);
- // Override to be notified about individual attribute changes
- virtual void changedAttribute(const ReadableString &name);
- // Override to be notified about location changes
- virtual void changedLocation(const IRect &oldLocation, const IRect &newLocation) {};
- // Custom call handler to manipulate components across a generic API
- virtual String call(const ReadableString &methodName, const ReadableString &arguments);
- };
- }
- #endif
|