VisualComponent.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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. IVector2D VisualComponent::getDesiredDimensions() {
  69. // Unless this virtual method is overridden, toolbars and such will try to give these dimensions to the component.
  70. return IVector2D(32, 32);
  71. }
  72. bool VisualComponent::isContainer() const {
  73. return true;
  74. }
  75. IRect VisualComponent::getLocation() {
  76. // If someone requested access to Left, Top, Right or Bottom, regionAccessed will be true
  77. if (this->regionAccessed) {
  78. // Now that a fixed location is requested, we need to recalculate the location from the flexible region based on parent dimensions
  79. this->updateLayout();
  80. this->regionAccessed = false;
  81. }
  82. return this->location;
  83. }
  84. void VisualComponent::setRegion(const FlexRegion &newRegion) {
  85. this->region = newRegion;
  86. }
  87. FlexRegion VisualComponent::getRegion() const {
  88. return this->region;
  89. }
  90. void VisualComponent::setVisible(bool visible) {
  91. this->visible.value = visible;
  92. }
  93. bool VisualComponent::getVisible() const {
  94. return this->visible.value;
  95. }
  96. void VisualComponent::setName(const String& newName) {
  97. this->name.value = newName;
  98. }
  99. String VisualComponent::getName() const {
  100. return this->name.value;
  101. }
  102. void VisualComponent::setIndex(int newIndex) {
  103. this->index.value = newIndex;
  104. }
  105. int VisualComponent::getIndex() const {
  106. return this->index.value;
  107. }
  108. void VisualComponent::setLocation(const IRect &newLocation) {
  109. IRect oldLocation = this->location;
  110. this->location = newLocation;
  111. if (oldLocation != newLocation) {
  112. this->updateLocationEvent(oldLocation, newLocation);
  113. }
  114. this->changedLocation(oldLocation, newLocation);
  115. }
  116. void VisualComponent::updateLayout() {
  117. this->setLocation(this->region.getNewLocation(this->givenSpace));
  118. }
  119. void VisualComponent::applyLayout(const IRect& givenSpace) {
  120. this->givenSpace = givenSpace;
  121. this->updateLayout();
  122. }
  123. void VisualComponent::updateLocationEvent(const IRect& oldLocation, const IRect& newLocation) {
  124. // Place each child component
  125. for (int i = 0; i < this->getChildCount(); i++) {
  126. this->children[i]->applyLayout(IRect(0, 0, newLocation.width(), newLocation.height()));
  127. }
  128. }
  129. // Check if any change requires the child layout to update.
  130. // Used to realign members of toolbars after a desired dimension changed.
  131. void VisualComponent::updateChildLocations() {
  132. if (this->childChanged) {
  133. this->updateLocationEvent(this->location, this->location);
  134. this->childChanged = false;
  135. }
  136. }
  137. // 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.
  138. static void drawOverlays(ImageRgbaU8& targetImage, VisualComponent &component, const IVector2D& offset) {
  139. // Invisible components are not allowed to display overlays, because the component system is
  140. // responsible for visibility settings that specific components are likely to forget about.
  141. if (component.getVisible()) {
  142. // Check if the component has the overlay shown.
  143. if (component.showingOverlay()) {
  144. // Draw the component's own overlay below child overlays.
  145. component.drawOverlay(targetImage, offset - component.location.upperLeft());
  146. }
  147. // Draw overlays in each child component on top.
  148. for (int i = 0; i < component.getChildCount(); i++) {
  149. drawOverlays(targetImage, *(component.children[i]), offset + component.children[i]->location.upperLeft());
  150. }
  151. }
  152. }
  153. // Offset may become non-zero when the origin is outside of targetImage from being clipped outside of the parent region
  154. void VisualComponent::draw(ImageRgbaU8& targetImage, const IVector2D& offset) {
  155. if (this->getVisible()) {
  156. this->updateChildLocations();
  157. IRect containerBound = this->getLocation() + offset;
  158. this->drawSelf(targetImage, containerBound);
  159. // Draw each child component
  160. if (!this->managesChildren()) {
  161. for (int i = 0; i < this->getChildCount(); i++) {
  162. this->children[i]->drawClipped(targetImage, containerBound.upperLeft(), containerBound);
  163. }
  164. }
  165. // When drawing the root, start recursive drawing of all overlays.
  166. if (this->parent == nullptr) {
  167. drawOverlays(targetImage, *this, this->location.upperLeft());
  168. }
  169. }
  170. }
  171. void VisualComponent::drawClipped(ImageRgbaU8 targetImage, const IVector2D& offset, const IRect& clipRegion) {
  172. IRect finalRegion = IRect::cut(clipRegion, IRect(0, 0, image_getWidth(targetImage), image_getHeight(targetImage)));
  173. if (finalRegion.hasArea()) {
  174. // TODO: Optimize allocation of sub-images
  175. ImageRgbaU8 target = image_getSubImage(targetImage, finalRegion);
  176. this->draw(target, offset - finalRegion.upperLeft());
  177. }
  178. }
  179. // A red rectangle is drawn as a placeholder if the class couldn't be found
  180. // TODO: Should the type name be remembered in the base class for serializing missing components?
  181. void VisualComponent::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) {
  182. draw_rectangle(targetImage, relativeLocation, ColorRgbaI32(200, 50, 50, 255));
  183. }
  184. void VisualComponent::drawOverlay(ImageRgbaU8& targetImage, const IVector2D &absoluteOffset) {}
  185. // Manual use with the correct type
  186. void VisualComponent::addChildComponent(std::shared_ptr<VisualComponent> child) {
  187. if (!this->isContainer()) {
  188. throwError(U"Cannot attach a child to a non-container parent component!\n");
  189. } else if (child.get() == this) {
  190. throwError(U"Cannot attach a component to itself!\n");
  191. } else if (child->hasChild(this)) {
  192. throwError(U"Cannot attach to its own parent as a child component!\n");
  193. } else {
  194. // Remove from any previous parent
  195. child->detachFromParent();
  196. // Update layout based on the new parent size
  197. child->applyLayout(IRect(0, 0, this->location.width(), this->location.height()));
  198. // Connect to the new parent
  199. this->children.push(child);
  200. this->childChanged = true;
  201. child->parent = this;
  202. }
  203. }
  204. // Automatic insertion from loading
  205. bool VisualComponent::addChild(std::shared_ptr<Persistent> child) {
  206. // Try to cast from base class Persistent to derived class VisualComponent
  207. std::shared_ptr<VisualComponent> visualComponent = std::dynamic_pointer_cast<VisualComponent>(child);
  208. if (visualComponent.get() == nullptr) {
  209. return false; // Wrong type!
  210. } else {
  211. this->addChildComponent(visualComponent);
  212. return true; // Success!
  213. }
  214. }
  215. int VisualComponent::getChildCount() const {
  216. return this->children.length();
  217. }
  218. std::shared_ptr<Persistent> VisualComponent::getChild(int index) const {
  219. if (index >= 0 && index < this->children.length()) {
  220. return this->children[index];
  221. } else {
  222. return std::shared_ptr<Persistent>(); // Null handle for out of bound.
  223. }
  224. }
  225. void VisualComponent::detachFromParent() {
  226. // Check if there's a parent component
  227. VisualComponent *parent = this->parent;
  228. if (parent != nullptr) {
  229. parent->childChanged = true;
  230. // If the removed component is focused from the parent, then remove focus so that the parent is focused instead.
  231. if (parent->focusComponent.get() == this) {
  232. parent->defocusChildren();
  233. }
  234. // Find the component to detach among the child components.
  235. for (int i = 0; i < parent->getChildCount(); i++) {
  236. std::shared_ptr<VisualComponent> current = parent->children[i];
  237. if (current.get() == this) {
  238. // Disconnect parent from child.
  239. current->parent = nullptr;
  240. // Disconnect child from parent.
  241. parent->children.remove(i);
  242. return;
  243. }
  244. }
  245. // Any ongoing drag action will allow the component to get the mouse up event to finish transactions safely before being deleted by reference counting.
  246. // Otherwise it may break program logic or cause crashes.
  247. }
  248. }
  249. bool VisualComponent::hasChild(VisualComponent *child) const {
  250. for (int i = 0; i < this->getChildCount(); i++) {
  251. std::shared_ptr<VisualComponent> current = this->children[i];
  252. if (current.get() == child) {
  253. return true; // Found the component
  254. } else {
  255. if (current->hasChild(child)) {
  256. return true; // Found the component recursively
  257. }
  258. }
  259. }
  260. return false; // Could not find the component
  261. }
  262. bool VisualComponent::hasChild(std::shared_ptr<VisualComponent> child) const {
  263. return this->hasChild(child.get());
  264. }
  265. std::shared_ptr<VisualComponent> VisualComponent::findChildByName(ReadableString name) const {
  266. for (int i = 0; i < this->getChildCount(); i++) {
  267. std::shared_ptr<VisualComponent> current = this->children[i];
  268. if (string_match(current->getName(), name)) {
  269. return current; // Found the component
  270. } else {
  271. std::shared_ptr<VisualComponent> searchResult = current->findChildByName(name);
  272. if (searchResult.get() != nullptr) {
  273. return searchResult; // Found the component recursively
  274. }
  275. }
  276. }
  277. return std::shared_ptr<VisualComponent>(); // Could not find the component
  278. }
  279. std::shared_ptr<VisualComponent> VisualComponent::findChildByNameAndIndex(ReadableString name, int index) const {
  280. for (int i = 0; i < this->getChildCount(); i++) {
  281. std::shared_ptr<VisualComponent> current = this->children[i];
  282. if (string_match(current->getName(), name) && current->getIndex() == index) {
  283. return current; // Found the component
  284. } else {
  285. std::shared_ptr<VisualComponent> searchResult = current->findChildByNameAndIndex(name, index);
  286. if (searchResult.get() != nullptr) {
  287. return searchResult; // Found the component recursively
  288. }
  289. }
  290. }
  291. return std::shared_ptr<VisualComponent>(); // Could not find the component
  292. }
  293. bool VisualComponent::pointIsInside(const IVector2D& pixelPosition) {
  294. return pixelPosition.x > this->location.left() && pixelPosition.x < this->location.right()
  295. && pixelPosition.y > this->location.top() && pixelPosition.y < this->location.bottom();
  296. }
  297. bool VisualComponent::pointIsInsideOfOverlay(const IVector2D& pixelPosition) {
  298. return false;
  299. }
  300. // Non-recursive top-down search
  301. std::shared_ptr<VisualComponent> VisualComponent::getDirectChild(const IVector2D& pixelPosition) {
  302. // Iterate child components in reverse drawing order
  303. for (int i = this->getChildCount() - 1; i >= 0; i--) {
  304. std::shared_ptr<VisualComponent> currentChild = this->children[i];
  305. // Check if the point is inside the child component
  306. if (currentChild->getVisible() && currentChild->pointIsInside(pixelPosition)) {
  307. return currentChild;
  308. }
  309. }
  310. // Return nothing if the point missed all child components
  311. return std::shared_ptr<VisualComponent>();
  312. }
  313. // 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.
  314. std::shared_ptr<VisualComponent> VisualComponent::getShared() {
  315. VisualComponent *parent = this->parent;
  316. if (parent == nullptr) {
  317. // Not working for the root component, because that would require access to the window.
  318. return std::shared_ptr<VisualComponent>();
  319. } else {
  320. for (int c = 0; c < parent->children.length(); c++) {
  321. if (parent->children[c].get() == this) {
  322. return parent->children[c];
  323. }
  324. }
  325. // Not found in its own parent if the component tree is broken.
  326. return std::shared_ptr<VisualComponent>();
  327. }
  328. }
  329. // Remove its pointer to its child and the whole trail of focus.
  330. void VisualComponent::defocusChildren() {
  331. // Using raw pointers because the this pointer is not reference counted.
  332. // Components should not call arbitrary events that might detach components during processing, until a better way to handle this has been implemented.
  333. VisualComponent* parent = this;
  334. while (true) {
  335. // Get the parent's focused direct child.
  336. VisualComponent* child = parent->focusComponent.get();
  337. if (child == nullptr) {
  338. return; // Reached the end.
  339. } else {
  340. parent->focusComponent = std::shared_ptr<VisualComponent>(); // The parent removes the focus pointer from the child.
  341. child->currentState &= ~componentState_focusTail; // Remember that it is not focused, for quick access.
  342. parent = child; // Prepare for the next iteration.
  343. }
  344. }
  345. }
  346. // Pre-condition: component != nullptr
  347. // Post-condition: Returns the root of component
  348. VisualComponent *getRoot(VisualComponent *component) {
  349. assert(component != nullptr);
  350. while (component->parent != nullptr) {
  351. component = component->parent;
  352. }
  353. return component;
  354. }
  355. // Create a chain of pointers from the root to this component
  356. // 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.
  357. void VisualComponent::makeFocused() {
  358. VisualComponent* current = this;
  359. // Remove any focus tail behind the new focus end.
  360. current->defocusChildren();
  361. while (current != nullptr) {
  362. VisualComponent* parent = current->parent;
  363. if (parent == nullptr) {
  364. return;
  365. } else {
  366. VisualComponent* oldFocus = parent->focusComponent.get();
  367. if (oldFocus == current) {
  368. // When reaching a parent that already points back at the component being focused, there is nothing more to do.
  369. return;
  370. } else {
  371. if (oldFocus != nullptr) {
  372. // When reaching a parent that deviated to the old focus branch, follow it and defocus the old components.
  373. parent->defocusChildren();
  374. }
  375. parent->focusComponent = current->getShared();
  376. this->currentState |= componentState_focusTail;
  377. current = parent;
  378. }
  379. }
  380. }
  381. }
  382. void VisualComponent::stateChanged(ComponentState oldState, ComponentState newState) {}
  383. void VisualComponent::sendNotifications() {
  384. // Detect differences for all flags at once using bits in the integers.
  385. if (this->currentState != this->previousState) {
  386. stateChanged(this->previousState, this->currentState);
  387. this->previousState = this->currentState;
  388. }
  389. for (int i = this->getChildCount() - 1; i >= 0; i--) {
  390. this->children[i]->sendNotifications();
  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].get(), 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. // Takes events with points relative to the upper left corner of the called component.
  424. void VisualComponent::sendMouseEvent(const MouseEvent& event, bool recursive) {
  425. // Update the layout if needed.
  426. this->updateChildLocations();
  427. // Get the point of interaction within the component being sent to,
  428. // so that it can be used to find direct child components expressed
  429. // relative to their container's upper left corner.
  430. // If a button is pressed down, this method will try to grab a component to begin mouse interaction.
  431. // Grabbing with the dragComponent pointer makes sure that move and up events can be given even if the cursor moves outside of the component.
  432. VisualComponent *childComponent = nullptr;
  433. // Find the component to interact with, from pressing down or hovering.
  434. if (event.mouseEventType == MouseEventType::MouseDown || this->dragComponent.get() == nullptr) {
  435. // Check the overlays first when getting mouse events to the root component.
  436. if (this->parent == nullptr) {
  437. childComponent = getTopmostOverlay(this, event.position);
  438. }
  439. // Check for direct child components for passing on the event recursively.
  440. // The sendMouseEvent method can be called recursively from a member of an overlay, so we can't know
  441. // which component is at the top without asking the components that manage interaction with their children.
  442. if (childComponent == nullptr && !this->managesChildren()) {
  443. std::shared_ptr<VisualComponent> nextContainer = this->getDirectChild(event.position);
  444. if (nextContainer.get() != nullptr) {
  445. childComponent = nextContainer.get();
  446. }
  447. }
  448. } else if (dragComponent.get() != nullptr) {
  449. // If we're grabbing a component, keep sending events to it.
  450. childComponent = this->dragComponent.get();
  451. }
  452. // Grab any detected component on mouse down events.
  453. if (event.mouseEventType == MouseEventType::MouseDown && childComponent != nullptr) {
  454. childComponent->makeFocused();
  455. this->dragComponent = childComponent->getShared();
  456. this->holdCount++;
  457. }
  458. // Send the signal to a child component or itself.
  459. if (childComponent != nullptr) {
  460. // Recalculate local offset through one or more levels of ownership.
  461. IVector2D offset = getTotalOffset(childComponent, this);
  462. MouseEvent localEvent = event;
  463. localEvent.position = event.position - offset;
  464. childComponent->sendMouseEvent(localEvent);
  465. } else {
  466. // If there is no child component found, interact directly with the parent.
  467. MouseEvent parentEvent = event;
  468. parentEvent.position += this->location.upperLeft();
  469. this->receiveMouseEvent(parentEvent);
  470. }
  471. // Release a component on mouse up.
  472. if (event.mouseEventType == MouseEventType::MouseUp) {
  473. this->holdCount--;
  474. if (this->holdCount <= 0) {
  475. this->dragComponent = std::shared_ptr<VisualComponent>(); // Abort drag.
  476. // Reset when we had more up than down events, in case that the root panel was created with a button already pressed.
  477. this->holdCount = 0;
  478. }
  479. }
  480. // Once all focusing and defocusing with arbitrary callbacks is over, send the focus notifications to the components that actually changed focus.
  481. if (this->parent == nullptr && !recursive) {
  482. this->sendNotifications();
  483. }
  484. }
  485. void VisualComponent::receiveMouseEvent(const MouseEvent& event) {
  486. if (event.mouseEventType == MouseEventType::MouseDown) {
  487. this->callback_mouseDownEvent(event);
  488. } else if (event.mouseEventType == MouseEventType::MouseUp) {
  489. this->callback_mouseUpEvent(event);
  490. } else if (event.mouseEventType == MouseEventType::MouseMove) {
  491. this->callback_mouseMoveEvent(event);
  492. } else if (event.mouseEventType == MouseEventType::Scroll) {
  493. this->callback_mouseScrollEvent(event);
  494. }
  495. }
  496. void VisualComponent::sendKeyboardEvent(const KeyboardEvent& event) {
  497. // Send the signal to a focused component or itself
  498. if (this->focusComponent.get() != nullptr) {
  499. this->focusComponent->sendKeyboardEvent(event);
  500. } else {
  501. this->receiveKeyboardEvent(event);
  502. }
  503. // Send focus events in case that any component changed focus.
  504. if (this->parent == nullptr) {
  505. this->sendNotifications();
  506. }
  507. }
  508. void VisualComponent::receiveKeyboardEvent(const KeyboardEvent& event) {
  509. if (event.keyboardEventType == KeyboardEventType::KeyDown) {
  510. this->callback_keyDownEvent(event);
  511. } else if (event.keyboardEventType == KeyboardEventType::KeyUp) {
  512. this->callback_keyUpEvent(event);
  513. } else if (event.keyboardEventType == KeyboardEventType::KeyType) {
  514. this->callback_keyTypeEvent(event);
  515. }
  516. }
  517. void VisualComponent::applyTheme(VisualTheme theme) {
  518. this->theme = theme;
  519. this->changedTheme(theme);
  520. for (int i = 0; i < this->getChildCount(); i++) {
  521. this->children[i] -> applyTheme(theme);
  522. }
  523. }
  524. VisualTheme VisualComponent::getTheme() const {
  525. return this->theme;
  526. }
  527. void VisualComponent::changedTheme(VisualTheme newTheme) {}
  528. String VisualComponent::call(const ReadableString &methodName, const ReadableString &arguments) {
  529. throwError("Unimplemented custom call received");
  530. return U"";
  531. }
  532. bool VisualComponent::managesChildren() {
  533. return false;
  534. }
  535. MediaResult dsr::component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed, int focused, int hover) {
  536. return method.callUsingKeywords([&theme, &method, width, height, red, green, blue, pressed, focused, hover](MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName){
  537. if (string_caseInsensitiveMatch(argumentName, U"width")) {
  538. machine_setInputByIndex(machine, methodIndex, inputIndex, width);
  539. } else if (string_caseInsensitiveMatch(argumentName, U"height")) {
  540. machine_setInputByIndex(machine, methodIndex, inputIndex, height);
  541. } else if (string_caseInsensitiveMatch(argumentName, U"pressed")) {
  542. machine_setInputByIndex(machine, methodIndex, inputIndex, pressed);
  543. } else if (string_caseInsensitiveMatch(argumentName, U"focused")) {
  544. machine_setInputByIndex(machine, methodIndex, inputIndex, focused);
  545. } else if (string_caseInsensitiveMatch(argumentName, U"hover")) {
  546. machine_setInputByIndex(machine, methodIndex, inputIndex, hover);
  547. } else if (string_caseInsensitiveMatch(argumentName, U"red")) {
  548. machine_setInputByIndex(machine, methodIndex, inputIndex, red);
  549. } else if (string_caseInsensitiveMatch(argumentName, U"green")) {
  550. machine_setInputByIndex(machine, methodIndex, inputIndex, green);
  551. } else if (string_caseInsensitiveMatch(argumentName, U"blue")) {
  552. machine_setInputByIndex(machine, methodIndex, inputIndex, blue);
  553. } else if (theme_assignMediaMachineArguments(theme, method.contextIndex, machine, methodIndex, inputIndex, argumentName)) {
  554. // Assigned by theme_assignMediaMachineArguments.
  555. } else {
  556. // TODO: Ask the theme for the argument using a specified style class for variations between different types of buttons, checkboxes, panels, et cetera.
  557. // Throw an exception if the theme did not provide an input argument to its own media function.
  558. throwError(U"Unhandled setting \"", argumentName, U"\" requested by the media method \"", machine_getMethodName(machine, methodIndex), U"\" in the visual theme!\n");
  559. }
  560. });
  561. }