Added support for scrolling text via the scrollingText tag. It uses the following extra attributes:

direction: horizontal | vertical
  horizontal scrolling will scroll over a single line.
  vertical scrolling will scroll multiple lines if the text does not fit inside the box.

alignment: left | right | centered | justified
  Defines the alignment of the vertical scrolling text inside the box.

scrollingSpeed:
  Defines the scrolling speed in pixels per second.

startPosition:
  Defines the horizontal starting position of the text compared to the left of the box.
  Defines the vertical starting position of the text compared to the top of the box.

startTime:
  Defines how many seconds the text will wait before it starts scrolling.

endTime:
  Defines how many seconds the text will take to reappear after it's done scrolling.
This commit is contained in:
Pieter Hulshoff 2016-07-06 09:39:08 +02:00
parent ad93b0a93d
commit 6a2ab3d2af
4 changed files with 709 additions and 1 deletions

View File

@ -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"

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <fstream>
#include <sstream>
#include <vector>
#include <iostream>
#include <algorithm>
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<std::string> 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<int>( xOrigin );
if (currentPosition_ < 0)
{
rect.x -= static_cast<int>( 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<int>( 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<int>( charRect.h * scale * scaleY_ );
rect.w = static_cast<int>( charRect.w * scale * scaleX_ );
rect.y = static_cast<int>( yOrigin );
if (font->getAscent( ) < glyph.maxY)
{
rect.y += static_cast<int>( (font->getAscent( ) - glyph.maxY) * scale * scaleY_ );
}
// Check if glyph falls partially outside the box at the back end
if ((rect.x + static_cast<int>( glyph.advance * scale * scaleX_ )) >= (static_cast<int>( xOrigin ) + imageMaxWidth))
{
rect.w = static_cast<int>( xOrigin ) + imageMaxWidth - rect.x;
charRect.w = static_cast<int>( 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<int>( glyph.advance * scale * scaleX_ + position - currentPosition_ );
charRect.x = static_cast<int>( charRect.x + charRect.w - rect.w / scale / scaleX_ );
charRect.w = static_cast<int>( 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<int>( glyph.advance * scale * scaleX_);
}
}
// Reformat the text based on the image width
std::vector<std::string> text;
std::vector<unsigned int> textWords;
std::vector<unsigned int> textWidth;
std::vector<bool> 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<int>( 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<int>( 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<int>( yOrigin ) + imageMaxHeight))
{
break;
}
// Define x coordinate
rect.x = static_cast<int>( xOrigin );
if (alignment_ == "right")
{
rect.x = static_cast<int>( xOrigin + imageMaxWidth - textWidth[l] - (textWords[l] - 1) * spaceWidth * scale * scaleX_ );
}
if (alignment_ == "centered")
{
rect.x = static_cast<int>( 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<int>( charRect.h * scale * scaleY_ );
rect.w = static_cast<int>( 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<int>( yOrigin ) + imageMaxHeight))
{
rect.h = static_cast<int>( yOrigin ) + imageMaxHeight - rect.y;
charRect.h = static_cast<int>( 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<int>( font->getHeight( ) * scale * scaleX_ + position - currentPosition_ );
rect.h = static_cast<int>( font->getHeight( ) * scale * scaleX_ + position - currentPosition_ );
charRect.y = static_cast<int>( charRect.y + charRect.h - rect.h / scale / scaleX_ );
charRect.h = static_cast<int>( 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<int>( glyph.advance * scale * scaleX_ );
}
}
// Print justified
wordCount -= 1;
if (wordCount > 0 && !textLast[l] && alignment_ == "justified")
{
unsigned int advance = static_cast<int>( spaceFill / wordCount );
spaceFill -= advance;
rect.x += advance;
}
else
{
rect.x += static_cast<int>( 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<int>( 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<int>( 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_;
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "Component.h"
#include "../../Collection/Item.h"
#include <SDL2/SDL.h>
#include <vector>
#include <string>
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<std::string> 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_;
};

View File

@ -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);