VisualComponent.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2018 to 2023 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. #ifndef DFPSR_GUI_VISUALCOMPONENT
  24. #define DFPSR_GUI_VISUALCOMPONENT
  25. #include "../persistent/includePersistent.h"
  26. #include "BackendWindow.h"
  27. #include "FlexRegion.h"
  28. #include "InputEvent.h"
  29. #include "componentStates.h"
  30. #include "VisualTheme.h"
  31. #include "../api/imageAPI.h"
  32. #include "../api/drawAPI.h"
  33. namespace dsr {
  34. // A reusable method for calling the media machine that allow providing additional variables as style flags.
  35. 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);
  36. class VisualComponent : public Persistent {
  37. PERSISTENT_DECLARATION(VisualComponent)
  38. public: // Relations
  39. // Handle to the backend window.
  40. Handle<BackendWindow> window;
  41. // TODO: Should a weak handle be implemented to safely avoid cycles, or is this safe enough?
  42. // Parent component
  43. VisualComponent *parent = nullptr;
  44. IRect givenSpace; // Remembering the local region that was reserved inside of the parent component.
  45. bool regionAccessed = false; // If someone requested access to the region, remember to update layout in case of new settings.
  46. // Child components
  47. List<Handle<VisualComponent>> children;
  48. // Remember the component used for a drag event.
  49. // Ensures that mouse down events are followed by mouse up events on the same component.
  50. int holdCount = 0;
  51. // Marked for removal from the parent when set to true.
  52. bool detach = false;
  53. // Remember the pressed component for sending mouse move events outside of its region.
  54. Handle<VisualComponent> dragComponent;
  55. private: // States
  56. // Use methods to set the current state, then have it copied to previousState after calling updateStateEvent in sendNotifications.
  57. ComponentState currentState = 0;
  58. ComponentState previousState = 0;
  59. private: // State updates
  60. // TODO: Can a faster update be made using a limited traversal from changed component to root, while reusing the same code for the update?
  61. // Called after changing direct states.
  62. void updateIndirectStates();
  63. // Looking for recent state changes and sending notifications through updateStateEvent for each components that had a state change.
  64. // Deferring update notifications using this makes sure that events that trigger updates get to finish before the next one starts.
  65. // This reduces the risk of dead-locks, race-conditions, pointer errors...
  66. // 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.
  67. void sendNotifications();
  68. // Remove the zeroes in addMask from ones in the component and all child components.
  69. void applyStateAndMask(ComponentState keepMask);
  70. // Clears directStates from all other components sharing the root, iff unique is true.
  71. // Adds directStates to the component.
  72. // Updates indirect states based on direct states.
  73. void addStateBits(ComponentState directStates, bool unique);
  74. // Removes directStates to the component.
  75. // Updates indirect states based on direct states.
  76. void removeStateBits(ComponentState directStates);
  77. public: // Focus is reset when a new component is focused.
  78. // It was clicked directly last time.
  79. inline bool isFocused() { return (this->currentState & componentState_focusDirect) != 0; }
  80. // One of its recursive children was clicked last time.
  81. inline bool ownsFocus() { return (this->currentState & (componentState_focusDirect | componentState_focusIndirect)) != 0; }
  82. // Remove focus from all of the component's children.
  83. void defocusChildren();
  84. // Give focus to a trail from root to the component.
  85. void makeFocused();
  86. public: // Hover is reset before assigned again using a bit mask.
  87. // The cursor hovered within this component without being occluded.
  88. bool isHovered() { return (this->currentState & componentState_hoverDirect) != 0; }
  89. // The cursor hovered within its region, but one of its recursive children got the direct hover state.
  90. bool ownsHover() { return (this->currentState & (componentState_hoverDirect | componentState_hoverIndirect)) != 0; }
  91. // Make the component directly hovered and its parents indirectly hovered.
  92. void hover();
  93. // Remove the hover effect.
  94. void leave();
  95. public: // Showing overlay
  96. inline bool showingOverlay() { return (this->currentState & componentState_showingOverlayDirect) != 0; }
  97. inline bool ownsOverlay() { return (this->currentState & componentState_showingOverlay) != 0; }
  98. void showOverlay();
  99. void hideOverlay();
  100. public:
  101. // Saved properties
  102. FlexRegion region;
  103. PersistentString name;
  104. PersistentInteger index;
  105. PersistentBoolean visible = PersistentBoolean(true);
  106. void declareAttributes(StructureDefinition &target) const override;
  107. public:
  108. Persistent* findAttribute(const ReadableString &name) override;
  109. public:
  110. // Generated automatically from region in applyLayout
  111. IRect location;
  112. void setLocation(const IRect &newLocation);
  113. // Applied reqursively while selecting the correct theme
  114. VisualTheme theme = theme_getDefault();
  115. public:
  116. void applyTheme(VisualTheme theme);
  117. VisualTheme getTheme() const;
  118. public:
  119. // Constructor
  120. VisualComponent();
  121. // Destructor
  122. virtual ~VisualComponent();
  123. public:
  124. virtual bool isContainer() const;
  125. IRect getLocation();
  126. void setRegion(const FlexRegion &newRegion);
  127. FlexRegion getRegion() const;
  128. void setVisible(bool visible);
  129. bool getVisible() const;
  130. void setName(const String& newName);
  131. String getName() const;
  132. void setIndex(int index);
  133. int getIndex() const;
  134. public: // Internal events that component classes override to know when something has changed.
  135. // Called after the component has been created, moved or resized.
  136. virtual void updateLocationEvent(const IRect& oldLocation, const IRect& newLocation);
  137. // Called after a component's state changed, when it is relatively safe to do so.
  138. // All state changes will be sent at the same time, because state changes are often used to trigger other changes.
  139. // Changes to the state made within the notification will not trigger new notifications, because the old state is saved after the call is finished.
  140. virtual void updateStateEvent(ComponentState oldState, ComponentState newState);
  141. public: // Callbacks that the application use by assigning lambdas to specific components in the interface.
  142. DECLARE_CALLBACK(pressedEvent, emptyCallback);
  143. DECLARE_CALLBACK(destroyEvent, emptyCallback);
  144. DECLARE_CALLBACK(mouseDownEvent, mouseCallback);
  145. DECLARE_CALLBACK(mouseUpEvent, mouseCallback);
  146. DECLARE_CALLBACK(mouseMoveEvent, mouseCallback);
  147. DECLARE_CALLBACK(mouseScrollEvent, mouseCallback);
  148. DECLARE_CALLBACK(keyDownEvent, keyboardCallback);
  149. DECLARE_CALLBACK(keyUpEvent, keyboardCallback);
  150. DECLARE_CALLBACK(keyTypeEvent, keyboardCallback);
  151. DECLARE_CALLBACK(selectEvent, indexCallback);
  152. public:
  153. // Returning a shader pointer to the topmost direct visible child that contains pixelPosition.
  154. // The pixelPosition is relative to the called component's upper left corner.
  155. Handle<VisualComponent> getDirectChild(const IVector2D& pixelPosition);
  156. // Returning a shared pointer to itself.
  157. // Currently not working for the root component because of limitations in C++.
  158. Handle<VisualComponent> getHandle();
  159. public:
  160. // Draw the component
  161. // The component is responsible for drawing the component at this->location + offset.
  162. // The caller is responsible for drawing the background for any pixels in the component that might not be fully opaque.
  163. // If drawing out of bound, the pixels that are outside should be skipped without any warning nor crash.
  164. // To clip the drawing of a component when calling this, give a sub-image and adjust for the new coordinate system using offset.
  165. // If not implemented, a rectangle will mark the region where the component will be drawn as a reference.
  166. // targetImage is the image being drawn to.
  167. // offset is the upper left corner of the parent container relative to the image.
  168. // Clipping will affect the offset by being relative to the new sub-image.
  169. void draw(ImageRgbaU8& targetImage, const IVector2D& offset);
  170. // Draw the component itself to targetImage at relativeLocation.
  171. // The method is responsible for clipping without a warning when bound is outside of targetImage.
  172. virtual void drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation);
  173. // Draw the component's overlays on top of other components in the window.
  174. // Overlays are drawn using absolute positions on the canvas.
  175. // The absoluteOffset is the location of the component's upper left corner relative to the whole window's canvas.
  176. // Use for anything that needs to be drawn on top of other components without being clipped by any parent components.
  177. virtual void drawOverlay(ImageRgbaU8& targetImage, const IVector2D &absoluteOffset);
  178. // Draw the component while skipping pixels outside of clipRegion
  179. // Multiple calls with non-overlapping clip regions should be equivalent to one call with the union of all clip regions.
  180. // This means that the draw methods should handle border clipping so that no extra borderlines or rounded edges appear from nowhere.
  181. // Example:
  182. // drawClipped(i, o, IRect(0, 0, 20, 20)) // Full region
  183. // <=>
  184. // drawClipped(i, o, IRect(0, 0, 10, 20)) // Left half
  185. // drawClipped(i, o, IRect(10, 0, 10, 20)) // Right half
  186. // 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.
  187. // draw(i, o) <=> drawClipped(i, o, IRect(0, 0, i.width(), i.height()))
  188. void drawClipped(ImageRgbaU8 targetImage, const IVector2D& offset, const IRect& clipRegion);
  189. // Add a child component
  190. // Preconditions:
  191. // The parent's component type is a container.
  192. // The child does not already have a parent.
  193. void addChildComponent(Handle<VisualComponent> child);
  194. // Called with any persistent type when constructing child components from text
  195. bool addChild(Handle<Persistent> child) override;
  196. // Called when saving to text
  197. int getChildCount() const override;
  198. Handle<Persistent> getChild(int index) const override;
  199. // Returns true iff child is a member of the component
  200. // Searches recursively
  201. bool hasChild(VisualComponent *child) const;
  202. bool hasChild(Handle<VisualComponent> child) const;
  203. // Find the first child component with the requested name using a case sensitive match.
  204. // Returns: A shared pointer to the child or null if not found.
  205. Handle<VisualComponent> findChildByName(ReadableString name) const;
  206. Handle<VisualComponent> findChildByNameAndIndex(ReadableString name, int index) const;
  207. // Detach the component from any parent
  208. void detachFromParent();
  209. // Adapt the location based on the space given by the parent.
  210. // The given space is usually a rectangle starting at the origin with the same dimensions as the parent component.
  211. // 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.
  212. // For example: A given space from 10 to 90 pixels will have 0% at 10 and 100% at 90.
  213. // 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.
  214. // TODO: How can internal changes to inner dimensions be detected by the parent to run this method again?
  215. virtual void applyLayout(const IRect& givenSpace);
  216. // Update layout when the component moved but the parent has the same dimensions
  217. void updateLayout();
  218. // 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.
  219. // 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.
  220. // 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.
  221. virtual IVector2D getDesiredDimensions();
  222. // Return true to turn off automatic drawing of and interaction with child components.
  223. virtual bool managesChildren();
  224. // Get the component's absolute position relative to the window's client region.
  225. //IVector2D getAbsolutePosition();
  226. // Calling updateLocationEvent without changing the location, to be used when a child component changed its desired dimensions from altering attributes.
  227. bool childChanged = false;
  228. // Called before rendering or getting mouse input in case that a child component changed desired dimensions.
  229. void updateChildLocations();
  230. // Returns true iff the pixel relative to the parent container's upper left corner is inside of the component.
  231. // By default, it returns true when pixelPosition is within the component's location, because most component are solid.
  232. // 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.
  233. virtual bool pointIsInside(const IVector2D& pixelPosition);
  234. // Returns true iff the pixelPosition relative to the parent container's upper left corner is inside of the component's overlay.
  235. // The caller is responsible for checking if the component is showing an overlay (this->showOverlay).
  236. virtual bool pointIsInsideOfOverlay(const IVector2D& pixelPosition);
  237. // Return true iff the pixelPosition relative to the parent container's upper left corner is inside of the region causing a hover effect.
  238. virtual bool pointIsInsideOfHover(const IVector2D& pixelPosition);
  239. // Send a mouse down event to the component
  240. // pixelPosition is relative to the component's own upper left corner. TODO: Does this make sense, or should it use parent coordinates?
  241. // The component is reponsible for bound checking, which can be used to either block the signal or pass to components below.
  242. // If recursive is true, notifications will be supressed to prevent duplicate events when called from within receiveMouseEvent.
  243. void sendMouseEvent(const MouseEvent& event, bool recursive = false);
  244. void sendKeyboardEvent(const KeyboardEvent& event);
  245. // Defines what the component does when it has received an event that didn't hit any sub components on the way.
  246. // pixelPosition is relative to the parent's (this->parent) upper left corner.
  247. // This is not a callback event, but a way for the component to handle events.
  248. virtual void receiveMouseEvent(const MouseEvent& event);
  249. virtual void receiveKeyboardEvent(const KeyboardEvent& event);
  250. // Notifies when the theme has been changed, so that temporary data depending on the theme can be replaced.
  251. virtual void changedTheme(VisualTheme newTheme);
  252. // Override to be notified about individual attribute changes
  253. virtual void changedAttribute(const ReadableString &name);
  254. // Override to be notified about location changes
  255. virtual void changedLocation(const IRect &oldLocation, const IRect &newLocation) {};
  256. // Custom call handler to manipulate components across a generic API
  257. virtual String call(const ReadableString &methodName, const ReadableString &arguments);
  258. };
  259. }
  260. #endif