VisualComponent.cpp 27 KB

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