diff --git a/RetroFE/Source/CMakeLists.txt b/RetroFE/Source/CMakeLists.txt index 47f3028..30c1d15 100644 --- a/RetroFE/Source/CMakeLists.txt +++ b/RetroFE/Source/CMakeLists.txt @@ -115,6 +115,7 @@ set(RETROFE_HEADERS "${RETROFE_DIR}/Source/Graphics/Component/ImageBuilder.h" "${RETROFE_DIR}/Source/Graphics/Component/ReloadableMedia.h" "${RETROFE_DIR}/Source/Graphics/Component/ReloadableText.h" + "${RETROFE_DIR}/Source/Graphics/Component/ScrollingText.h" "${RETROFE_DIR}/Source/Graphics/Component/ScrollingList.h" "${RETROFE_DIR}/Source/Graphics/Component/Text.h" "${RETROFE_DIR}/Source/Graphics/Component/VideoComponent.h" @@ -171,6 +172,7 @@ set(RETROFE_SOURCES "${RETROFE_DIR}/Source/Graphics/Component/Text.cpp" "${RETROFE_DIR}/Source/Graphics/Component/ReloadableMedia.cpp" "${RETROFE_DIR}/Source/Graphics/Component/ReloadableText.cpp" + "${RETROFE_DIR}/Source/Graphics/Component/ScrollingText.cpp" "${RETROFE_DIR}/Source/Graphics/Component/ScrollingList.cpp" "${RETROFE_DIR}/Source/Graphics/Component/VideoBuilder.cpp" "${RETROFE_DIR}/Source/Graphics/Component/VideoComponent.cpp" diff --git a/RetroFE/Source/Graphics/Component/ScrollingText.cpp b/RetroFE/Source/Graphics/Component/ScrollingText.cpp new file mode 100644 index 0000000..1efd166 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/ScrollingText.cpp @@ -0,0 +1,594 @@ +/* This file is part of RetroFE. + * + * RetroFE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RetroFE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RetroFE. If not, see . + */ + +#include "ScrollingText.h" +#include "../ViewInfo.h" +#include "../../Database/Configuration.h" +#include "../../Utility/Log.h" +#include "../../Utility/Utils.h" +#include "../../SDL.h" +#include "../Font.h" +#include +#include +#include +#include +#include + + +ScrollingText::ScrollingText(Configuration &config, bool systemMode, bool layoutMode, std::string type, std::string textFormat, std::string alignment, Page &p, int displayOffset, Font *font, float scaleX, float scaleY, std::string direction, float scrollingSpeed, float startPosition, float startTime, float endTime ) + : Component(p) + , config_(config) + , systemMode_(systemMode) + , layoutMode_(layoutMode) + , fontInst_(font) + , type_(type) + , textFormat_(textFormat) + , alignment_(alignment) + , scaleX_(scaleX) + , scaleY_(scaleY) + , direction_(direction) + , scrollingSpeed_(scrollingSpeed) + , startPosition_(startPosition) + , currentPosition_(-startPosition) + , startTime_(startTime) + , waitStartTime_(startTime) + , endTime_(endTime) + , waitEndTime_(0.0f) + , currentCollection_("") + , page_(NULL) + , displayOffset_(displayOffset) + +{ + text_.clear( ); +} + + +ScrollingText::~ScrollingText( ) +{ +} + + +void ScrollingText::update(float dt) +{ + + if (waitEndTime_ > 0) + { + waitEndTime_ -= dt; + } + else if (waitStartTime_ > 0) + { + waitStartTime_ -= dt; + } + else + { + if (direction_ == "horizontal") + { + currentPosition_ += scrollingSpeed_ * dt * scaleX_; + } + else if (direction_ == "vertical") + { + currentPosition_ += scrollingSpeed_ * dt * scaleY_; + } + } + + if (newItemSelected) + { + reloadTexture( ); + newItemSelected = false; + } + + Component::update(dt); +} + + +void ScrollingText::freeGraphicsMemory( ) +{ + Component::freeGraphicsMemory( ); + text_.clear( ); +} + + +void ScrollingText::reloadTexture( ) +{ + + currentPosition_ = -startPosition_ * scaleX_; + waitStartTime_ = startTime_; + waitEndTime_ = 0.0f; + + text_.clear( ); + + Item *selectedItem = page.getSelectedItem( displayOffset_ ); + if (!selectedItem) + { + return; + } + + config_.getProperty( "currentCollection", currentCollection_ ); + + // build clone list + std::vector names; + + names.push_back( selectedItem->name ); + names.push_back( selectedItem->fullTitle ); + + if (selectedItem->cloneof.length( ) > 0) + { + names.push_back( selectedItem->cloneof ); + } + + // Check for corresponding .txt files + for (unsigned int n = 0; n < names.size( ) && text_.empty( ); ++n) + { + + std::string basename = names[n]; + + Utils::replaceSlashesWithUnderscores( basename ); + + if (systemMode_) + { + + // check the master collection for the system artifact + loadText( collectionName, type_, type_, true ); + + // check collection for the system artifact + if (text_.empty( )) + { + loadText( selectedItem->collectionInfo->name, type_, type_, true ); + } + + } + else + { + + // are we looking at a leaf or a submenu + if (selectedItem->leaf) // item is a leaf + { + + // check the master collection for the artifact + loadText( collectionName, type_, basename, false ); + + // check the collection for the artifact + if (text_.empty( )) + { + loadText( selectedItem->collectionInfo->name, type_, basename, false ); + } + + } + else // item is a submenu + { + + // check the master collection for the artifact + loadText( collectionName, type_, basename, false ); + + // check the collection for the artifact + if (text_.empty( )) + { + loadText( selectedItem->collectionInfo->name, type_, basename, false ); + } + + // check the submenu collection for the system artifact + if (text_.empty( )) + { + loadText( selectedItem->name, type_, type_, true ); + } + + } + + } + } + +} + + +void ScrollingText::loadText( std::string collection, std::string type, std::string basename, bool systemMode ) +{ + + std::string textPath = ""; + + // check the system folder + if (layoutMode_) + { + std::string layoutName; + config_.getProperty("layout", layoutName); + textPath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, "collections", collection); + if (systemMode) + textPath = Utils::combinePath(textPath, "system_artwork"); + else + textPath = Utils::combinePath(textPath, "medium_artwork", type); + } + else + { + config_.getMediaPropertyAbsolutePath( collection, type, systemMode, textPath ); + } + + textPath = Utils::combinePath( textPath, basename ); + + textPath += ".txt"; + + std::ifstream includeStream( textPath.c_str( ) ); + + if (!includeStream.good( )) + { + return; + } + + std::string line; + + while(std::getline(includeStream, line)) + { + + // In horizontal scrolling direction, add a space before every line except the first. + if (direction_ == "horizontal" && !text_.empty( )) + { + line = " " + line; + } + + // Reformat lines to uppercase or lowercase + if (textFormat_ == "uppercase") + { + std::transform(line.begin(), line.end(), line.begin(), ::toupper); + } + if (textFormat_ == "lowercase") + { + std::transform(line.begin(), line.end(), line.begin(), ::tolower); + } + + text_.push_back( line ); + + } + + return; + +} + + +void ScrollingText::draw( ) +{ + Component::draw( ); + + if (!text_.empty( ) && waitEndTime_ <= 0.0f) + { + + Font *font; + if (baseViewInfo.font) // Use font of this specific item if available + font = baseViewInfo.font; + else // If not, use the general font settings + font = fontInst_; + + SDL_Texture *t = font->getTexture( ); + + float imageWidth = 0; + float imageMaxWidth = 0; + float imageMaxHeight = 0; + if (baseViewInfo.Width < baseViewInfo.MaxWidth && baseViewInfo.Width > 0) + { + imageMaxWidth = baseViewInfo.Width; + } + else + { + imageMaxWidth = baseViewInfo.MaxWidth; + } + if (baseViewInfo.Height < baseViewInfo.MaxHeight && baseViewInfo.Height > 0) + { + imageMaxHeight = baseViewInfo.Height; + } + else + { + imageMaxHeight = baseViewInfo.MaxHeight; + } + + float scale = (float)baseViewInfo.FontSize / (float)font->getHeight( ); + + float xOrigin = baseViewInfo.XRelativeToOrigin( ); + float yOrigin = baseViewInfo.YRelativeToOrigin( ); + + SDL_Rect rect; + + float position = 0.0f; + + if (direction_ == "horizontal") + { + + rect.x = static_cast( xOrigin ); + + if (currentPosition_ < 0) + { + rect.x -= static_cast( currentPosition_ ); + } + + for (unsigned int l = 0; l < text_.size( ); ++l) + { + for (unsigned int i = 0; i < text_[l].size( ); ++i) + { + + // Do not print outside the box + if (rect.x >= (static_cast( xOrigin ) + imageMaxWidth)) + { + break; + } + + Font::GlyphInfo glyph; + + if (font->getRect( text_[l][i], glyph) && glyph.rect.h > 0) + { + SDL_Rect charRect = glyph.rect; + rect.h = static_cast( charRect.h * scale * scaleY_ ); + rect.w = static_cast( charRect.w * scale * scaleX_ ); + rect.y = static_cast( yOrigin ); + + if (font->getAscent( ) < glyph.maxY) + { + rect.y += static_cast( (font->getAscent( ) - glyph.maxY) * scale * scaleY_ ); + } + + // Check if glyph falls partially outside the box at the back end + if ((rect.x + static_cast( glyph.advance * scale * scaleX_ )) >= (static_cast( xOrigin ) + imageMaxWidth)) + { + rect.w = static_cast( xOrigin ) + imageMaxWidth - rect.x; + charRect.w = static_cast( rect.w / scale / scaleX_ ); + } + + // Print the glyph if it falls (partially) within the box + if ( position + glyph.advance * scale * scaleX_ > currentPosition_ ) + { + // Check if glyph falls partially outside the box at the front end + if ( position < currentPosition_ ) + { + rect.w = static_cast( glyph.advance * scale * scaleX_ + position - currentPosition_ ); + charRect.x = static_cast( charRect.x + charRect.w - rect.w / scale / scaleX_ ); + charRect.w = static_cast( rect.w / scale / scaleX_ ); + } + if (rect.w > 0) + { + SDL::renderCopy(t, baseViewInfo.Alpha, &charRect, &rect, baseViewInfo.Angle, baseViewInfo.Reflection, baseViewInfo.ReflectionDistance, baseViewInfo.ReflectionScale, baseViewInfo.ReflectionAlpha); + } + rect.x += rect.w; + } + position += glyph.advance * scale * scaleX_; + + } + } + } + + // Determine image width + for (unsigned int l = 0; l < text_.size( ); ++l) + { + for (unsigned int i = 0; i < text_[l].size( ); ++i) + { + Font::GlyphInfo glyph; + if (font->getRect( text_[l][i], glyph )) + { + imageWidth += glyph.advance; + } + } + } + + // Reset scrolling position when we're done + if (currentPosition_ > imageWidth * scale * scaleX_) + { + waitStartTime_ = startTime_; + waitEndTime_ = endTime_; + currentPosition_ = -startPosition_ * scaleX_; + } + + } + else if (direction_ == "vertical") + { + + unsigned int spaceWidth = 0; + { + Font::GlyphInfo glyph; + if (font->getRect( ' ', glyph) ) + { + spaceWidth = static_cast( glyph.advance * scale * scaleX_); + } + } + + // Reformat the text based on the image width + std::vector text; + std::vector textWords; + std::vector textWidth; + std::vector textLast; + for (unsigned int l = 0; l < text_.size( ); ++l) + { + std::string line = ""; + std::istringstream iss(text_[l]); + std::string word; + unsigned int width = 0; + unsigned int lineWidth = 0; + unsigned int wordCount = 0; + while (iss >> word) + { + + // Determine word image width + unsigned int wordWidth = 0; + for (unsigned int i = 0; i < word.size( ); ++i) + { + Font::GlyphInfo glyph; + if (font->getRect( word[i], glyph) ) + { + wordWidth += static_cast( glyph.advance * scale * scaleX_ ); + } + } + // Determine if the word will fit on the line + if (width > 0 && (width + spaceWidth + wordWidth > imageMaxWidth)) + { + text.push_back( line ); + textWords.push_back( wordCount ); + textWidth.push_back( lineWidth ); + textLast.push_back( false ); + line = word; + width = wordWidth; + lineWidth = wordWidth; + wordCount = 1; + } + else + { + if (width == 0) + { + line += word; + width += wordWidth; + } + else + { + line += " " + word; + width += spaceWidth + wordWidth; + } + lineWidth += wordWidth; + wordCount += 1; + } + } + if (text_[l] == "" || line != "") + { + text.push_back( line ); + textWords.push_back( wordCount ); + textWidth.push_back( lineWidth ); + textLast.push_back( true ); + width = 0; + lineWidth = 0; + wordCount = 0; + } + } + + + // Print reformatted text + rect.y = static_cast( yOrigin ); + + // Do not scroll if the text fits fully inside the box, and start position is 0 + if (text.size() * font->getHeight( ) * scale * scaleY_ <= imageMaxHeight && startPosition_ == 0.0f) + { + currentPosition_ = 0.0f; + startTime_ = 0.0f; + endTime_ = 0.0f; + } + + for (unsigned int l = 0; l < text.size( ); ++l) + { + + // Do not print outside the box + if (rect.y >= (static_cast( yOrigin ) + imageMaxHeight)) + { + break; + } + + // Define x coordinate + rect.x = static_cast( xOrigin ); + if (alignment_ == "right") + { + rect.x = static_cast( xOrigin + imageMaxWidth - textWidth[l] - (textWords[l] - 1) * spaceWidth * scale * scaleX_ ); + } + if (alignment_ == "centered") + { + rect.x = static_cast( xOrigin + imageMaxWidth / 2 - textWidth[l] / 2 - (textWords[l] - 1) * spaceWidth * scale * scaleX_ / 2 ); + } + + std::istringstream iss(text[l]); + std::string word; + unsigned int wordCount = textWords[l]; + unsigned int spaceFill = imageMaxWidth - textWidth[l]; + unsigned int yAdvance = font->getHeight( ) * scale * scaleY_; + while (iss >> word) + { + + for (unsigned int i = 0; i < word.size( ); ++i) + { + Font::GlyphInfo glyph; + + if (font->getRect( word[i], glyph) && glyph.rect.h > 0) + { + SDL_Rect charRect = glyph.rect; + rect.h = static_cast( charRect.h * scale * scaleY_ ); + rect.w = static_cast( charRect.w * scale * scaleX_ ); + yAdvance = font->getHeight( ) * scale * scaleY_; + + // Check if glyph falls partially outside the box at the bottom end + if ((rect.y + rect.h) >= (static_cast( yOrigin ) + imageMaxHeight)) + { + rect.h = static_cast( yOrigin ) + imageMaxHeight - rect.y; + charRect.h = static_cast( rect.h / scale / scaleY_ ); + } + + // Print the glyph if it falls (partially) within the box + if ( position + font->getHeight( ) * scale * scaleY_ > currentPosition_ ) + { + // Check if glyph falls partially outside the box at the front end + if ( position < currentPosition_ ) + { + yAdvance -= rect.h - static_cast( font->getHeight( ) * scale * scaleX_ + position - currentPosition_ ); + rect.h = static_cast( font->getHeight( ) * scale * scaleX_ + position - currentPosition_ ); + charRect.y = static_cast( charRect.y + charRect.h - rect.h / scale / scaleX_ ); + charRect.h = static_cast( rect.h / scale / scaleX_ ); + } + if (rect.h > 0) + { + SDL::renderCopy(t, baseViewInfo.Alpha, &charRect, &rect, baseViewInfo.Angle, baseViewInfo.Reflection, baseViewInfo.ReflectionDistance, baseViewInfo.ReflectionScale, baseViewInfo.ReflectionAlpha); + } + } + rect.x += static_cast( glyph.advance * scale * scaleX_ ); + } + } + + // Print justified + wordCount -= 1; + if (wordCount > 0 && !textLast[l] && alignment_ == "justified") + { + unsigned int advance = static_cast( spaceFill / wordCount ); + spaceFill -= advance; + rect.x += advance; + } + else + { + rect.x += static_cast( spaceWidth * scale * scaleX_ ); + } + } + + // Handle scrolling of empty lines + if (text[l] == "") + { + Font::GlyphInfo glyph; + + if (font->getRect( ' ', glyph) && glyph.rect.h > 0) + { + rect.h = static_cast( glyph.rect.h * scale * scaleY_ ); + + // Check if the glyph falls (partially) within the box at the front end + if ((position + font->getHeight( ) * scale * scaleY_ > currentPosition_) && + (position < currentPosition_)) + { + yAdvance -= rect.h - static_cast( font->getHeight( ) * scale * scaleX_ + position - currentPosition_ ); + } + } + } + + if ( position + font->getHeight( ) * scale * scaleY_ > currentPosition_ ) + { + rect.y += yAdvance; + } + position += font->getHeight( ) * scale * scaleY_; + + } + + // Reset scrolling position when we're done + if (currentPosition_ > text.size( ) * font->getHeight( ) * scale * scaleX_) + { + waitStartTime_ = startTime_; + waitEndTime_ = endTime_; + currentPosition_ = -startPosition_ * scaleY_; + } + + } + } +} diff --git a/RetroFE/Source/Graphics/Component/ScrollingText.h b/RetroFE/Source/Graphics/Component/ScrollingText.h new file mode 100644 index 0000000..4d1cc92 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/ScrollingText.h @@ -0,0 +1,57 @@ +/* This file is part of RetroFE. + * + * RetroFE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RetroFE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RetroFE. If not, see . + */ +#pragma once +#include "Component.h" +#include "../../Collection/Item.h" +#include +#include +#include + + +class ScrollingText : public Component +{ +public: + ScrollingText(Configuration &config, bool systemMode, bool layoutMode, std::string type, std::string textFormat, std::string alignment, Page &page, int displayOffset, Font *font, float scaleX, float scaleY, std::string direction, float scrollingSpeed, float startPosition, float startTime, float endTime ); + virtual ~ScrollingText( ); + void update(float dt); + void draw( ); + void freeGraphicsMemory( ); + +private: + void reloadTexture( ); + void loadText( std::string collection, std::string type, std::string basename, bool systemMode ); + Configuration &config_; + bool systemMode_; + bool layoutMode_; + Font *fontInst_; + std::string type_; + std::string textFormat_; + std::string alignment_; + std::vector text_; + float scaleX_; + float scaleY_; + std::string direction_; + float scrollingSpeed_; + float startPosition_; + float currentPosition_; + float startTime_; + float waitStartTime_; + float endTime_; + float waitEndTime_; + std::string currentCollection_; + Page *page_; + int displayOffset_; +}; diff --git a/RetroFE/Source/Graphics/PageBuilder.cpp b/RetroFE/Source/Graphics/PageBuilder.cpp index 6e8b5c8..0e34492 100644 --- a/RetroFE/Source/Graphics/PageBuilder.cpp +++ b/RetroFE/Source/Graphics/PageBuilder.cpp @@ -22,6 +22,7 @@ #include "Component/Text.h" #include "Component/ReloadableText.h" #include "Component/ReloadableMedia.h" +#include "Component/ScrollingText.h" #include "Component/ScrollingList.h" #include "Component/Video.h" #include "Animate/AnimationEvents.h" @@ -432,7 +433,8 @@ bool PageBuilder::buildComponents(xml_node<> *layout, Page *page) loadReloadableImages(layout, "reloadableImage", page); loadReloadableImages(layout, "reloadableVideo", page); - loadReloadableImages(layout, "reloadableText", page); + loadReloadableImages(layout, "reloadableText", page); + loadReloadableImages(layout, "scrollingText", page); return true; } @@ -453,6 +455,12 @@ void PageBuilder::loadReloadableImages(xml_node<> *layout, std::string tagName, xml_attribute<> *pluralPrefixXml = componentXml->first_attribute("pluralPrefix"); xml_attribute<> *pluralPostfixXml = componentXml->first_attribute("pluralPostfix"); xml_attribute<> *selectedOffsetXml = componentXml->first_attribute("selectedOffset"); + xml_attribute<> *directionXml = componentXml->first_attribute("direction"); + xml_attribute<> *scrollingSpeedXml = componentXml->first_attribute("scrollingSpeed"); + xml_attribute<> *startPositionXml = componentXml->first_attribute("startPosition"); + xml_attribute<> *startTimeXml = componentXml->first_attribute("startTime"); + xml_attribute<> *endTimeXml = componentXml->first_attribute("endTime"); + xml_attribute<> *alignmentXml = componentXml->first_attribute("alignment"); bool systemMode = false; bool layoutMode = false; int selectedOffset = 0; @@ -470,6 +478,10 @@ void PageBuilder::loadReloadableImages(xml_node<> *layout, std::string tagName, { Logger::write(Logger::ZONE_ERROR, "Layout", "Image component in layout does not specify a source image file"); } + if(!type && tagName == "scrollingText") + { + Logger::write(Logger::ZONE_ERROR, "Layout", "Scroling Text component in layout does not specify a type"); + } if(mode) @@ -538,6 +550,49 @@ void PageBuilder::loadReloadableImages(xml_node<> *layout, std::string tagName, c = new ReloadableText(type->value(), *page, config_, font, layoutKey, timeFormat, textFormat, singlePrefix, singlePostfix, pluralPrefix, pluralPostfix, scaleX_, scaleY_); } } + else if(tagName == "scrollingText") + { + if(type) + { + Font *font = addFont(componentXml, NULL); + std::string direction = "horizontal"; + std::string textFormat = ""; + if (textFormatXml) + { + textFormat = textFormatXml->value(); + } + if (directionXml) + { + direction = directionXml->value(); + } + float scrollingSpeed = 1.0f; + if (scrollingSpeedXml) + { + scrollingSpeed = Utils::convertFloat(scrollingSpeedXml->value()); + } + float startPosition = 0.0f; + if (startPositionXml) + { + startPosition = Utils::convertFloat(startPositionXml->value()); + } + float startTime = 0.0f; + if (startTimeXml) + { + startTime = Utils::convertFloat(startTimeXml->value()); + } + float endTime = 0.0f; + if (endTimeXml) + { + endTime = Utils::convertFloat(endTimeXml->value()); + } + std::string alignment = ""; + if (alignmentXml) + { + alignment = alignmentXml->value(); + } + c = new ScrollingText(config_, systemMode, layoutMode, type->value(), textFormat, alignment, *page, selectedOffset, font, scaleX_, scaleY_, direction, scrollingSpeed, startPosition, startTime, endTime); + } + } else { Font *font = addFont(componentXml, NULL);