VisualComponent.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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. #include <stdint.h>
  24. #include "VisualComponent.h"
  25. #include "../image/internal/imageInternal.h"
  26. using namespace dsr;
  27. PERSISTENT_DEFINITION(VisualComponent)
  28. VisualComponent::VisualComponent() {}
  29. VisualComponent::~VisualComponent() {
  30. // Let the children know that the parent component no longer exists.
  31. for (int i = 0; i < this->getChildCount(); i++) {
  32. this->children[i]->parent = nullptr;
  33. }
  34. }
  35. bool VisualComponent::isContainer() const {
  36. return true;
  37. }
  38. IRect VisualComponent::getLocation() {
  39. // If someone requested access to Left, Top, Right or Bottom, regionAccessed will be true
  40. if (this->regionAccessed) {
  41. // Now that a fixed location is requested, we need to recalculate the location from the flexible region based on parent dimensions
  42. this->updateLayout();
  43. this->regionAccessed = false;
  44. }
  45. return this->location;
  46. }
  47. IVector2D VisualComponent::getSize() {
  48. return this->getLocation().size();
  49. }
  50. void VisualComponent::setRegion(const FlexRegion &newRegion) {
  51. this->region = newRegion;
  52. }
  53. FlexRegion VisualComponent::getRegion() const {
  54. return this->region;
  55. }
  56. void VisualComponent::setVisible(bool visible) {
  57. this->visible.value = visible;
  58. }
  59. bool VisualComponent::getVisible() const {
  60. return this->visible.value;
  61. }
  62. void VisualComponent::setName(const String& newName) {
  63. this->name.value = newName;
  64. }
  65. String VisualComponent::getName() const {
  66. return this->name.value;
  67. }
  68. void VisualComponent::setIndex(int newIndex) {
  69. this->index.value = newIndex;
  70. }
  71. int VisualComponent::getIndex() const {
  72. return this->index.value;
  73. }
  74. void VisualComponent::setLocation(const IRect &newLocation) {
  75. IRect oldLocation = this->location;
  76. this->location = newLocation;
  77. if (oldLocation != newLocation) {
  78. this->updateLocationEvent(oldLocation, newLocation);
  79. }
  80. this->changedLocation(oldLocation, newLocation);
  81. }
  82. void VisualComponent::updateLayout() {
  83. this->setLocation(this->region.getNewLocation(this->parentSize));
  84. }
  85. void VisualComponent::applyLayout(IVector2D parentSize) {
  86. this->parentSize = parentSize;
  87. this->updateLayout();
  88. }
  89. void VisualComponent::updateLocationEvent(const IRect& oldLocation, const IRect& newLocation) {
  90. // Place each child component
  91. for (int i = 0; i < this->getChildCount(); i++) {
  92. this->children[i]->applyLayout(newLocation.size());
  93. }
  94. }
  95. // Offset may become non-zero when the origin is outside of targetImage from being clipped outside of the parent region
  96. void VisualComponent::draw(ImageRgbaU8& targetImage, const IVector2D& offset) {
  97. if (this->getVisible()) {
  98. IRect containerBound = this->getLocation() + offset;
  99. this->drawSelf(targetImage, containerBound);
  100. // Draw each child component
  101. for (int i = 0; i < this->getChildCount(); i++) {
  102. this->children[i]->drawClipped(targetImage, containerBound.upperLeft(), containerBound);
  103. }
  104. }
  105. }
  106. void VisualComponent::drawClipped(ImageRgbaU8 targetImage, const IVector2D& offset, const IRect& clipRegion) {
  107. IRect finalRegion = IRect::cut(clipRegion, IRect(0, 0, image_getWidth(targetImage), image_getHeight(targetImage)));
  108. if (finalRegion.hasArea()) {
  109. // TODO: Optimize allocation of sub-images
  110. ImageRgbaU8 target = image_getSubImage(targetImage, finalRegion);
  111. this->draw(target, offset - finalRegion.upperLeft());
  112. }
  113. }
  114. // A red rectangle is drawn as a placeholder if the class couldn't be found
  115. // TODO: Should the type name be remembered in the base class for serializing missing components?
  116. void VisualComponent::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) {
  117. draw_rectangle(targetImage, relativeLocation, ColorRgbaI32(200, 50, 50, 255));
  118. }
  119. // Manual use with the correct type
  120. void VisualComponent::addChildComponent(std::shared_ptr<VisualComponent> child) {
  121. if (!this->isContainer()) {
  122. throwError(U"Cannot attach a child to a non-container parent component!\n");
  123. } else if (child.get() == this) {
  124. throwError(U"Cannot attach a component to itself!\n");
  125. } else if (child->hasChild(this)) {
  126. throwError(U"Cannot attach to its own parent as a child component!\n");
  127. } else {
  128. // Remove from any previous parent
  129. child->detachFromParent();
  130. // Update layout based on the new parent size
  131. child->applyLayout(this->getSize());
  132. // Connect to the new parent
  133. this->children.push(child);
  134. child->parent = this;
  135. }
  136. }
  137. // Automatic insertion from loading
  138. bool VisualComponent::addChild(std::shared_ptr<Persistent> child) {
  139. // Try to cast from base class Persistent to derived class VisualComponent
  140. std::shared_ptr<VisualComponent> visualComponent = std::dynamic_pointer_cast<VisualComponent>(child);
  141. if (visualComponent.get() == nullptr) {
  142. return false; // Wrong type!
  143. } else {
  144. this->addChildComponent(visualComponent);
  145. return true; // Success!
  146. }
  147. }
  148. int VisualComponent::getChildCount() const {
  149. return this->children.length();
  150. }
  151. std::shared_ptr<Persistent> VisualComponent::getChild(int index) const {
  152. return this->children[index];
  153. }
  154. void VisualComponent::detachFromParent() {
  155. // Check if there's a parent component
  156. VisualComponent *parent = this->parent;
  157. if (parent != nullptr) {
  158. // If the removed component is focused from the parent, then remove focus
  159. if (parent->focusComponent.get() == this) {
  160. parent->focusComponent = std::shared_ptr<VisualComponent>();
  161. }
  162. // Iterate over all children in the parent component
  163. for (int i = 0; i < parent->getChildCount(); i++) {
  164. std::shared_ptr<VisualComponent> current = parent->children[i];
  165. if (current.get() == this) {
  166. current->parent = nullptr; // Assign null
  167. parent->children.remove(i);
  168. return;
  169. }
  170. }
  171. }
  172. }
  173. bool VisualComponent::hasChild(VisualComponent *child) const {
  174. for (int i = 0; i < this->getChildCount(); i++) {
  175. std::shared_ptr<VisualComponent> current = this->children[i];
  176. if (current.get() == child) {
  177. return true; // Found the component
  178. } else {
  179. if (current->hasChild(child)) {
  180. return true; // Found the component recursively
  181. }
  182. }
  183. }
  184. return false; // Could not find the component
  185. }
  186. bool VisualComponent::hasChild(std::shared_ptr<VisualComponent> child) const {
  187. return this->hasChild(child.get());
  188. }
  189. std::shared_ptr<VisualComponent> VisualComponent::findChildByName(ReadableString name, bool mustExist) const {
  190. for (int i = 0; i < this->getChildCount(); i++) {
  191. std::shared_ptr<VisualComponent> current = this->children[i];
  192. if (string_match(current->getName(), name)) {
  193. return current; // Found the component
  194. } else {
  195. std::shared_ptr<VisualComponent> searchResult = current->findChildByName(name, mustExist);
  196. if (searchResult.get() != nullptr) {
  197. return searchResult; // Found the component recursively
  198. }
  199. }
  200. }
  201. return std::shared_ptr<VisualComponent>(); // Could not find the component
  202. }
  203. std::shared_ptr<VisualComponent> VisualComponent::findChildByNameAndIndex(ReadableString name, int index, bool mustExist) const {
  204. for (int i = 0; i < this->getChildCount(); i++) {
  205. std::shared_ptr<VisualComponent> current = this->children[i];
  206. if (string_match(current->getName(), name) && current->getIndex() == index) {
  207. return current; // Found the component
  208. } else {
  209. std::shared_ptr<VisualComponent> searchResult = current->findChildByNameAndIndex(name, index, mustExist);
  210. if (searchResult.get() != nullptr) {
  211. return searchResult; // Found the component recursively
  212. }
  213. }
  214. }
  215. return std::shared_ptr<VisualComponent>(); // Could not find the component
  216. }
  217. bool VisualComponent::pointIsInside(const IVector2D& pixelPosition) {
  218. return pixelPosition.x > this->location.left() && pixelPosition.x < this->location.right()
  219. && pixelPosition.y > this->location.top() && pixelPosition.y < this->location.bottom();
  220. }
  221. // Non-recursive top-down search
  222. std::shared_ptr<VisualComponent> VisualComponent::getDirectChild(const IVector2D& pixelPosition, bool includeInvisible) {
  223. // Iterate child components in reverse drawing order
  224. for (int i = this->getChildCount() - 1; i >= 0; i--) {
  225. std::shared_ptr<VisualComponent> currentChild = this->children[i];
  226. // Check if the point is inside the child component
  227. if ((currentChild->getVisible() || includeInvisible) && currentChild->pointIsInside(pixelPosition)) {
  228. return currentChild;
  229. }
  230. }
  231. // Return nothing if the point missed all child components
  232. return std::shared_ptr<VisualComponent>();
  233. }
  234. // Recursive top-down search
  235. std::shared_ptr<VisualComponent> VisualComponent::getTopChild(const IVector2D& pixelPosition, bool includeInvisible) {
  236. // Iterate child components in reverse drawing order
  237. for (int i = this->getChildCount() - 1; i >= 0; i--) {
  238. std::shared_ptr<VisualComponent> currentChild = this->children[i];
  239. // Check if the point is inside the child component
  240. if ((currentChild->getVisible() || includeInvisible) && currentChild->pointIsInside(pixelPosition)) {
  241. // Check if a component inside the child component is even higher up
  242. std::shared_ptr<VisualComponent> subChild = currentChild->getTopChild(pixelPosition - this->getLocation().upperLeft(), includeInvisible);
  243. if (subChild.get() != nullptr) {
  244. return subChild;
  245. } else {
  246. return currentChild;
  247. }
  248. }
  249. }
  250. // Return nothing if the point missed all child components
  251. return std::shared_ptr<VisualComponent>();
  252. }
  253. void VisualComponent::sendMouseEvent(const MouseEvent& event) {
  254. // Convert to local coordinates recursively
  255. MouseEvent localEvent = event - this->getLocation().upperLeft();
  256. std::shared_ptr<VisualComponent> childComponent;
  257. // Grab a component on mouse down
  258. if (event.mouseEventType == MouseEventType::MouseDown) {
  259. childComponent = this->dragComponent = this->focusComponent = this->getDirectChild(localEvent.position, false);
  260. this->holdCount++;
  261. }
  262. if (this->holdCount > 0) {
  263. // If we're grabbing a component, keep sending events to it
  264. childComponent = this->dragComponent;
  265. } else if (this->getVisible() && this->pointIsInside(event.position)) {
  266. // If we're not grabbing a component, see if we can send the action to another component
  267. childComponent = this->getDirectChild(localEvent.position, false);
  268. }
  269. // Send the signal to a child component or itself
  270. if (childComponent.get() != nullptr) {
  271. childComponent->sendMouseEvent(localEvent);
  272. } else {
  273. this->receiveMouseEvent(event);
  274. }
  275. // Release a component on mouse up
  276. if (event.mouseEventType == MouseEventType::MouseUp) {
  277. this->dragComponent = std::shared_ptr<VisualComponent>(); // Abort drag
  278. this->holdCount--;
  279. if (this->holdCount < 0) {
  280. this->holdCount = 0;
  281. }
  282. }
  283. }
  284. void VisualComponent::receiveMouseEvent(const MouseEvent& event) {
  285. if (event.mouseEventType == MouseEventType::MouseDown) {
  286. this->callback_mouseDownEvent(event);
  287. } else if (event.mouseEventType == MouseEventType::MouseUp) {
  288. this->callback_mouseUpEvent(event);
  289. } else if (event.mouseEventType == MouseEventType::MouseMove) {
  290. this->callback_mouseMoveEvent(event);
  291. } else if (event.mouseEventType == MouseEventType::Scroll) {
  292. this->callback_mouseScrollEvent(event);
  293. }
  294. }
  295. void VisualComponent::sendKeyboardEvent(const KeyboardEvent& event) {
  296. // Send the signal to a focused component or itself
  297. if (this->focusComponent.get() != nullptr) {
  298. this->focusComponent->sendKeyboardEvent(event);
  299. } else {
  300. this->receiveKeyboardEvent(event);
  301. }
  302. }
  303. void VisualComponent::receiveKeyboardEvent(const KeyboardEvent& event) {
  304. if (event.keyboardEventType == KeyboardEventType::KeyDown) {
  305. this->callback_keyDownEvent(event);
  306. } else if (event.keyboardEventType == KeyboardEventType::KeyUp) {
  307. this->callback_keyUpEvent(event);
  308. } else if (event.keyboardEventType == KeyboardEventType::KeyType) {
  309. this->callback_keyTypeEvent(event);
  310. }
  311. }
  312. void VisualComponent::applyTheme(VisualTheme theme) {
  313. this->theme = theme;
  314. this->changedTheme(theme);
  315. for (int i = 0; i < this->getChildCount(); i++) {
  316. this->children[i] -> applyTheme(theme);
  317. }
  318. }
  319. VisualTheme VisualComponent::getTheme() const {
  320. return this->theme;
  321. }
  322. void VisualComponent::changedTheme(VisualTheme newTheme) {}
  323. String VisualComponent::call(const ReadableString &methodName, const ReadableString &arguments) {
  324. throwError("Unimplemented custom call received");
  325. return U"";
  326. }