// zlib open source license // // Copyright (c) 2018 to 2023 David Forsgren Piuva // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. #include "Menu.h" using namespace dsr; PERSISTENT_DEFINITION(Menu) static AlignedImageU8 arrowImage = image_fromAscii( "< .xX>" "<.x. >" "< XX. >" "< xXX.>" "< XX. >" "<.x. >" ); void Menu::declareAttributes(StructureDefinition &target) const { VisualComponent::declareAttributes(target); target.declareAttribute(U"BackColor"); target.declareAttribute(U"ForeColor"); target.declareAttribute(U"Text"); target.declareAttribute(U"Padding"); target.declareAttribute(U"Spacing"); target.declareAttribute(U"HeadClass"); target.declareAttribute(U"ListClass"); } Persistent* Menu::findAttribute(const ReadableString &name) { if (string_caseInsensitiveMatch(name, U"Color") || string_caseInsensitiveMatch(name, U"BackColor")) { // The short Color alias refers to the back color in Buttons, because most buttons use black text. return &(this->backColor); } else if (string_caseInsensitiveMatch(name, U"ForeColor")) { return &(this->foreColor); } else if (string_caseInsensitiveMatch(name, U"Text")) { return &(this->text); } else if (string_caseInsensitiveMatch(name, U"Padding")) { return &(this->padding); } else if (string_caseInsensitiveMatch(name, U"Spacing")) { return &(this->spacing); } else if (string_caseInsensitiveMatch(name, U"HeadClass") || string_caseInsensitiveMatch(name, U"Class")) { // Class is an alias for HeadClass. return &(this->headClass); } else if (string_caseInsensitiveMatch(name, U"ListClass")) { return &(this->listClass); } else { return VisualComponent::findAttribute(name); } } Menu::Menu() {} bool Menu::isContainer() const { return true; } bool Menu::hasArrow() { return this->subMenu && this->getChildCount() > 0; } static OrderedImageRgbaU8 generateHeadImage(Menu &menu, MediaMethod imageGenerator, int pressed, int width, int height, ColorRgbI32 backColor, ColorRgbI32 foreColor, const ReadableString &text, RasterFont font) { // Create a scaled image OrderedImageRgbaU8 result; component_generateImage(menu.getTheme(), imageGenerator, width, height, backColor.red, backColor.green, backColor.blue, pressed)(result); if (string_length(text) > 0) { int backWidth = image_getWidth(result); int backHeight = image_getHeight(result); int left = menu.padding.value; int top = (backHeight - font_getSize(font)) / 2; if (pressed) { top += 1; } // Print the text font_printLine(result, font, text, IVector2D(left, top), ColorRgbaI32(foreColor, 255)); // Draw the arrow if (menu.hasArrow()) { int arrowWidth = image_getWidth(arrowImage); int arrowHeight = image_getHeight(arrowImage); int arrowLeft = backWidth - arrowWidth - 4; int arrowTop = (backHeight - arrowHeight) / 2; draw_silhouette(result, arrowImage, ColorRgbaI32(foreColor, 255), arrowLeft, arrowTop); } } return result; } void Menu::generateGraphics() { int headWidth = this->location.width(); int headHeight = this->location.height(); if (headWidth < 1) { headWidth = 1; } if (headHeight < 1) { headHeight = 1; } if (!this->hasImages) { completeAssets(); this->imageUp = generateHeadImage(*this, this->headImageMethod, 0, headWidth, headHeight, this->backColor.value, this->foreColor.value, this->text.value, this->font); this->imageDown = generateHeadImage(*this, this->headImageMethod, 0, headWidth, headHeight, ColorRgbI32(0, 0, 0), ColorRgbI32(255, 255, 255), this->text.value, this->font); this->hasImages = true; } } // Fill the listBackgroundImageMethod with a solid color void Menu::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) { this->generateGraphics(); if (this->menuHead_filter == 1) { draw_alphaFilter(targetImage, this->showingOverlay() ? this->imageDown : this->imageUp, relativeLocation.left(), relativeLocation.top()); } else { draw_copy(targetImage, this->showingOverlay() ? this->imageDown : this->imageUp, relativeLocation.left(), relativeLocation.top()); } } void Menu::generateBackground() { if (!image_exists(this->listBackgroundImage)) { int listWidth = this->overlayLocation.width(); int listHeight = this->overlayLocation.height(); if (listWidth < 1) { listWidth = 1; } if (listHeight < 1) { listHeight = 1; } component_generateImage(this->theme, this->listBackgroundImageMethod, listWidth, listHeight, this->backColor.value.red, this->backColor.value.green, this->backColor.value.blue)(this->listBackgroundImage); } } void Menu::createOverlay() { if (!this->showingOverlay()) { this->showOverlay(); this->makeFocused(); // Focus on the current menu path to make others lose focus. IRect memberBound = this->children[0]->location; for (int i = 1; i < this->getChildCount(); i++) { memberBound = IRect::merge(memberBound, this->children[i]->location); } // Calculate the new list bound. this->overlayLocation = memberBound.expanded(this->padding.value) + this->location.upperLeft(); } } bool Menu::managesChildren() { return true; } bool Menu::pointIsInsideOfOverlay(const IVector2D& pixelPosition) { return pixelPosition.x > this->overlayLocation.left() && pixelPosition.x < this->overlayLocation.right() && pixelPosition.y > this->overlayLocation.top() && pixelPosition.y < this->overlayLocation.bottom(); } void Menu::drawOverlay(ImageRgbaU8& targetImage, const IVector2D &absoluteOffset) { this->generateBackground(); IVector2D overlayOffset = absoluteOffset + this->overlayLocation.upperLeft(); if (this->menuList_filter == 1) { draw_alphaFilter(targetImage, this->listBackgroundImage, overlayOffset.x, overlayOffset.y); } else { draw_copy(targetImage, this->listBackgroundImage, overlayOffset.x, overlayOffset.y); } for (int i = 0; i < this->getChildCount(); i++) { this->children[i]->draw(targetImage, absoluteOffset + this->location.upperLeft()); } } void Menu::loadTheme(const VisualTheme &theme) { // Is it a sub-menu or top menu? this->subMenu = this->parent != nullptr && dynamic_cast