VisualComponent.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  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. #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. this->callback_destroyEvent();
  31. // Let the children know that the parent component no longer exists.
  32. for (int i = 0; i < this->getChildCount(); i++) {
  33. this->children[i]->parent = nullptr;
  34. }
  35. }
  36. void VisualComponent::declareAttributes(StructureDefinition &target) const {
  37. target.declareAttribute(U"Name");
  38. target.declareAttribute(U"Index");
  39. target.declareAttribute(U"Visible");
  40. target.declareAttribute(U"Left");
  41. target.declareAttribute(U"Top");
  42. target.declareAttribute(U"Right");
  43. target.declareAttribute(U"Bottom");
  44. }
  45. Persistent* VisualComponent::findAttribute(const ReadableString &name) {
  46. if (string_caseInsensitiveMatch(name, U"Name")) {
  47. return &(this->name);
  48. } else if (string_caseInsensitiveMatch(name, U"Index")) {
  49. return &(this->index);
  50. } else if (string_caseInsensitiveMatch(name, U"Visible")) {
  51. return &(this->visible);
  52. } else if (string_caseInsensitiveMatch(name, U"Left")) {
  53. this->regionAccessed = true;
  54. return &(this->region.left);
  55. } else if (string_caseInsensitiveMatch(name, U"Top")) {
  56. this->regionAccessed = true;
  57. return &(this->region.top);
  58. } else if (string_caseInsensitiveMatch(name, U"Right")) {
  59. this->regionAccessed = true;
  60. return &(this->region.right);
  61. } else if (string_caseInsensitiveMatch(name, U"Bottom")) {
  62. this->regionAccessed = true;
  63. return &(this->region.bottom);
  64. } else {
  65. return nullptr;
  66. }
  67. }
  68. // Pre-condition: component != nullptr
  69. // Post-condition: Returns the root of component
  70. static VisualComponent *getRoot(VisualComponent *component) {
  71. assert(component != nullptr);
  72. while (component->parent != nullptr) {
  73. component = component->parent;
  74. }
  75. return component;
  76. }
  77. IVector2D VisualComponent::getDesiredDimensions() {
  78. // Unless this virtual method is overridden, toolbars and such will try to give these dimensions to the component.
  79. return IVector2D(32, 32);
  80. }
  81. bool VisualComponent::isContainer() const {
  82. return true;
  83. }
  84. IRect VisualComponent::getLocation() {
  85. // If someone requested access to Left, Top, Right or Bottom, regionAccessed will be true
  86. if (this->regionAccessed) {
  87. // Now that a fixed location is requested, we need to recalculate the location from the flexible region based on parent dimensions
  88. this->updateLayout();
  89. this->regionAccessed = false;
  90. }
  91. return this->location;
  92. }
  93. void VisualComponent::changedAttribute(const ReadableString &name) {
  94. if (this->parent) {
  95. this->parent->childChanged = true;
  96. }
  97. }
  98. void VisualComponent::setRegion(const FlexRegion &newRegion) {
  99. this->region = newRegion;
  100. if (this->parent) {
  101. this->parent->childChanged = true;
  102. }
  103. }
  104. FlexRegion VisualComponent::getRegion() const {
  105. return this->region;
  106. }
  107. void VisualComponent::setVisible(bool visible) {
  108. this->visible.value = visible;
  109. if (this->parent) {
  110. this->parent->childChanged = true;
  111. }
  112. }
  113. bool VisualComponent::getVisible() const {
  114. return this->visible.value;
  115. }
  116. void VisualComponent::setName(const String& newName) {
  117. this->name.value = newName;
  118. if (this->parent) {
  119. this->parent->childChanged = true;
  120. }
  121. }
  122. String VisualComponent::getName() const {
  123. return this->name.value;
  124. }
  125. void VisualComponent::setIndex(int newIndex) {
  126. this->index.value = newIndex;
  127. if (this->parent) {
  128. this->parent->childChanged = true;
  129. }
  130. }
  131. int VisualComponent::getIndex() const {
  132. return this->index.value;
  133. }
  134. void VisualComponent::setLocation(const IRect &newLocation) {
  135. IRect oldLocation = this->location;
  136. this->location = newLocation;
  137. if (oldLocation != newLocation) {
  138. this->updateLocationEvent(oldLocation, newLocation);
  139. }
  140. this->changedLocation(oldLocation, newLocation);
  141. }
  142. void VisualComponent::updateLayout() {
  143. this->setLocation(this->region.getNewLocation(this->givenSpace));
  144. }
  145. void VisualComponent::applyLayout(const IRect& givenSpace) {
  146. this->givenSpace = givenSpace;
  147. this->updateLayout();
  148. }
  149. void VisualComponent::updateLocationEvent(const IRect& oldLocation, const IRect& newLocation) {
  150. // Place each child component
  151. for (int i = 0; i < this->getChildCount(); i++) {
  152. this->children[i]->applyLayout(IRect(0, 0, newLocation.width(), newLocation.height()));
  153. }
  154. }
  155. // Check if any change requires the child layout to update.
  156. // Used to realign members of toolbars after a desired dimension changed.
  157. void VisualComponent::updateChildLocations() {
  158. if (this->childChanged) {
  159. this->updateLocationEvent(this->location, this->location);
  160. this->childChanged = false;
  161. }
  162. }
  163. // Overlays are only cropped by the entire canvas, so the offset is the upper left corner of component relative to the upper left corner of the canvas.
  164. static void drawOverlays(ImageRgbaU8& targetImage, VisualComponent &component, const IVector2D& offset) {
  165. // Invisible components are not allowed to display overlays, because the component system is
  166. // responsible for visibility settings that specific components are likely to forget about.
  167. if (component.getVisible() && component.ownsOverlay()) {
  168. // Check if the component has the overlay shown.
  169. if (component.showingOverlay()) {
  170. // Draw the component's own overlay below child overlays.
  171. component.drawOverlay(targetImage, offset - component.location.upperLeft());
  172. }
  173. // Draw overlays in each child component on top.
  174. for (int i = 0; i < component.getChildCount(); i++) {
  175. drawOverlays(targetImage, *(component.children[i]), offset + component.children[i]->location.upperLeft());
  176. }
  177. }
  178. }
  179. // Offset may become non-zero when the origin is outside of targetImage from being clipped outside of the parent region
  180. void VisualComponent::draw(ImageRgbaU8& targetImage, const IVector2D& offset) {
  181. // TODO: Any more good places to send notifications to make the GUI respond faster?
  182. // When about to start drawing from the root, check for state changes and handle events before drawing,
  183. // so that anything needed for visuals is handled without further delay.
  184. if (this->parent == nullptr) {
  185. this->sendNotifications();
  186. }
  187. if (this->getVisible()) {
  188. this->updateChildLocations();
  189. IRect containerBound = this->getLocation() + offset;
  190. this->drawSelf(targetImage, containerBound);
  191. // Draw each child component
  192. if (!this->managesChildren()) {
  193. for (int i = 0; i < this->getChildCount(); i++) {
  194. this->children[i]->drawClipped(targetImage, containerBound.upperLeft(), containerBound);
  195. }
  196. }
  197. // When drawing the root, start recursive drawing of all overlays.
  198. if (this->parent == nullptr) {
  199. drawOverlays(targetImage, *this, this->location.upperLeft());
  200. }
  201. }
  202. }
  203. void VisualComponent::drawClipped(ImageRgbaU8 targetImage, const IVector2D& offset, const IRect& clipRegion) {
  204. IRect finalRegion = IRect::cut(clipRegion, IRect(0, 0, image_getWidth(targetImage), image_getHeight(targetImage)));
  205. if (finalRegion.hasArea()) {
  206. // TODO: Optimize allocation of sub-images
  207. ImageRgbaU8 target = image_getSubImage(targetImage, finalRegion);
  208. this->draw(target, offset - finalRegion.upperLeft());
  209. }
  210. }
  211. // A red rectangle is drawn as a placeholder if the class couldn't be found
  212. // TODO: Should the type name be remembered in the base class for serializing missing components?
  213. void VisualComponent::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) {
  214. draw_rectangle(targetImage, relativeLocation, ColorRgbaI32(200, 50, 50, 255));
  215. }
  216. void VisualComponent::drawOverlay(ImageRgbaU8& targetImage, const IVector2D &absoluteOffset) {}
  217. // Manual use with the correct type
  218. void VisualComponent::addChildComponent(std::shared_ptr<VisualComponent> child) {
  219. if (!this->isContainer()) {
  220. sendWarning(U"Cannot attach a child to a non-container parent component!\n");
  221. } else if (child.get() == this) {
  222. sendWarning(U"Cannot attach a component to itself!\n");
  223. } else if (child->hasChild(this)) {
  224. sendWarning(U"Cannot attach to its own parent as a child component!\n");
  225. } else {
  226. // Remove from any previous parent
  227. child->detachFromParent();
  228. // Update layout based on the new parent size
  229. child->applyLayout(IRect(0, 0, this->location.width(), this->location.height()));
  230. // Connect to the new parent
  231. this->children.push(child);
  232. this->childChanged = true;
  233. child->parent = this;
  234. }
  235. }
  236. // Automatic insertion from loading
  237. bool VisualComponent::addChild(std::shared_ptr<Persistent> child) {
  238. // Try to cast from base class Persistent to derived class VisualComponent
  239. std::shared_ptr<VisualComponent> visualComponent = std::dynamic_pointer_cast<VisualComponent>(child);
  240. if (visualComponent.get() == nullptr) {
  241. return false; // Wrong type!
  242. } else {
  243. this->addChildComponent(visualComponent);
  244. return true; // Success!
  245. }
  246. }
  247. int VisualComponent::getChildCount() const {
  248. return this->children.length();
  249. }
  250. std::shared_ptr<Persistent> VisualComponent::getChild(int index) const {
  251. if (index >= 0 && index < this->children.length()) {
  252. return this->children[index];
  253. } else {
  254. return std::shared_ptr<Persistent>(); // Null handle for out of bound.
  255. }
  256. }
  257. void VisualComponent::detachFromParent() {
  258. // Check if there's a parent component
  259. VisualComponent *parent = this->parent;
  260. if (parent != nullptr) {
  261. parent->childChanged = true;
  262. // Find the component to detach among the child components.
  263. for (int i = 0; i < parent->getChildCount(); i++) {
  264. std::shared_ptr<VisualComponent> current = parent->children[i];
  265. if (current.get() == this) {
  266. // Disconnect parent from child.
  267. current->parent = nullptr;
  268. // Disconnect child from parent.
  269. parent->children.remove(i);
  270. return;
  271. }
  272. }
  273. // Update indirect states.
  274. getRoot(this)->updateIndirectStates();
  275. // Any ongoing drag action will allow the component to get the mouse up event to finish transactions safely before being deleted by reference counting.
  276. // Otherwise it may break program logic or cause crashes.
  277. }
  278. }
  279. bool VisualComponent::hasChild(VisualComponent *child) const {
  280. for (int i = 0; i < this->getChildCount(); i++) {
  281. std::shared_ptr<VisualComponent> current = this->children[i];
  282. if (current.get() == child) {
  283. return true; // Found the component
  284. } else {
  285. if (current->hasChild(child)) {
  286. return true; // Found the component recursively
  287. }
  288. }
  289. }
  290. return false; // Could not find the component
  291. }
  292. bool VisualComponent::hasChild(std::shared_ptr<VisualComponent> child) const {
  293. return this->hasChild(child.get());
  294. }
  295. std::shared_ptr<VisualComponent> VisualComponent::findChildByName(ReadableString name) const {
  296. for (int i = 0; i < this->getChildCount(); i++) {
  297. std::shared_ptr<VisualComponent> current = this->children[i];
  298. if (string_match(current->getName(), name)) {
  299. return current; // Found the component
  300. } else {
  301. std::shared_ptr<VisualComponent> searchResult = current->findChildByName(name);
  302. if (searchResult.get() != nullptr) {
  303. return searchResult; // Found the component recursively
  304. }
  305. }
  306. }
  307. return std::shared_ptr<VisualComponent>(); // Could not find the component
  308. }
  309. std::shared_ptr<VisualComponent> VisualComponent::findChildByNameAndIndex(ReadableString name, int index) const {
  310. for (int i = 0; i < this->getChildCount(); i++) {
  311. std::shared_ptr<VisualComponent> current = this->children[i];
  312. if (string_match(current->getName(), name) && current->getIndex() == index) {
  313. return current; // Found the component
  314. } else {
  315. std::shared_ptr<VisualComponent> searchResult = current->findChildByNameAndIndex(name, index);
  316. if (searchResult.get() != nullptr) {
  317. return searchResult; // Found the component recursively
  318. }
  319. }
  320. }
  321. return std::shared_ptr<VisualComponent>(); // Could not find the component
  322. }
  323. bool VisualComponent::pointIsInside(const IVector2D& pixelPosition) {
  324. return pixelPosition.x > this->location.left() && pixelPosition.x < this->location.right()
  325. && pixelPosition.y > this->location.top() && pixelPosition.y < this->location.bottom();
  326. }
  327. bool VisualComponent::pointIsInsideOfOverlay(const IVector2D& pixelPosition) {
  328. return false;
  329. }
  330. // Non-recursive top-down search
  331. std::shared_ptr<VisualComponent> VisualComponent::getDirectChild(const IVector2D& pixelPosition) {
  332. // Iterate child components in reverse drawing order
  333. for (int i = this->getChildCount() - 1; i >= 0; i--) {
  334. std::shared_ptr<VisualComponent> currentChild = this->children[i];
  335. // Check if the point is inside the child component
  336. if (currentChild->getVisible() && currentChild->pointIsInside(pixelPosition)) {
  337. return currentChild;
  338. }
  339. }
  340. // Return nothing if the point missed all child components
  341. return std::shared_ptr<VisualComponent>();
  342. }
  343. // TODO: Store a pointer to the window in each visual component, so that one can get the shared pointer to the root and get access to clipboard functionality.
  344. std::shared_ptr<VisualComponent> VisualComponent::getShared() {
  345. VisualComponent *parent = this->parent;
  346. if (parent == nullptr) {
  347. // Not working for the root component, because that would require access to the window.
  348. return std::shared_ptr<VisualComponent>();
  349. } else {
  350. for (int c = 0; c < parent->children.length(); c++) {
  351. if (parent->children[c].get() == this) {
  352. return parent->children[c];
  353. }
  354. }
  355. // Not found in its own parent if the component tree is broken.
  356. return std::shared_ptr<VisualComponent>();
  357. }
  358. }
  359. void VisualComponent::updateStateEvent(ComponentState oldState, ComponentState newState) {}
  360. void VisualComponent::updateIndirectStates() {
  361. // Call recursively for child components while checking what they contain.
  362. ComponentState childStates = 0;
  363. for (int i = this->getChildCount() - 1; i >= 0; i--) {
  364. this->children[i]->updateIndirectStates();
  365. childStates |= this->children[i]->currentState;
  366. }
  367. // Direct and indirect inheritance.
  368. ComponentState expectedIndirectStates = ((childStates & componentState_direct) << 1) | (childStates & componentState_indirect);
  369. this->currentState = (this->currentState & componentState_direct) | expectedIndirectStates;
  370. }
  371. void VisualComponent::sendNotifications() {
  372. // Call recursively for child components while checking what they contain.
  373. // Run the loop backwards, so that no components are missed when once is detached.
  374. for (int i = this->getChildCount() - 1; i >= 0; i--) {
  375. // Use a reference counted pointer to the child, so that it can be removed safely outside of custom events.
  376. std::shared_ptr<VisualComponent> child = this->children[i];
  377. if (child->detach) {
  378. child->detach = false;
  379. child->detachFromParent();
  380. } else {
  381. child->sendNotifications();
  382. }
  383. }
  384. // Detect differences for all flags at once using bits in the integers.
  385. if (this->currentState != this->previousState) {
  386. updateStateEvent(this->previousState, this->currentState);
  387. this->previousState = this->currentState;
  388. }
  389. }
  390. // Find the topmost overlay by searching backwards with the parent last and returning a pointer to the component.
  391. // The point is relative to the upper left corner of component.
  392. static VisualComponent *getTopmostOverlay(VisualComponent *component, const IVector2D &point) {
  393. // Only visible component may show its overlay or child components.
  394. if (component->getVisible()) {
  395. // Go through child components in reverse draw order to stop when reaching the one that is visible.
  396. for (int i = component->getChildCount() - 1; i >= 0; i--) {
  397. VisualComponent *result = getTopmostOverlay(component->children[i].get(), point - component->children[i]->location.upperLeft());
  398. if (result != nullptr) return result;
  399. }
  400. // Check itself behind child overlays.
  401. if (component->showingOverlay() && component->pointIsInsideOfOverlay(point + component->location.upperLeft())) {
  402. return component;
  403. } else {
  404. return nullptr;
  405. }
  406. } else {
  407. return nullptr;
  408. }
  409. }
  410. // Get the upper left corner of child relative to the upper left corner of parent.
  411. // If parent is null or not a parent of child, then child's offset is relative to the window's canvas.
  412. static IVector2D getTotalOffset(const VisualComponent *child, const VisualComponent *parent = nullptr) {
  413. IVector2D result;
  414. while ((child != nullptr) && (child != parent)) {
  415. result += child->location.upperLeft();
  416. child = child->parent;
  417. }
  418. return result;
  419. }
  420. // Remove its pointer to its child and the whole trail of focus.
  421. void VisualComponent::defocusChildren() {
  422. for (int i = 0; i < this->getChildCount(); i++) {
  423. this->children[i]->applyStateAndMask(~componentState_focus);
  424. }
  425. }
  426. void VisualComponent::addStateBits(ComponentState directStates, bool unique) {
  427. VisualComponent *root = getRoot(this);
  428. // Remove all focus in the window if unique.
  429. if (unique) root->applyStateAndMask(~directStates);
  430. // Apply focus directly to itself and indirectly to parents.
  431. this->currentState |= directStates;
  432. // Update indirect states, so that parent components know what happens to their child components.
  433. root->updateIndirectStates();
  434. }
  435. void VisualComponent::removeStateBits(ComponentState directStates) {
  436. VisualComponent *root = getRoot(this);
  437. // Apply focus directly to itself and indirectly to parents.
  438. this->currentState &= ~directStates;
  439. // Update indirect states, so that parent components know what happens to their child components.
  440. root->updateIndirectStates();
  441. }
  442. // Create a chain of pointers from the root to this component
  443. // Any focus pointers that are not along the chain will not count but work as a memory for when one of its parents get focus again.
  444. void VisualComponent::makeFocused() {
  445. this->addStateBits(componentState_focus, true);
  446. }
  447. void VisualComponent::hover() {
  448. this->addStateBits(componentState_hoverDirect, true);
  449. }
  450. void VisualComponent::showOverlay() {
  451. this->addStateBits(componentState_showingOverlayDirect, false);
  452. }
  453. // When multiple components are allowed to have the direct flag set, one needs to clean it up like a tree.
  454. void VisualComponent::hideOverlay() {
  455. this->removeStateBits(componentState_showingOverlayDirect);
  456. }
  457. void VisualComponent::applyStateAndMask(ComponentState keepMask) {
  458. this->currentState &= keepMask;
  459. for (int i = 0; i < this->getChildCount(); i++) {
  460. this->children[i]->applyStateAndMask(keepMask);
  461. }
  462. }
  463. // Takes events with points relative to the upper left corner of the called component.
  464. void VisualComponent::sendMouseEvent(const MouseEvent& event, bool recursive) {
  465. if (this->parent == nullptr && !recursive) {
  466. // Use a combined bit mask for any state that needs to be reset at this time.
  467. this->applyStateAndMask(~(componentState_hover));
  468. // Update the layout if needed.
  469. this->updateChildLocations();
  470. }
  471. // Get the point of interaction within the component being sent to,
  472. // so that it can be used to find direct child components expressed
  473. // relative to their container's upper left corner.
  474. // If a button is pressed down, this method will try to grab a component to begin mouse interaction.
  475. // Grabbing with the dragComponent pointer makes sure that move and up events can be given even if the cursor moves outside of the component.
  476. VisualComponent *childComponent = nullptr;
  477. // Find the component to interact with.
  478. if (event.mouseEventType == MouseEventType::MouseDown || this->dragComponent.get() == nullptr) {
  479. // Check the overlays first when getting mouse events to the root component.
  480. if (this->parent == nullptr) {
  481. childComponent = getTopmostOverlay(this, event.position);
  482. }
  483. // Check for direct child components for passing on the event recursively.
  484. // The sendMouseEvent method can be called recursively from a member of an overlay, so we can't know
  485. // which component is at the top without asking the components that manage interaction with their children.
  486. if (childComponent == nullptr && !this->managesChildren()) {
  487. std::shared_ptr<VisualComponent> nextContainer = this->getDirectChild(event.position);
  488. if (nextContainer.get() != nullptr) {
  489. childComponent = nextContainer.get();
  490. }
  491. }
  492. } else if (dragComponent.get() != nullptr) {
  493. // If we're grabbing a component, keep sending events to it.
  494. childComponent = this->dragComponent.get();
  495. }
  496. // Grab any detected component on mouse down events.
  497. if (event.mouseEventType == MouseEventType::MouseDown && childComponent != nullptr) {
  498. childComponent->makeFocused();
  499. this->dragComponent = childComponent->getShared();
  500. this->holdCount++;
  501. }
  502. // Send the signal to a child component or itself.
  503. if (childComponent != nullptr) {
  504. // Recalculate local offset through one or more levels of ownership.
  505. IVector2D offset = getTotalOffset(childComponent, this);
  506. MouseEvent localEvent = event;
  507. localEvent.position = event.position - offset;
  508. childComponent->sendMouseEvent(localEvent);
  509. } else {
  510. // If there is no child component found, interact directly with the parent.
  511. MouseEvent parentEvent = event;
  512. parentEvent.position += this->location.upperLeft();
  513. // Itself is directly hovered.
  514. this->hover();
  515. // If the event receiver pass it on to child components, it can just reset the hover flags again.
  516. this->receiveMouseEvent(parentEvent);
  517. }
  518. // Release a component on mouse up.
  519. if (event.mouseEventType == MouseEventType::MouseUp) {
  520. this->holdCount--;
  521. if (this->holdCount <= 0) {
  522. this->dragComponent = std::shared_ptr<VisualComponent>(); // Abort drag.
  523. // Reset when we had more up than down events, in case that the root panel was created with a button already pressed.
  524. this->holdCount = 0;
  525. }
  526. }
  527. // Once all focusing and defocusing with arbitrary callbacks is over, send the focus notifications to the components that actually changed focus.
  528. if (this->parent == nullptr && !recursive) {
  529. //Should not be needed if everything works. this->updateIndirectStates();
  530. this->sendNotifications();
  531. }
  532. }
  533. void VisualComponent::receiveMouseEvent(const MouseEvent& event) {
  534. if (event.mouseEventType == MouseEventType::MouseDown) {
  535. this->callback_mouseDownEvent(event);
  536. } else if (event.mouseEventType == MouseEventType::MouseUp) {
  537. this->callback_mouseUpEvent(event);
  538. } else if (event.mouseEventType == MouseEventType::MouseMove) {
  539. this->callback_mouseMoveEvent(event);
  540. } else if (event.mouseEventType == MouseEventType::Scroll) {
  541. this->callback_mouseScrollEvent(event);
  542. }
  543. }
  544. void VisualComponent::sendKeyboardEvent(const KeyboardEvent& event) {
  545. for (int i = 0; i < this->getChildCount(); i++) {
  546. ComponentState state = this->children[i]->currentState;
  547. if (state & componentState_focus) {
  548. if (state & componentState_focusDirect) {
  549. this->children[i]->receiveKeyboardEvent(event);
  550. } else if (state & componentState_focusIndirect) {
  551. this->children[i]->sendKeyboardEvent(event);
  552. }
  553. }
  554. }
  555. // Check for any state updates.
  556. if (this->parent == nullptr) {
  557. //Should not be needed if everything works. this->updateIndirectStates();
  558. this->sendNotifications();
  559. }
  560. }
  561. void VisualComponent::receiveKeyboardEvent(const KeyboardEvent& event) {
  562. if (event.keyboardEventType == KeyboardEventType::KeyDown) {
  563. this->callback_keyDownEvent(event);
  564. } else if (event.keyboardEventType == KeyboardEventType::KeyUp) {
  565. this->callback_keyUpEvent(event);
  566. } else if (event.keyboardEventType == KeyboardEventType::KeyType) {
  567. this->callback_keyTypeEvent(event);
  568. }
  569. }
  570. void VisualComponent::applyTheme(VisualTheme theme) {
  571. this->theme = theme;
  572. this->changedTheme(theme);
  573. for (int i = 0; i < this->getChildCount(); i++) {
  574. this->children[i] -> applyTheme(theme);
  575. }
  576. }
  577. VisualTheme VisualComponent::getTheme() const {
  578. return this->theme;
  579. }
  580. void VisualComponent::changedTheme(VisualTheme newTheme) {}
  581. String VisualComponent::call(const ReadableString &methodName, const ReadableString &arguments) {
  582. sendWarning("Unimplemented custom call received");
  583. return U"";
  584. }
  585. bool VisualComponent::managesChildren() {
  586. return false;
  587. }
  588. MediaResult dsr::component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed, int focused, int hover) {
  589. return method.callUsingKeywords([&theme, &method, width, height, red, green, blue, pressed, focused, hover](MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName){
  590. if (string_caseInsensitiveMatch(argumentName, U"width")) {
  591. machine_setInputByIndex(machine, methodIndex, inputIndex, width);
  592. } else if (string_caseInsensitiveMatch(argumentName, U"height")) {
  593. machine_setInputByIndex(machine, methodIndex, inputIndex, height);
  594. } else if (string_caseInsensitiveMatch(argumentName, U"pressed")) {
  595. machine_setInputByIndex(machine, methodIndex, inputIndex, pressed);
  596. } else if (string_caseInsensitiveMatch(argumentName, U"focused")) {
  597. machine_setInputByIndex(machine, methodIndex, inputIndex, focused);
  598. } else if (string_caseInsensitiveMatch(argumentName, U"hover")) {
  599. machine_setInputByIndex(machine, methodIndex, inputIndex, hover);
  600. } else if (string_caseInsensitiveMatch(argumentName, U"red")) {
  601. machine_setInputByIndex(machine, methodIndex, inputIndex, red);
  602. } else if (string_caseInsensitiveMatch(argumentName, U"green")) {
  603. machine_setInputByIndex(machine, methodIndex, inputIndex, green);
  604. } else if (string_caseInsensitiveMatch(argumentName, U"blue")) {
  605. machine_setInputByIndex(machine, methodIndex, inputIndex, blue);
  606. } else if (theme_assignMediaMachineArguments(theme, method.contextIndex, machine, methodIndex, inputIndex, argumentName)) {
  607. // Assigned by theme_assignMediaMachineArguments.
  608. } else {
  609. // TODO: Ask the theme for the argument using a specified style class for variations between different types of buttons, checkboxes, panels, et cetera.
  610. // Send a warning if the theme did not provide an input argument to its own media function.
  611. sendWarning(U"Unhandled setting \"", argumentName, U"\" requested by the media method \"", machine_getMethodName(machine, methodIndex), U"\" in the visual theme!\n");
  612. }
  613. });
  614. }