VisualComponent.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2018 to 2019 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" // TODO: Separate event types from the window module
  27. #include "FlexRegion.h"
  28. #include "InputEvent.h"
  29. #include "VisualTheme.h"
  30. #include "../api/imageAPI.h"
  31. #include "../api/drawAPI.h"
  32. namespace dsr {
  33. // A reusable method for calling the media machine that allow providing additional variables as style flags.
  34. MediaResult component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed = 0, int focused = 0, int hover = 0);
  35. class VisualComponent : public Persistent {
  36. PERSISTENT_DECLARATION(VisualComponent)
  37. public:
  38. // Parent component
  39. VisualComponent *parent = nullptr;
  40. IRect givenSpace; // Remembering the local region that was reserved inside of the parent component.
  41. bool regionAccessed = false; // If someone requested access to the region, remember to update layout in case of new settings.
  42. // Child components
  43. List<std::shared_ptr<VisualComponent>> children;
  44. // Remember the component used for a drag event.
  45. // Ensures that mouse down events are followed by mouse up events on the same component.
  46. int holdCount = 0;
  47. // Remember the pressed component for sending mouse move events outside of its region.
  48. std::shared_ptr<VisualComponent> dragComponent;
  49. // Remember the focused component for keyboard input.
  50. std::shared_ptr<VisualComponent> focusComponent;
  51. // The next overlay component, which may refer to one of its children as the next overlay to draw itself on top of other components.
  52. // The linked list goes from the root component in the window, across each component with an active overlay.
  53. // Examples:
  54. // Root -> ToolTipText
  55. // Root -> TopMenu -> SubMenu -> SubMenu
  56. std::shared_ptr<VisualComponent> overlayComponent;
  57. // Saved properties
  58. FlexRegion region;
  59. PersistentString name;
  60. PersistentInteger index;
  61. PersistentBoolean visible = PersistentBoolean(true);
  62. void declareAttributes(StructureDefinition &target) const override {
  63. target.declareAttribute(U"Name");
  64. target.declareAttribute(U"Index");
  65. target.declareAttribute(U"Visible");
  66. target.declareAttribute(U"Left");
  67. target.declareAttribute(U"Top");
  68. target.declareAttribute(U"Right");
  69. target.declareAttribute(U"Bottom");
  70. }
  71. public:
  72. Persistent* findAttribute(const ReadableString &name) override {
  73. if (string_caseInsensitiveMatch(name, U"Name")) {
  74. return &(this->name);
  75. } else if (string_caseInsensitiveMatch(name, U"Index")) {
  76. return &(this->index);
  77. } else if (string_caseInsensitiveMatch(name, U"Visible")) {
  78. return &(this->visible);
  79. } else if (string_caseInsensitiveMatch(name, U"Left")) {
  80. this->regionAccessed = true;
  81. return &(this->region.left);
  82. } else if (string_caseInsensitiveMatch(name, U"Top")) {
  83. this->regionAccessed = true;
  84. return &(this->region.top);
  85. } else if (string_caseInsensitiveMatch(name, U"Right")) {
  86. this->regionAccessed = true;
  87. return &(this->region.right);
  88. } else if (string_caseInsensitiveMatch(name, U"Bottom")) {
  89. this->regionAccessed = true;
  90. return &(this->region.bottom);
  91. } else {
  92. return nullptr;
  93. }
  94. }
  95. public:
  96. // Generated automatically from region in applyLayout
  97. IRect location;
  98. void setLocation(const IRect &newLocation);
  99. // Applied reqursively while selecting the correct theme
  100. VisualTheme theme = theme_getDefault();
  101. public:
  102. void applyTheme(VisualTheme theme);
  103. VisualTheme getTheme() const;
  104. public:
  105. // Constructor
  106. VisualComponent();
  107. // Destructor
  108. virtual ~VisualComponent();
  109. public:
  110. virtual bool isContainer() const;
  111. IRect getLocation();
  112. void setRegion(const FlexRegion &newRegion);
  113. FlexRegion getRegion() const;
  114. void setVisible(bool visible);
  115. bool getVisible() const;
  116. void setName(const String& newName);
  117. String getName() const;
  118. void setIndex(int index);
  119. int getIndex() const;
  120. public:
  121. // Callbacks
  122. DECLARE_CALLBACK(pressedEvent, emptyCallback);
  123. DECLARE_CALLBACK(destroyEvent, emptyCallback);
  124. DECLARE_CALLBACK(mouseDownEvent, mouseCallback);
  125. DECLARE_CALLBACK(mouseUpEvent, mouseCallback);
  126. DECLARE_CALLBACK(mouseMoveEvent, mouseCallback);
  127. DECLARE_CALLBACK(mouseScrollEvent, mouseCallback);
  128. DECLARE_CALLBACK(keyDownEvent, keyboardCallback);
  129. DECLARE_CALLBACK(keyUpEvent, keyboardCallback);
  130. DECLARE_CALLBACK(keyTypeEvent, keyboardCallback);
  131. DECLARE_CALLBACK(selectEvent, indexCallback);
  132. public:
  133. std::shared_ptr<VisualComponent> getDirectChild(const IVector2D& pixelPosition, bool includeInvisible);
  134. public:
  135. // Draw the component
  136. // The component is responsible for drawing the component at this->location + offset.
  137. // The caller is responsible for drawing the background for any pixels in the component that might not be fully opaque.
  138. // If drawing out of bound, the pixels that are outside should be skipped without any warning nor crash.
  139. // To clip the drawing of a component when calling this, give a sub-image and adjust for the new coordinate system using offset.
  140. // If not implemented, a rectangle will mark the region where the component will be drawn as a reference.
  141. // targetImage is the image being drawn to.
  142. // offset is the upper left corner of the parent container relative to the image.
  143. // Clipping will affect the offset by being relative to the new sub-image.
  144. void draw(ImageRgbaU8& targetImage, const IVector2D& offset);
  145. // Draw the component itself to targetImage at relativeLocation.
  146. // The method is responsible for clipping without a warning when bound is outside of targetImage.
  147. virtual void drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation);
  148. // Draw the component's overlays on top of other components in the window.
  149. // Overlays are drawn using absolute positions in the window, without caring about any parent location.
  150. virtual void drawOverlay(ImageRgbaU8& targetImage);
  151. // Draw the component while skipping pixels outside of clipRegion
  152. // Multiple calls with non-overlapping clip regions should be equivalent to one call with the union of all clip regions.
  153. // This means that the draw methods should handle border clipping so that no extra borderlines or rounded edges appear from nowhere.
  154. // Example:
  155. // drawClipped(i, o, IRect(0, 0, 20, 20)) // Full region
  156. // <=>
  157. // drawClipped(i, o, IRect(0, 0, 10, 20)) // Left half
  158. // drawClipped(i, o, IRect(10, 0, 10, 20)) // Right half
  159. // 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.
  160. // draw(i, o) <=> drawClipped(i, o, IRect(0, 0, i.width(), i.height()))
  161. void drawClipped(ImageRgbaU8 targetImage, const IVector2D& offset, const IRect& clipRegion);
  162. // TODO: Distinguish from the generic version
  163. // Add a child component
  164. // Preconditions:
  165. // The parent's component type is a container.
  166. // The child does not already have a parent.
  167. void addChildComponent(std::shared_ptr<VisualComponent> child);
  168. // Called with any persistent type when constructing child components from text
  169. bool addChild(std::shared_ptr<Persistent> child) override;
  170. // Called when saving to text
  171. int getChildCount() const override;
  172. std::shared_ptr<Persistent> getChild(int index) const override;
  173. // TODO: Reuse in Persistent
  174. // Returns true iff child is a member of the component
  175. // Searches recursively
  176. bool hasChild(VisualComponent *child) const;
  177. bool hasChild(std::shared_ptr<VisualComponent> child) const;
  178. // Find the first child component with the requested name using a case sensitive match.
  179. // Returns: A shared pointer to the child or null if not found.
  180. std::shared_ptr<VisualComponent> findChildByName(ReadableString name) const;
  181. std::shared_ptr<VisualComponent> findChildByNameAndIndex(ReadableString name, int index) const;
  182. // Detach the component from any parent
  183. void detachFromParent();
  184. // Adapt the location based on the space given by the parent.
  185. // The given space is usually a rectangle starting at the origin with the same dimensions as the parent component.
  186. // 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.
  187. // For example: A given space from 10 to 90 pixels will have 0% at 10 and 100% at 90.
  188. // 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.
  189. // TODO: How can internal changes to inner dimensions be detected by the parent to run this method again?
  190. virtual void applyLayout(const IRect& givenSpace);
  191. // Update layout when the component moved but the parent has the same dimensions
  192. void updateLayout();
  193. // 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.
  194. // 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.
  195. virtual IVector2D getDesiredDimensions();
  196. // Called after the component has been created, moved or resized.
  197. virtual void updateLocationEvent(const IRect& oldLocation, const IRect& newLocation);
  198. // Calling updateLocationEvent without changing the location, to be used when a child component changed its desired dimensions from altering attributes.
  199. bool childChanged = false;
  200. // Called before rendering or getting mouse input in case that a child component changed desired dimensions.
  201. void updateChildLocations();
  202. // Returns true iff the pixel with its upper left corner at pixelPosition is inside the component.
  203. // A rectangular bound check with location is used by default.
  204. // The caller is responsible for checking if the component is visible when needed.
  205. virtual bool pointIsInside(const IVector2D& pixelPosition);
  206. // Get a pointer to the topmost child
  207. // Invisible components are ignored by default, but includeInvisible can be enabled to change that.
  208. // Returns an empty reference if the pixel position didn't hit anything in
  209. // Since the root component might not be heap allocated, it cannot return itself by reference.
  210. // Use pointIsInside if your root component doesn't cover the whole window.
  211. std::shared_ptr<VisualComponent> getTopChild(const IVector2D& pixelPosition, bool includeInvisible = false);
  212. // Send a mouse down event to the component
  213. // pixelPosition is relative to the parent container.
  214. // The component is reponsible for bound checking, which can be used to either block the signal or pass to components below.
  215. void sendMouseEvent(const MouseEvent& event);
  216. void sendKeyboardEvent(const KeyboardEvent& event);
  217. // Defines what the component does when it has received an event that didn't hit any sub components on the way.
  218. // pixelPosition is relative to the parent container.
  219. // This is not a callback event.
  220. virtual void receiveMouseEvent(const MouseEvent& event);
  221. virtual void receiveKeyboardEvent(const KeyboardEvent& event);
  222. // Notifies when the theme has been changed, so that temporary data depending on the theme can be replaced
  223. virtual void changedTheme(VisualTheme newTheme);
  224. // Override to be notified about individual attribute changes
  225. virtual void changedAttribute(const ReadableString &name) {};
  226. // Override to be notified about location changes
  227. virtual void changedLocation(const IRect &oldLocation, const IRect &newLocation) {};
  228. // Custom call handler to manipulate components across a generic API
  229. virtual String call(const ReadableString &methodName, const ReadableString &arguments);
  230. // Returns true iff the component is focused.
  231. // Used for textboxes to know if they should be drawn as active.
  232. // The root component is considered focused if none of its children are focused.
  233. bool isFocused();
  234. // Returns true iff itself, a direct child or an indirect child has focus.
  235. // Used for menus to keep the whole path of sub-menus alive all the way down to the focused component.
  236. bool containsFocused();
  237. };
  238. }
  239. #endif