Code cleanup and code documentation update.

As of this commit, the initial code cleanup and code documentation has been completed for the entire application.
This commit is contained in:
Leon Styhre 2020-06-28 18:39:18 +02:00
parent 5b17edde8b
commit e4fdd1e20d
53 changed files with 3831 additions and 3642 deletions

View file

@ -25,7 +25,7 @@ Some key points:
* Name member variables starting with a small 'm', e.g. mMyMemberVariable * Name member variables starting with a small 'm', e.g. mMyMemberVariable
* Use the same naming convention for functions as for local variables, e.g. someFunction() * Use the same naming convention for functions as for local variables, e.g. someFunction()
* Inline functions makes perfect sense to use, but don't overdo it by using them for functions that won't be called very frequently * Inline functions makes perfect sense to use, but don't overdo it by using them for functions that won't be called very frequently
* Never put more than one statement on a single line, except for lambda expressions * Never put more than one statement on a single line (there are some exceptions though like lambda expressions and some switch statements)
* Avoid overoptimizations, especially if it sacrifices readability, makes the code hard to expand on or is error prone * Avoid overoptimizations, especially if it sacrifices readability, makes the code hard to expand on or is error prone
* For the rest, check the code and have fun! :) * For the rest, check the code and have fun! :)

View file

@ -1,5 +1,5 @@
// //
// EmulationStation.h // EmulationStation.h
// //
// Version and build information. // Version and build information.
// //

View file

@ -2,7 +2,7 @@
// SystemScreenSaver.cpp // SystemScreenSaver.cpp
// //
// Screensaver, supporting the following modes: // Screensaver, supporting the following modes:
// Dim, black, video, slideshow. // Dim, black, video, slideshow.
// //
#include "SystemScreenSaver.h" #include "SystemScreenSaver.h"

View file

@ -2,7 +2,7 @@
// SystemScreenSaver.h // SystemScreenSaver.h
// //
// Screensaver, supporting the following modes: // Screensaver, supporting the following modes:
// Dim, black, video, slideshow. // Dim, black, video, slideshow.
// //
#pragma once #pragma once

View file

@ -2,7 +2,7 @@
// GuiCollectionSystemsOptions.cpp // GuiCollectionSystemsOptions.cpp
// //
// User interface for the game collection settings. // User interface for the game collection settings.
// Submenu to the GuiMenu main menu. // Submenu to the GuiMenu main menu.
// //
#include "guis/GuiCollectionSystemsOptions.h" #include "guis/GuiCollectionSystemsOptions.h"

View file

@ -2,7 +2,7 @@
// GuiCollectionSystemsOptions.h // GuiCollectionSystemsOptions.h
// //
// User interface for the game collection settings. // User interface for the game collection settings.
// Submenu to the GuiMenu main menu. // Submenu to the GuiMenu main menu.
// //
#pragma once #pragma once

View file

@ -3,7 +3,7 @@
// //
// User interface for the screensaver options. // User interface for the screensaver options.
// Based on the GuiScreenSaverOptions template. // Based on the GuiScreenSaverOptions template.
// Submenu to the GuiMenu main menu. // Submenu to the GuiMenu main menu.
// //
#pragma once #pragma once

View file

@ -1,5 +1,5 @@
// //
// GuiSlideshowScreensaverOptions.h // GuiSlideshowScreensaverOptions.h
// //
// User interface for the slideshow screensaver options. // User interface for the slideshow screensaver options.
// Submenu to GuiGeneralScreensaverOptions. // Submenu to GuiGeneralScreensaverOptions.

View file

@ -59,7 +59,7 @@ GuiVideoScreensaverOptions::GuiVideoScreensaverOptions(Window* window, const cha
// Set subtitle position. // Set subtitle position.
auto ss_omx_subs_align = std::make_shared<OptionListComponent<std::string>> auto ss_omx_subs_align = std::make_shared<OptionListComponent<std::string>>
(mWindow, "GAME INFO ALIGNMENT", false); (mWindow, getHelpStyle(), "GAME INFO ALIGNMENT", false);
std::vector<std::string> align_mode; std::vector<std::string> align_mode;
align_mode.push_back("left"); align_mode.push_back("left");
align_mode.push_back("center"); align_mode.push_back("center");
@ -132,7 +132,7 @@ void GuiVideoScreensaverOptions::save()
"never" && Settings::getInstance()->getBool("ScreenSaverOmxPlayer")); "never" && Settings::getInstance()->getBool("ScreenSaverOmxPlayer"));
if (startingStatusNotRisky && endStatusRisky) { if (startingStatusNotRisky && endStatusRisky) {
// If before it wasn't risky but now there's a risk of problems, show warning. // If before it wasn't risky but now there's a risk of problems, show warning.
mWindow->pushGui(new GuiMsgBox(mWindow, mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
"Using OMX Player and displaying Game Info may result in the video flickering in " "Using OMX Player and displaying Game Info may result in the video flickering in "
"some TV modes. If that happens, consider:\n\n• Disabling the \"Show Game Info\" " "some TV modes. If that happens, consider:\n\n• Disabling the \"Show Game Info\" "
"option;\n• Disabling \"Overscan\" on the Pi configuration menu might help:\nRetroPie > " "option;\n• Disabling \"Overscan\" on the Pi configuration menu might help:\nRetroPie > "

View file

@ -1,5 +1,5 @@
// //
// GuiVideoScreensaverOptions.h // GuiVideoScreensaverOptions.h
// //
// User interface for the video screensaver options. // User interface for the video screensaver options.
// Submenu to GuiGeneralScreensaverOptions. // Submenu to GuiGeneralScreensaverOptions.

View file

@ -24,9 +24,9 @@
// able to have it throw in case of error with the following: // able to have it throw in case of error with the following:
//ifndef RAPIDJSON_ASSERT //ifndef RAPIDJSON_ASSERT
//#define RAPIDJSON_ASSERT(x) \ //#define RAPIDJSON_ASSERT(x) \
// if (!(x)) { \ // if (!(x)) { \
// throw std::runtime_error("rapidjson internal assertion failure: " #x); \ // throw std::runtime_error("rapidjson internal assertion failure: " #x); \
// } // }
//#endif // RAPIDJSON_ASSERT //#endif // RAPIDJSON_ASSERT
#include <rapidjson/document.h> #include <rapidjson/document.h>

View file

@ -44,11 +44,11 @@ bool HttpReq::isUrl(const std::string& str)
std::string::npos || str.find("www.") != std::string::npos)); std::string::npos || str.find("www.") != std::string::npos));
} }
HttpReq::HttpReq(const std::string& url) : mStatus(REQ_IN_PROGRESS), mHandle(NULL) HttpReq::HttpReq(const std::string& url) : mStatus(REQ_IN_PROGRESS), mHandle(nullptr)
{ {
mHandle = curl_easy_init(); mHandle = curl_easy_init();
if (mHandle == NULL) { if (mHandle == nullptr) {
mStatus = REQ_IO_ERROR; mStatus = REQ_IO_ERROR;
onError("curl_easy_init failed"); onError("curl_easy_init failed");
return; return;
@ -147,7 +147,7 @@ HttpReq::Status HttpReq::status()
if (msg->msg == CURLMSG_DONE) { if (msg->msg == CURLMSG_DONE) {
HttpReq* req = s_requests[msg->easy_handle]; HttpReq* req = s_requests[msg->easy_handle];
if (req == NULL) { if (req == nullptr) {
LOG(LogError) << "Cannot find easy handle!"; LOG(LogError) << "Cannot find easy handle!";
continue; continue;
} }

View file

@ -69,7 +69,7 @@ void touch(const std::string& filename)
{ {
#ifdef WIN32 #ifdef WIN32
FILE* fp = fopen(filename.c_str(), "ab+"); FILE* fp = fopen(filename.c_str(), "ab+");
if (fp != NULL) if (fp != nullptr)
fclose(fp); fclose(fp);
#else #else
int fd = open(filename.c_str(), O_CREAT|O_WRONLY, 0644); int fd = open(filename.c_str(), O_CREAT|O_WRONLY, 0644);

View file

@ -11,11 +11,12 @@
#include "Log.h" #include "Log.h"
#include "Scripting.h" #include "Scripting.h"
#include "Platform.h" #include "Platform.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
Settings* Settings::sInstance = NULL; Settings* Settings::sInstance = nullptr;
// These values are NOT saved to es_settings.cfg since they're not set via // These values are NOT saved to es_settings.cfg since they're not set via
// the in-program settings menu. Most can be set using command-line arguments, // the in-program settings menu. Most can be set using command-line arguments,
@ -59,7 +60,7 @@ Settings::Settings()
Settings* Settings::getInstance() Settings* Settings::getInstance()
{ {
if (sInstance == NULL) if (sInstance == nullptr)
sInstance = new Settings(); sInstance = new Settings();
return sInstance; return sInstance;

View file

@ -82,7 +82,7 @@ bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID)
Sound::Sound( Sound::Sound(
const std::string & path) const std::string & path)
: mSampleData(NULL), : mSampleData(nullptr),
mSamplePos(0), mSamplePos(0),
mSampleLength(0), mSampleLength(0),
playing(false) playing(false)
@ -103,7 +103,7 @@ void Sound::loadFile(const std::string & path)
void Sound::init() void Sound::init()
{ {
if(mSampleData != NULL) if(mSampleData != nullptr)
deinit(); deinit();
if(mPath.empty()) if(mPath.empty())

View file

@ -1,86 +1,90 @@
//
// AnimatedImageComponent.cpp
//
// Creates animation from multiple images files.
//
#include "components/AnimatedImageComponent.h" #include "components/AnimatedImageComponent.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
#include "Log.h" #include "Log.h"
AnimatedImageComponent::AnimatedImageComponent(Window* window) : GuiComponent(window), mEnabled(false) AnimatedImageComponent::AnimatedImageComponent(Window* window)
: GuiComponent(window), mEnabled(false)
{ {
} }
void AnimatedImageComponent::load(const AnimationDef* def) void AnimatedImageComponent::load(const AnimationDef* def)
{ {
mFrames.clear(); mFrames.clear();
assert(def->frameCount >= 1); assert(def->frameCount >= 1);
for(size_t i = 0; i < def->frameCount; i++) for (size_t i = 0; i < def->frameCount; i++) {
{ if (def->frames[i].path != nullptr &&
if(def->frames[i].path != NULL && !ResourceManager::getInstance()->fileExists(def->frames[i].path)) !ResourceManager::getInstance()->fileExists(def->frames[i].path)) {
{ LOG(LogError) << "Missing animation frame " << i <<
LOG(LogError) << "Missing animation frame " << i << " (\"" << def->frames[i].path << "\")"; " (\"" << def->frames[i].path << "\")";
continue; continue;
} }
auto img = std::unique_ptr<ImageComponent>(new ImageComponent(mWindow)); auto img = std::unique_ptr<ImageComponent>(new ImageComponent(mWindow));
img->setResize(mSize.x(), mSize.y()); img->setResize(mSize.x(), mSize.y());
img->setImage(std::string(def->frames[i].path), false); img->setImage(std::string(def->frames[i].path), false);
mFrames.push_back(ImageFrame(std::move(img), def->frames[i].time)); mFrames.push_back(ImageFrame(std::move(img), def->frames[i].time));
} }
mLoop = def->loop; mLoop = def->loop;
mCurrentFrame = 0; mCurrentFrame = 0;
mFrameAccumulator = 0; mFrameAccumulator = 0;
mEnabled = true; mEnabled = true;
} }
void AnimatedImageComponent::reset() void AnimatedImageComponent::reset()
{ {
mCurrentFrame = 0; mCurrentFrame = 0;
mFrameAccumulator = 0; mFrameAccumulator = 0;
} }
void AnimatedImageComponent::onSizeChanged() void AnimatedImageComponent::onSizeChanged()
{ {
for(auto it = mFrames.cbegin(); it != mFrames.cend(); it++) for (auto it = mFrames.cbegin(); it != mFrames.cend(); it++) {
{ it->first->setResize(mSize.x(), mSize.y());
it->first->setResize(mSize.x(), mSize.y()); }
}
} }
void AnimatedImageComponent::update(int deltaTime) void AnimatedImageComponent::update(int deltaTime)
{ {
if(!mEnabled || mFrames.size() == 0) if (!mEnabled || mFrames.size() == 0)
return; return;
mFrameAccumulator += deltaTime; mFrameAccumulator += deltaTime;
while(mFrames.at(mCurrentFrame).second <= mFrameAccumulator) while (mFrames.at(mCurrentFrame).second <= mFrameAccumulator) {
{ mCurrentFrame++;
mCurrentFrame++;
if(mCurrentFrame == (int)mFrames.size()) if (mCurrentFrame == (int)mFrames.size()) {
{ if (mLoop) {
if(mLoop) // Restart.
{ mCurrentFrame = 0;
// restart }
mCurrentFrame = 0; else {
}else{ // Done, stop at last frame.
// done, stop at last frame mCurrentFrame--;
mCurrentFrame--; mEnabled = false;
mEnabled = false; break;
break; }
} }
}
mFrameAccumulator -= mFrames.at(mCurrentFrame).second; mFrameAccumulator -= mFrames.at(mCurrentFrame).second;
} }
} }
void AnimatedImageComponent::render(const Transform4x4f& trans) void AnimatedImageComponent::render(const Transform4x4f& trans)
{ {
if(mFrames.size()) if (mFrames.size())
mFrames.at(mCurrentFrame).first->render(getTransform() * trans); mFrames.at(mCurrentFrame).first->render(getTransform() * trans);
} }

View file

@ -1,3 +1,9 @@
//
// AnimatedImageComponent.h
//
// Creates animation from multiple images files.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_ANIMATED_IMAGE_COMPONENT_H #ifndef ES_CORE_COMPONENTS_ANIMATED_IMAGE_COMPONENT_H
#define ES_CORE_COMPONENTS_ANIMATED_IMAGE_COMPONENT_H #define ES_CORE_COMPONENTS_ANIMATED_IMAGE_COMPONENT_H
@ -6,42 +12,40 @@
class ImageComponent; class ImageComponent;
struct AnimationFrame struct AnimationFrame {
{ const char* path;
const char* path; int time;
int time;
}; };
struct AnimationDef struct AnimationDef {
{ AnimationFrame* frames;
AnimationFrame* frames; size_t frameCount;
size_t frameCount; bool loop;
bool loop;
}; };
class AnimatedImageComponent : public GuiComponent class AnimatedImageComponent : public GuiComponent
{ {
public: public:
AnimatedImageComponent(Window* window); AnimatedImageComponent(Window* window);
void load(const AnimationDef* def); // no reference to def is kept after loading is complete void load(const AnimationDef* def); // No reference to def is kept after loading is complete.
void reset(); // set to frame 0 void reset(); // Set to frame 0.
void update(int deltaTime) override; void update(int deltaTime) override;
void render(const Transform4x4f& trans) override; void render(const Transform4x4f& trans) override;
void onSizeChanged() override; void onSizeChanged() override;
private: private:
typedef std::pair<std::unique_ptr<ImageComponent>, int> ImageFrame; typedef std::pair<std::unique_ptr<ImageComponent>, int> ImageFrame;
std::vector<ImageFrame> mFrames; std::vector<ImageFrame> mFrames;
bool mLoop; bool mLoop;
bool mEnabled; bool mEnabled;
int mFrameAccumulator; int mFrameAccumulator;
int mCurrentFrame; int mCurrentFrame;
}; };
#endif // ES_CORE_COMPONENTS_ANIMATED_IMAGE_COMPONENT_H #endif // ES_CORE_COMPONENTS_ANIMATED_IMAGE_COMPONENT_H

View file

@ -1,56 +1,65 @@
//
// BusyComponent.cpp
//
// Animated busy indicator.
//
#include "BusyComponent.h" #include "BusyComponent.h"
#include "components/AnimatedImageComponent.h" #include "components/AnimatedImageComponent.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
// animation definition // Animation definition.
AnimationFrame BUSY_ANIMATION_FRAMES[] = { AnimationFrame BUSY_ANIMATION_FRAMES[] = {
{":/graphics/busy_0.svg", 300}, {":/graphics/busy_0.svg", 300},
{":/graphics/busy_1.svg", 300}, {":/graphics/busy_1.svg", 300},
{":/graphics/busy_2.svg", 300}, {":/graphics/busy_2.svg", 300},
{":/graphics/busy_3.svg", 300}, {":/graphics/busy_3.svg", 300},
}; };
const AnimationDef BUSY_ANIMATION_DEF = { BUSY_ANIMATION_FRAMES, 4, true }; const AnimationDef BUSY_ANIMATION_DEF = { BUSY_ANIMATION_FRAMES, 4, true };
BusyComponent::BusyComponent(Window* window) : GuiComponent(window), BusyComponent::BusyComponent(Window* window): GuiComponent(window),
mBackground(window, ":/graphics/frame.png"), mGrid(window, Vector2i(5, 3)) mBackground(window, ":/graphics/frame.png"), mGrid(window, Vector2i(5, 3))
{ {
mAnimation = std::make_shared<AnimatedImageComponent>(mWindow); mAnimation = std::make_shared<AnimatedImageComponent>(mWindow);
mAnimation->load(&BUSY_ANIMATION_DEF); mAnimation->load(&BUSY_ANIMATION_DEF);
mText = std::make_shared<TextComponent>(mWindow, "WORKING...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); mText = std::make_shared<TextComponent>(mWindow, "WORKING...",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
// col 0 = animation, col 1 = spacer, col 2 = text // Col 0 = animation, col 1 = spacer, col 2 = text.
mGrid.setEntry(mAnimation, Vector2i(1, 1), false, true); mGrid.setEntry(mAnimation, Vector2i(1, 1), false, true);
mGrid.setEntry(mText, Vector2i(3, 1), false, true); mGrid.setEntry(mText, Vector2i(3, 1), false, true);
addChild(&mBackground); addChild(&mBackground);
addChild(&mGrid); addChild(&mGrid);
} }
void BusyComponent::onSizeChanged() void BusyComponent::onSizeChanged()
{ {
mGrid.setSize(mSize); mGrid.setSize(mSize);
if(mSize.x() == 0 || mSize.y() == 0) if (mSize.x() == 0 || mSize.y() == 0)
return; return;
const float middleSpacerWidth = 0.01f * Renderer::getScreenWidth(); const float middleSpacerWidth = 0.01f * Renderer::getScreenWidth();
const float textHeight = mText->getFont()->getLetterHeight(); const float textHeight = mText->getFont()->getLetterHeight();
mText->setSize(0, textHeight); mText->setSize(0, textHeight);
const float textWidth = mText->getSize().x() + 4; const float textWidth = mText->getSize().x() + 4;
mGrid.setColWidthPerc(1, textHeight / mSize.x()); // animation is square mGrid.setColWidthPerc(1, textHeight / mSize.x()); // Animation is square.
mGrid.setColWidthPerc(2, middleSpacerWidth / mSize.x()); mGrid.setColWidthPerc(2, middleSpacerWidth / mSize.x());
mGrid.setColWidthPerc(3, textWidth / mSize.x()); mGrid.setColWidthPerc(3, textWidth / mSize.x());
mGrid.setRowHeightPerc(1, textHeight / mSize.y()); mGrid.setRowHeightPerc(1, textHeight / mSize.y());
mBackground.fitTo(Vector2f(mGrid.getColWidth(1) + mGrid.getColWidth(2) + mGrid.getColWidth(3), textHeight + 2), mBackground.fitTo(Vector2f(mGrid.getColWidth(1) +
mAnimation->getPosition(), Vector2f(0, 0)); mGrid.getColWidth(2) + mGrid.getColWidth(3), textHeight + 2),
mAnimation->getPosition(), Vector2f(0, 0));
} }
void BusyComponent::reset() void BusyComponent::reset()
{ {
//mAnimation->reset(); //mAnimation->reset();
} }

View file

@ -1,3 +1,9 @@
//
// BusyComponent.h
//
// Animated busy indicator.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_BUSY_COMPONENT_H #ifndef ES_CORE_COMPONENTS_BUSY_COMPONENT_H
#define ES_CORE_COMPONENTS_BUSY_COMPONENT_H #define ES_CORE_COMPONENTS_BUSY_COMPONENT_H
@ -12,18 +18,18 @@ class TextComponent;
class BusyComponent : public GuiComponent class BusyComponent : public GuiComponent
{ {
public: public:
BusyComponent(Window* window); BusyComponent(Window* window);
void onSizeChanged() override; void onSizeChanged() override;
void reset(); // reset to frame 0 void reset(); // Reset to frame 0.
private: private:
NinePatchComponent mBackground; NinePatchComponent mBackground;
ComponentGrid mGrid; ComponentGrid mGrid;
std::shared_ptr<AnimatedImageComponent> mAnimation; std::shared_ptr<AnimatedImageComponent> mAnimation;
std::shared_ptr<TextComponent> mText; std::shared_ptr<TextComponent> mText;
}; };
#endif // ES_CORE_COMPONENTS_BUSY_COMPONENT_H #endif // ES_CORE_COMPONENTS_BUSY_COMPONENT_H

View file

@ -1,119 +1,128 @@
//
// ButtonComponent.cpp
//
// Basic on/off button.
//
#include "components/ButtonComponent.h" #include "components/ButtonComponent.h"
#include "resources/Font.h" #include "resources/Font.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
ButtonComponent::ButtonComponent(Window* window, const std::string& text, const std::string& helpText, const std::function<void()>& func) : GuiComponent(window), ButtonComponent::ButtonComponent(
mBox(window, ":/graphics/button.png"), Window* window, const std::string& text,
mFont(Font::get(FONT_SIZE_MEDIUM)), const std::string& helpText,
mFocused(false), const std::function<void()>& func)
mEnabled(true), : GuiComponent(window),
mTextColorFocused(0xFFFFFFFF), mTextColorUnfocused(0x777777FF) mBox(window, ":/graphics/button.png"),
mFont(Font::get(FONT_SIZE_MEDIUM)),
mFocused(false),
mEnabled(true),
mTextColorFocused(0xFFFFFFFF), mTextColorUnfocused(0x777777FF)
{ {
setPressedFunc(func); setPressedFunc(func);
setText(text, helpText); setText(text, helpText);
updateImage(); updateImage();
} }
void ButtonComponent::onSizeChanged() void ButtonComponent::onSizeChanged()
{ {
mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32));
} }
void ButtonComponent::setPressedFunc(std::function<void()> f) void ButtonComponent::setPressedFunc(std::function<void()> f)
{ {
mPressedFunc = f; mPressedFunc = f;
} }
bool ButtonComponent::input(InputConfig* config, Input input) bool ButtonComponent::input(InputConfig* config, Input input)
{ {
if(config->isMappedTo("a", input) && input.value != 0) if (config->isMappedTo("a", input) && input.value != 0) {
{ if (mPressedFunc && mEnabled)
if(mPressedFunc && mEnabled) mPressedFunc();
mPressedFunc(); return true;
return true; }
}
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
void ButtonComponent::setText(const std::string& text, const std::string& helpText) void ButtonComponent::setText(const std::string& text, const std::string& helpText)
{ {
mText = Utils::String::toUpper(text); mText = Utils::String::toUpper(text);
mHelpText = helpText; mHelpText = helpText;
mTextCache = std::unique_ptr<TextCache>(mFont->buildTextCache(mText, 0, 0, getCurTextColor())); mTextCache = std::unique_ptr<TextCache>(mFont->buildTextCache(mText, 0, 0, getCurTextColor()));
float minWidth = mFont->sizeText("DELETE").x() + 12; float minWidth = mFont->sizeText("DELETE").x() + 12;
setSize(Math::max(mTextCache->metrics.size.x() + 12, minWidth), mTextCache->metrics.size.y()); setSize(Math::max(mTextCache->metrics.size.x() + 12, minWidth), mTextCache->metrics.size.y());
updateHelpPrompts(); updateHelpPrompts();
} }
void ButtonComponent::onFocusGained() void ButtonComponent::onFocusGained()
{ {
mFocused = true; mFocused = true;
updateImage(); updateImage();
} }
void ButtonComponent::onFocusLost() void ButtonComponent::onFocusLost()
{ {
mFocused = false; mFocused = false;
updateImage(); updateImage();
} }
void ButtonComponent::setEnabled(bool enabled) void ButtonComponent::setEnabled(bool enabled)
{ {
mEnabled = enabled; mEnabled = enabled;
updateImage(); updateImage();
} }
void ButtonComponent::updateImage() void ButtonComponent::updateImage()
{ {
if(!mEnabled || !mPressedFunc) if (!mEnabled || !mPressedFunc) {
{ mBox.setImagePath(":/graphics/button_filled.png");
mBox.setImagePath(":/graphics/button_filled.png"); mBox.setCenterColor(0x770000FF);
mBox.setCenterColor(0x770000FF); mBox.setEdgeColor(0x770000FF);
mBox.setEdgeColor(0x770000FF); return;
return; }
}
mBox.setCenterColor(0xFFFFFFFF); mBox.setCenterColor(0xFFFFFFFF);
mBox.setEdgeColor(0xFFFFFFFF); mBox.setEdgeColor(0xFFFFFFFF);
mBox.setImagePath(mFocused ? ":/graphics/button_filled.png" : ":/graphics/button.png"); mBox.setImagePath(mFocused ? ":/graphics/button_filled.png" : ":/graphics/button.png");
} }
void ButtonComponent::render(const Transform4x4f& parentTrans) void ButtonComponent::render(const Transform4x4f& parentTrans)
{ {
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
mBox.render(trans); mBox.render(trans);
if(mTextCache) if (mTextCache)
{ {
Vector3f centerOffset((mSize.x() - mTextCache->metrics.size.x()) / 2, (mSize.y() - mTextCache->metrics.size.y()) / 2, 0); Vector3f centerOffset((mSize.x() - mTextCache->metrics.size.x()) / 2,
trans = trans.translate(centerOffset); (mSize.y() - mTextCache->metrics.size.y()) / 2, 0);
trans = trans.translate(centerOffset);
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
mTextCache->setColor(getCurTextColor()); mTextCache->setColor(getCurTextColor());
mFont->renderTextCache(mTextCache.get()); mFont->renderTextCache(mTextCache.get());
trans = trans.translate(-centerOffset); trans = trans.translate(-centerOffset);
} }
renderChildren(trans); renderChildren(trans);
} }
unsigned int ButtonComponent::getCurTextColor() const unsigned int ButtonComponent::getCurTextColor() const
{ {
if(!mFocused) if (!mFocused)
return mTextColorUnfocused; return mTextColorUnfocused;
else else
return mTextColorFocused; return mTextColorFocused;
} }
std::vector<HelpPrompt> ButtonComponent::getHelpPrompts() std::vector<HelpPrompt> ButtonComponent::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("a", mHelpText.empty() ? mText.c_str() : mHelpText.c_str())); prompts.push_back(HelpPrompt("a", mHelpText.empty() ? mText.c_str() : mHelpText.c_str()));
return prompts; return prompts;
} }

View file

@ -1,3 +1,9 @@
//
// ButtonComponent.h
//
// Basic on/off button.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_BUTTON_COMPONENT_H #ifndef ES_CORE_COMPONENTS_BUTTON_COMPONENT_H
#define ES_CORE_COMPONENTS_BUTTON_COMPONENT_H #define ES_CORE_COMPONENTS_BUTTON_COMPONENT_H
@ -10,42 +16,42 @@ class TextCache;
class ButtonComponent : public GuiComponent class ButtonComponent : public GuiComponent
{ {
public: public:
ButtonComponent(Window* window, const std::string& text = "", const std::string& helpText = "", const std::function<void()>& func = nullptr); ButtonComponent(Window* window, const std::string& text = "",
const std::string& helpText = "", const std::function<void()>& func = nullptr);
void setPressedFunc(std::function<void()> f); void setPressedFunc(std::function<void()> f);
void setEnabled(bool enable);
void setEnabled(bool enable); bool input(InputConfig* config, Input input) override;
void render(const Transform4x4f& parentTrans) override;
bool input(InputConfig* config, Input input) override; void setText(const std::string& text, const std::string& helpText);
void render(const Transform4x4f& parentTrans) override;
void setText(const std::string& text, const std::string& helpText); inline const std::string& getText() const { return mText; };
inline const std::function<void()>& getPressedFunc() const { return mPressedFunc; };
inline const std::string& getText() const { return mText; }; void onSizeChanged() override;
inline const std::function<void()>& getPressedFunc() const { return mPressedFunc; }; void onFocusGained() override;
void onFocusLost() override;
void onSizeChanged() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
void onFocusGained() override;
void onFocusLost() override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
private: private:
std::shared_ptr<Font> mFont; std::shared_ptr<Font> mFont;
std::function<void()> mPressedFunc; std::function<void()> mPressedFunc;
bool mFocused; bool mFocused;
bool mEnabled; bool mEnabled;
unsigned int mTextColorFocused; unsigned int mTextColorFocused;
unsigned int mTextColorUnfocused; unsigned int mTextColorUnfocused;
unsigned int getCurTextColor() const; unsigned int getCurTextColor() const;
void updateImage(); void updateImage();
std::string mText; std::string mText;
std::string mHelpText; std::string mHelpText;
std::unique_ptr<TextCache> mTextCache; std::unique_ptr<TextCache> mTextCache;
NinePatchComponent mBox; NinePatchComponent mBox;
}; };
#endif // ES_CORE_COMPONENTS_BUTTON_COMPONENT_H #endif // ES_CORE_COMPONENTS_BUTTON_COMPONENT_H

View file

@ -100,7 +100,7 @@ void ComponentGrid::setEntry(
{ {
assert(pos.x() >= 0 && pos.x() < mGridSize.x() && pos.y() >= 0 && pos.y() < mGridSize.y()); assert(pos.x() >= 0 && pos.x() < mGridSize.x() && pos.y() >= 0 && pos.y() < mGridSize.y());
assert(comp != nullptr); assert(comp != nullptr);
assert(comp->getParent() == NULL); assert(comp->getParent() == nullptr);
GridEntry entry(pos, size, comp, canFocus, resize, updateType, border); GridEntry entry(pos, size, comp, canFocus, resize, updateType, border);
mCells.push_back(entry); mCells.push_back(entry);
@ -223,7 +223,7 @@ const ComponentGrid::GridEntry* ComponentGrid::getCellAt(int x, int y) const
return &(*it); return &(*it);
} }
return NULL; return nullptr;
} }
bool ComponentGrid::input(InputConfig* config, Input input) bool ComponentGrid::input(InputConfig* config, Input input)
@ -327,7 +327,7 @@ void ComponentGrid::onFocusGained()
bool ComponentGrid::cursorValid() bool ComponentGrid::cursorValid()
{ {
const GridEntry* e = getCellAt(mCursor); const GridEntry* e = getCellAt(mCursor);
return (e != NULL && e->canFocus); return (e != nullptr && e->canFocus);
} }
void ComponentGrid::update(int deltaTime) void ComponentGrid::update(int deltaTime)
@ -359,7 +359,7 @@ void ComponentGrid::render(const Transform4x4f& parentTrans)
void ComponentGrid::textInput(const char* text) void ComponentGrid::textInput(const char* text)
{ {
const GridEntry* selectedEntry = getCellAt(mCursor); const GridEntry* selectedEntry = getCellAt(mCursor);
if (selectedEntry != NULL && selectedEntry->canFocus) if (selectedEntry != nullptr && selectedEntry->canFocus)
selectedEntry->component->textInput(text); selectedEntry->component->textInput(text);
} }

View file

@ -104,7 +104,7 @@ private:
operator bool() const operator bool() const
{ {
return component != NULL; return component != nullptr;
} }
}; };

View file

@ -20,7 +20,7 @@ void ComponentList::addRow(const ComponentListRow& row, bool setCursorHere)
{ {
IList<ComponentListRow, void*>::Entry e; IList<ComponentListRow, void*>::Entry e;
e.name = ""; e.name = "";
e.object = NULL; e.object = nullptr;
e.data = row; e.data = row;
this->add(e); this->add(e);

View file

@ -1,3 +1,9 @@
//
// GridTileComponent.cpp
//
// X*Y grid.
//
#include "GridTileComponent.h" #include "GridTileComponent.h"
#include "animations/LambdaAnimation.h" #include "animations/LambdaAnimation.h"
@ -6,318 +12,310 @@
GridTileComponent::GridTileComponent(Window* window) : GuiComponent(window), mBackground(window) GridTileComponent::GridTileComponent(Window* window) : GuiComponent(window), mBackground(window)
{ {
mDefaultProperties.mSize = getDefaultTileSize(); mDefaultProperties.mSize = getDefaultTileSize();
mDefaultProperties.mPadding = Vector2f(16.0f, 16.0f); mDefaultProperties.mPadding = Vector2f(16.0f, 16.0f);
mDefaultProperties.mImageColor = 0xAAAAAABB; mDefaultProperties.mImageColor = 0xAAAAAABB;
mDefaultProperties.mBackgroundImage = ":/graphics/frame.png"; mDefaultProperties.mBackgroundImage = ":/graphics/frame.png";
mDefaultProperties.mBackgroundCornerSize = Vector2f(16 ,16); mDefaultProperties.mBackgroundCornerSize = Vector2f(16 ,16);
mDefaultProperties.mBackgroundCenterColor = 0xAAAAEEFF; mDefaultProperties.mBackgroundCenterColor = 0xAAAAEEFF;
mDefaultProperties.mBackgroundEdgeColor = 0xAAAAEEFF; mDefaultProperties.mBackgroundEdgeColor = 0xAAAAEEFF;
mSelectedProperties.mSize = getSelectedTileSize(); mSelectedProperties.mSize = getSelectedTileSize();
mSelectedProperties.mPadding = mDefaultProperties.mPadding; mSelectedProperties.mPadding = mDefaultProperties.mPadding;
mSelectedProperties.mImageColor = 0xFFFFFFFF; mSelectedProperties.mImageColor = 0xFFFFFFFF;
mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage; mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage;
mSelectedProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize; mSelectedProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize;
mSelectedProperties.mBackgroundCenterColor = 0xFFFFFFFF; mSelectedProperties.mBackgroundCenterColor = 0xFFFFFFFF;
mSelectedProperties.mBackgroundEdgeColor = 0xFFFFFFFF; mSelectedProperties.mBackgroundEdgeColor = 0xFFFFFFFF;
mImage = std::make_shared<ImageComponent>(mWindow); mImage = std::make_shared<ImageComponent>(mWindow);
mImage->setOrigin(0.5f, 0.5f); mImage->setOrigin(0.5f, 0.5f);
mBackground.setOrigin(0.5f, 0.5f); mBackground.setOrigin(0.5f, 0.5f);
addChild(&mBackground); addChild(&mBackground);
addChild(&(*mImage)); addChild(&(*mImage));
mSelectedZoomPercent = 0; mSelectedZoomPercent = 0;
setSelected(false, false); setSelected(false, false);
setVisible(true); setVisible(true);
} }
void GridTileComponent::render(const Transform4x4f& parentTrans) void GridTileComponent::render(const Transform4x4f& parentTrans)
{ {
Transform4x4f trans = getTransform() * parentTrans; Transform4x4f trans = getTransform() * parentTrans;
if (mVisible) if (mVisible)
renderChildren(trans); renderChildren(trans);
} }
// Update all the tile properties to the new status (selected or default) // Update all the tile properties to the new status (selected or default).
void GridTileComponent::update(int deltaTime) void GridTileComponent::update(int deltaTime)
{ {
GuiComponent::update(deltaTime); GuiComponent::update(deltaTime);
calcCurrentProperties(); calcCurrentProperties();
mBackground.setImagePath(mCurrentProperties.mBackgroundImage); mBackground.setImagePath(mCurrentProperties.mBackgroundImage);
mImage->setColorShift(mCurrentProperties.mImageColor); mImage->setColorShift(mCurrentProperties.mImageColor);
mBackground.setCenterColor(mCurrentProperties.mBackgroundCenterColor); mBackground.setCenterColor(mCurrentProperties.mBackgroundCenterColor);
mBackground.setEdgeColor(mCurrentProperties.mBackgroundEdgeColor); mBackground.setEdgeColor(mCurrentProperties.mBackgroundEdgeColor);
resize(); resize();
} }
void applyThemeToProperties(const ThemeData::ThemeElement* elem, GridTileProperties* properties) void applyThemeToProperties(const ThemeData::ThemeElement* elem, GridTileProperties* properties)
{ {
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); Vector2f screen = Vector2f((float)Renderer::getScreenWidth(),
(float)Renderer::getScreenHeight());
if (elem->has("size")) if (elem->has("size"))
properties->mSize = elem->get<Vector2f>("size") * screen; properties->mSize = elem->get<Vector2f>("size") * screen;
if (elem->has("padding")) if (elem->has("padding"))
properties->mPadding = elem->get<Vector2f>("padding"); properties->mPadding = elem->get<Vector2f>("padding");
if (elem->has("imageColor")) if (elem->has("imageColor"))
properties->mImageColor = elem->get<unsigned int>("imageColor"); properties->mImageColor = elem->get<unsigned int>("imageColor");
if (elem->has("backgroundImage")) if (elem->has("backgroundImage"))
properties->mBackgroundImage = elem->get<std::string>("backgroundImage"); properties->mBackgroundImage = elem->get<std::string>("backgroundImage");
if (elem->has("backgroundCornerSize")) if (elem->has("backgroundCornerSize"))
properties->mBackgroundCornerSize = elem->get<Vector2f>("backgroundCornerSize"); properties->mBackgroundCornerSize = elem->get<Vector2f>("backgroundCornerSize");
if (elem->has("backgroundColor")) if (elem->has("backgroundColor")) {
{ properties->mBackgroundCenterColor = elem->get<unsigned int>("backgroundColor");
properties->mBackgroundCenterColor = elem->get<unsigned int>("backgroundColor"); properties->mBackgroundEdgeColor = elem->get<unsigned int>("backgroundColor");
properties->mBackgroundEdgeColor = elem->get<unsigned int>("backgroundColor"); }
}
if (elem->has("backgroundCenterColor")) if (elem->has("backgroundCenterColor"))
properties->mBackgroundCenterColor = elem->get<unsigned int>("backgroundCenterColor"); properties->mBackgroundCenterColor = elem->get<unsigned int>("backgroundCenterColor");
if (elem->has("backgroundEdgeColor")) if (elem->has("backgroundEdgeColor"))
properties->mBackgroundEdgeColor = elem->get<unsigned int>("backgroundEdgeColor"); properties->mBackgroundEdgeColor = elem->get<unsigned int>("backgroundEdgeColor");
} }
void GridTileComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& /*element*/, unsigned int /*properties*/) void GridTileComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view, const std::string& /*element*/, unsigned int /*properties*/)
{ {
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); Vector2f screen = Vector2f((float)Renderer::getScreenWidth(),
(float)Renderer::getScreenHeight());
// Apply theme to the default gridtile // Apply theme to the default gridtile.
const ThemeData::ThemeElement* elem = theme->getElement(view, "default", "gridtile"); const ThemeData::ThemeElement* elem = theme->getElement(view, "default", "gridtile");
if (elem) if (elem)
applyThemeToProperties(elem, &mDefaultProperties); applyThemeToProperties(elem, &mDefaultProperties);
// Apply theme to the selected gridtile // Apply theme to the selected gridtile. Note that some of the default gridtile
// NOTE that some of the default gridtile properties influence on the selected gridtile properties // properties have influence on the selected gridtile properties.
// See THEMES.md for more informations // See THEMES.md for more informations.
elem = theme->getElement(view, "selected", "gridtile"); elem = theme->getElement(view, "selected", "gridtile");
mSelectedProperties.mSize = getSelectedTileSize(); mSelectedProperties.mSize = getSelectedTileSize();
mSelectedProperties.mPadding = mDefaultProperties.mPadding; mSelectedProperties.mPadding = mDefaultProperties.mPadding;
mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage; mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage;
mSelectedProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize; mSelectedProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize;
if (elem) if (elem)
applyThemeToProperties(elem, &mSelectedProperties); applyThemeToProperties(elem, &mSelectedProperties);
} }
// Made this a static function because the ImageGridComponent need to know the default tile size // Made this a static function because the ImageGridComponent needs to know the default tile
// to calculate the grid dimension before it instantiate the GridTileComponents // max size to calculate the grid dimension before it instantiates the GridTileComponents.
Vector2f GridTileComponent::getDefaultTileSize() Vector2f GridTileComponent::getDefaultTileSize()
{ {
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); Vector2f screen = Vector2f((float)Renderer::getScreenWidth(),
(float)Renderer::getScreenHeight());
return screen * 0.22f; return screen * 0.22f;
} }
Vector2f GridTileComponent::getSelectedTileSize() const Vector2f GridTileComponent::getSelectedTileSize() const
{ {
return mDefaultProperties.mSize * 1.2f; return mDefaultProperties.mSize * 1.2f;
} }
bool GridTileComponent::isSelected() const bool GridTileComponent::isSelected() const
{ {
return mSelected; return mSelected;
} }
void GridTileComponent::reset() void GridTileComponent::reset()
{ {
setImage(""); setImage("");
} }
void GridTileComponent::setImage(const std::string& path) void GridTileComponent::setImage(const std::string& path)
{ {
mImage->setImage(path); mImage->setImage(path);
// Resize now to prevent flickering images when scrolling // Resize now to prevent flickering images when scrolling.
resize(); resize();
} }
void GridTileComponent::setImage(const std::shared_ptr<TextureResource>& texture) void GridTileComponent::setImage(const std::shared_ptr<TextureResource>& texture)
{ {
mImage->setImage(texture); mImage->setImage(texture);
// Resize now to prevent flickering images when scrolling // Resize now to prevent flickering images when scrolling.
resize(); resize();
} }
void GridTileComponent::setSelected(bool selected, bool allowAnimation, Vector3f* pPosition, bool force) void GridTileComponent::setSelected(bool selected, bool allowAnimation,
Vector3f* pPosition, bool force)
{ {
if (mSelected == selected && !force) if (mSelected == selected && !force)
{ return;
return;
}
mSelected = selected; mSelected = selected;
if (selected) if (selected) {
{ if (pPosition == nullptr || !allowAnimation) {
if (pPosition == NULL || !allowAnimation) cancelAnimation(3);
{
cancelAnimation(3);
this->setSelectedZoom(1); this->setSelectedZoom(1);
mAnimPosition = Vector3f(0, 0, 0); mAnimPosition = Vector3f(0, 0, 0);
resize(); resize();
} }
else else {
{ mAnimPosition = Vector3f(pPosition->x(), pPosition->y(), pPosition->z());
mAnimPosition = Vector3f(pPosition->x(), pPosition->y(), pPosition->z());
auto func = [this](float t) auto func = [this](float t) {
{ t -= 1; // Cubic ease out.
t -= 1; // cubic ease out float pct = Math::lerp(0, 1, t*t*t + 1);
float pct = Math::lerp(0, 1, t*t*t + 1); this->setSelectedZoom(pct);
};
this->setSelectedZoom(pct); cancelAnimation(3);
}; setAnimation(new LambdaAnimation(func, 250), 0, [this] {
this->setSelectedZoom(1);
mAnimPosition = Vector3f(0, 0, 0);
}, false, 3);
}
}
// If (!selected).
else {
if (!allowAnimation) {
cancelAnimation(3);
this->setSelectedZoom(0);
cancelAnimation(3); resize();
setAnimation(new LambdaAnimation(func, 250), 0, [this] { }
this->setSelectedZoom(1); else {
mAnimPosition = Vector3f(0, 0, 0); this->setSelectedZoom(1);
}, false, 3);
}
}
else // if (!selected)
{
if (!allowAnimation)
{
cancelAnimation(3);
this->setSelectedZoom(0);
resize(); auto func = [this](float t) {
} t -= 1; // Cubic ease out.
else float pct = Math::lerp(0, 1, t*t*t + 1);
{ this->setSelectedZoom(1.0 - pct);
this->setSelectedZoom(1); };
auto func = [this](float t) cancelAnimation(3);
{ setAnimation(new LambdaAnimation(func, 250), 0, [this] {
t -= 1; // cubic ease out this->setSelectedZoom(0);
float pct = Math::lerp(0, 1, t*t*t + 1); }, false, 3);
this->setSelectedZoom(1.0 - pct); }
}; }
cancelAnimation(3);
setAnimation(new LambdaAnimation(func, 250), 0, [this] {
this->setSelectedZoom(0);
}, false, 3);
}
}
} }
void GridTileComponent::setSelectedZoom(float percent) void GridTileComponent::setSelectedZoom(float percent)
{ {
if (mSelectedZoomPercent == percent) if (mSelectedZoomPercent == percent)
return; return;
mSelectedZoomPercent = percent; mSelectedZoomPercent = percent;
resize(); resize();
} }
void GridTileComponent::setVisible(bool visible) void GridTileComponent::setVisible(bool visible)
{ {
mVisible = visible; mVisible = visible;
} }
void GridTileComponent::resize() void GridTileComponent::resize()
{ {
calcCurrentProperties(); calcCurrentProperties();
mImage->setMaxSize(mCurrentProperties.mSize - mCurrentProperties.mPadding * 2); mImage->setMaxSize(mCurrentProperties.mSize - mCurrentProperties.mPadding * 2);
mBackground.setCornerSize(mCurrentProperties.mBackgroundCornerSize); mBackground.setCornerSize(mCurrentProperties.mBackgroundCornerSize);
mBackground.fitTo(mCurrentProperties.mSize - mBackground.getCornerSize() * 2); mBackground.fitTo(mCurrentProperties.mSize - mBackground.getCornerSize() * 2);
} }
unsigned int mixColors(unsigned int first, unsigned int second, float percent) unsigned int mixColors(unsigned int first, unsigned int second, float percent)
{ {
unsigned char alpha0 = (first >> 24) & 0xFF; unsigned char alpha0 = (first >> 24) & 0xFF;
unsigned char blue0 = (first >> 16) & 0xFF; unsigned char blue0 = (first >> 16) & 0xFF;
unsigned char green0 = (first >> 8) & 0xFF; unsigned char green0 = (first >> 8) & 0xFF;
unsigned char red0 = first & 0xFF; unsigned char red0 = first & 0xFF;
unsigned char alpha1 = (second >> 24) & 0xFF; unsigned char alpha1 = (second >> 24) & 0xFF;
unsigned char blue1 = (second >> 16) & 0xFF; unsigned char blue1 = (second >> 16) & 0xFF;
unsigned char green1 = (second >> 8) & 0xFF; unsigned char green1 = (second >> 8) & 0xFF;
unsigned char red1 = second & 0xFF; unsigned char red1 = second & 0xFF;
unsigned char alpha = (unsigned char)(alpha0 * (1.0 - percent) + alpha1 * percent); unsigned char alpha = (unsigned char)(alpha0 * (1.0 - percent) + alpha1 * percent);
unsigned char blue = (unsigned char)(blue0 * (1.0 - percent) + blue1 * percent); unsigned char blue = (unsigned char)(blue0 * (1.0 - percent) + blue1 * percent);
unsigned char green = (unsigned char)(green0 * (1.0 - percent) + green1 * percent); unsigned char green = (unsigned char)(green0 * (1.0 - percent) + green1 * percent);
unsigned char red = (unsigned char)(red0 * (1.0 - percent) + red1 * percent); unsigned char red = (unsigned char)(red0 * (1.0 - percent) + red1 * percent);
return (alpha << 24) | (blue << 16) | (green << 8) | red; return (alpha << 24) | (blue << 16) | (green << 8) | red;
} }
void GridTileComponent::calcCurrentProperties() void GridTileComponent::calcCurrentProperties()
{ {
mCurrentProperties = mSelected ? mSelectedProperties : mDefaultProperties; mCurrentProperties = mSelected ? mSelectedProperties : mDefaultProperties;
float zoomPercentInverse = 1.0 - mSelectedZoomPercent; float zoomPercentInverse = 1.0 - mSelectedZoomPercent;
if (mSelectedZoomPercent != 0.0f && mSelectedZoomPercent != 1.0f) { if (mSelectedZoomPercent != 0.0f && mSelectedZoomPercent != 1.0f) {
if (mDefaultProperties.mSize != mSelectedProperties.mSize) { if (mDefaultProperties.mSize != mSelectedProperties.mSize)
mCurrentProperties.mSize = mDefaultProperties.mSize * zoomPercentInverse + mSelectedProperties.mSize * mSelectedZoomPercent; mCurrentProperties.mSize = mDefaultProperties.mSize * zoomPercentInverse +
} mSelectedProperties.mSize * mSelectedZoomPercent;
if (mDefaultProperties.mPadding != mSelectedProperties.mPadding) if (mDefaultProperties.mPadding != mSelectedProperties.mPadding)
{ mCurrentProperties.mPadding = mDefaultProperties.mPadding * zoomPercentInverse +
mCurrentProperties.mPadding = mDefaultProperties.mPadding * zoomPercentInverse + mSelectedProperties.mPadding * mSelectedZoomPercent; mSelectedProperties.mPadding * mSelectedZoomPercent;
}
if (mDefaultProperties.mImageColor != mSelectedProperties.mImageColor) if (mDefaultProperties.mImageColor != mSelectedProperties.mImageColor)
{ mCurrentProperties.mImageColor = mixColors(mDefaultProperties.mImageColor,
mCurrentProperties.mImageColor = mixColors(mDefaultProperties.mImageColor, mSelectedProperties.mImageColor, mSelectedZoomPercent); mSelectedProperties.mImageColor, mSelectedZoomPercent);
}
if (mDefaultProperties.mBackgroundCornerSize != mSelectedProperties.mBackgroundCornerSize) if (mDefaultProperties.mBackgroundCornerSize != mSelectedProperties.mBackgroundCornerSize)
{ mCurrentProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize *
mCurrentProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize * zoomPercentInverse + mSelectedProperties.mBackgroundCornerSize * mSelectedZoomPercent; zoomPercentInverse + mSelectedProperties.mBackgroundCornerSize *
} mSelectedZoomPercent;
if (mDefaultProperties.mBackgroundCenterColor != mSelectedProperties.mBackgroundCenterColor) if (mDefaultProperties.mBackgroundCenterColor != mSelectedProperties.mBackgroundCenterColor)
{ mCurrentProperties.mBackgroundCenterColor =
mCurrentProperties.mBackgroundCenterColor = mixColors(mDefaultProperties.mBackgroundCenterColor, mSelectedProperties.mBackgroundCenterColor, mSelectedZoomPercent); mixColors(mDefaultProperties.mBackgroundCenterColor,
} mSelectedProperties.mBackgroundCenterColor, mSelectedZoomPercent);
if (mDefaultProperties.mBackgroundEdgeColor != mSelectedProperties.mBackgroundEdgeColor) if (mDefaultProperties.mBackgroundEdgeColor != mSelectedProperties.mBackgroundEdgeColor)
{ mCurrentProperties.mBackgroundEdgeColor =
mCurrentProperties.mBackgroundEdgeColor = mixColors(mDefaultProperties.mBackgroundEdgeColor, mSelectedProperties.mBackgroundEdgeColor, mSelectedZoomPercent); mixColors(mDefaultProperties.mBackgroundEdgeColor,
} mSelectedProperties.mBackgroundEdgeColor, mSelectedZoomPercent);
} }
} }
Vector3f GridTileComponent::getBackgroundPosition() Vector3f GridTileComponent::getBackgroundPosition()
{ {
return mBackground.getPosition() + mPosition; return mBackground.getPosition() + mPosition;
} }
std::shared_ptr<TextureResource> GridTileComponent::getTexture() std::shared_ptr<TextureResource> GridTileComponent::getTexture()
{ {
if (mImage != nullptr) if (mImage != nullptr)
return mImage->getTexture(); return mImage->getTexture();
return nullptr; return nullptr;
}; };
void GridTileComponent::forceSize(Vector2f size, float selectedZoom) void GridTileComponent::forceSize(Vector2f size, float selectedZoom)
{ {
mDefaultProperties.mSize = size; mDefaultProperties.mSize = size;
mSelectedProperties.mSize = size * selectedZoom; mSelectedProperties.mSize = size * selectedZoom;
} }

View file

@ -1,3 +1,9 @@
//
// GridTileComponent.h
//
// X*Y grid.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H #ifndef ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H
#define ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H #define ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H
@ -5,63 +11,64 @@
#include "NinePatchComponent.h" #include "NinePatchComponent.h"
#include "ImageComponent.h" #include "ImageComponent.h"
struct GridTileProperties struct GridTileProperties {
{ Vector2f mSize;
Vector2f mSize; Vector2f mPadding;
Vector2f mPadding; unsigned int mImageColor;
unsigned int mImageColor; std::string mBackgroundImage;
std::string mBackgroundImage; Vector2f mBackgroundCornerSize;
Vector2f mBackgroundCornerSize; unsigned int mBackgroundCenterColor;
unsigned int mBackgroundCenterColor; unsigned int mBackgroundEdgeColor;
unsigned int mBackgroundEdgeColor;
}; };
class GridTileComponent : public GuiComponent class GridTileComponent : public GuiComponent
{ {
public: public:
GridTileComponent(Window* window); GridTileComponent(Window* window);
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override; virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties) override;
// Made this a static function because the ImageGridComponent need to know the default tile max size // Made this a static function because the ImageGridComponent needs to know the default tile
// to calculate the grid dimension before it instantiate the GridTileComponents // max size to calculate the grid dimension before it instantiates the GridTileComponents.
static Vector2f getDefaultTileSize(); static Vector2f getDefaultTileSize();
Vector2f getSelectedTileSize() const; Vector2f getSelectedTileSize() const;
bool isSelected() const; bool isSelected() const;
void reset(); void reset();
void setImage(const std::string& path); void setImage(const std::string& path);
void setImage(const std::shared_ptr<TextureResource>& texture); void setImage(const std::shared_ptr<TextureResource>& texture);
void setSelected(bool selected, bool allowAnimation = true, Vector3f* pPosition = NULL, bool force=false); void setSelected(bool selected, bool allowAnimation = true,
void setVisible(bool visible); Vector3f* pPosition = nullptr, bool force=false);
void setVisible(bool visible);
void forceSize(Vector2f size, float selectedZoom); void forceSize(Vector2f size, float selectedZoom);
Vector3f getBackgroundPosition(); Vector3f getBackgroundPosition();
virtual void update(int deltaTime) override; virtual void update(int deltaTime) override;
std::shared_ptr<TextureResource> getTexture(); std::shared_ptr<TextureResource> getTexture();
private: private:
void resize(); void resize();
void calcCurrentProperties(); void calcCurrentProperties();
void setSelectedZoom(float percent); void setSelectedZoom(float percent);
std::shared_ptr<ImageComponent> mImage; std::shared_ptr<ImageComponent> mImage;
NinePatchComponent mBackground; NinePatchComponent mBackground;
GridTileProperties mDefaultProperties; GridTileProperties mDefaultProperties;
GridTileProperties mSelectedProperties; GridTileProperties mSelectedProperties;
GridTileProperties mCurrentProperties; GridTileProperties mCurrentProperties;
float mSelectedZoomPercent; float mSelectedZoomPercent;
bool mSelected; bool mSelected;
bool mVisible; bool mVisible;
Vector3f mAnimPosition; Vector3f mAnimPosition;
}; };
#endif // ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H #endif // ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H

View file

@ -1,3 +1,9 @@
//
// HelpComponent.cpp
//
// Help information in icon and text pairs.
//
#include "components/HelpComponent.h" #include "components/HelpComponent.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
@ -8,25 +14,25 @@
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#define OFFSET_X 12 // move the entire thing right by this amount (px) #define OFFSET_X 12 // Move the entire thing right by this amount (px).
#define OFFSET_Y 12 // move the entire thing up by this amount (px) #define OFFSET_Y 12 // Move the entire thing up by this amount (px).
#define ICON_TEXT_SPACING 8 // space between [icon] and [text] (px) #define ICON_TEXT_SPACING 8 // Space between [icon] and [text] (px).
#define ENTRY_SPACING 16 // space between [text] and next [icon] (px) #define ENTRY_SPACING 16 // Space between [text] and next [icon] (px).
static const std::map<std::string, const char*> ICON_PATH_MAP { static const std::map<std::string, const char*> ICON_PATH_MAP {
{ "up/down", ":/help/dpad_updown.svg" }, { "up/down", ":/help/dpad_updown.svg" },
{ "left/right", ":/help/dpad_leftright.svg" }, { "left/right", ":/help/dpad_leftright.svg" },
{ "up/down/left/right", ":/help/dpad_all.svg" }, { "up/down/left/right", ":/help/dpad_all.svg" },
{ "a", ":/help/button_a.svg" }, { "a", ":/help/button_a.svg" },
{ "b", ":/help/button_b.svg" }, { "b", ":/help/button_b.svg" },
{ "x", ":/help/button_x.svg" }, { "x", ":/help/button_x.svg" },
{ "y", ":/help/button_y.svg" }, { "y", ":/help/button_y.svg" },
{ "l", ":/help/button_l.svg" }, { "l", ":/help/button_l.svg" },
{ "r", ":/help/button_r.svg" }, { "r", ":/help/button_r.svg" },
{ "lr", ":/help/button_lr.svg" }, { "lr", ":/help/button_lr.svg" },
{ "start", ":/help/button_start.svg" }, { "start", ":/help/button_start.svg" },
{ "select", ":/help/button_select.svg" } { "select", ":/help/button_select.svg" }
}; };
HelpComponent::HelpComponent(Window* window) : GuiComponent(window) HelpComponent::HelpComponent(Window* window) : GuiComponent(window)
@ -35,108 +41,106 @@ HelpComponent::HelpComponent(Window* window) : GuiComponent(window)
void HelpComponent::clearPrompts() void HelpComponent::clearPrompts()
{ {
mPrompts.clear(); mPrompts.clear();
updateGrid(); updateGrid();
} }
void HelpComponent::setPrompts(const std::vector<HelpPrompt>& prompts) void HelpComponent::setPrompts(const std::vector<HelpPrompt>& prompts)
{ {
mPrompts = prompts; mPrompts = prompts;
updateGrid(); updateGrid();
} }
void HelpComponent::setStyle(const HelpStyle& style) void HelpComponent::setStyle(const HelpStyle& style)
{ {
mStyle = style; mStyle = style;
updateGrid(); updateGrid();
} }
void HelpComponent::updateGrid() void HelpComponent::updateGrid()
{ {
if(!Settings::getInstance()->getBool("ShowHelpPrompts") || mPrompts.empty()) if (!Settings::getInstance()->getBool("ShowHelpPrompts") || mPrompts.empty()) {
{ mGrid.reset();
mGrid.reset(); return;
return; }
}
std::shared_ptr<Font>& font = mStyle.font; std::shared_ptr<Font>& font = mStyle.font;
mGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i((int)mPrompts.size() * 4, 1)); mGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i((int)mPrompts.size() * 4, 1));
// [icon] [spacer1] [text] [spacer2]
std::vector< std::shared_ptr<ImageComponent> > icons; // [icon] [spacer1] [text] [spacer2]
std::vector< std::shared_ptr<TextComponent> > labels;
float width = 0; std::vector< std::shared_ptr<ImageComponent> > icons;
const float height = Math::round(font->getLetterHeight() * 1.25f); std::vector< std::shared_ptr<TextComponent> > labels;
for(auto it = mPrompts.cbegin(); it != mPrompts.cend(); it++)
{
auto icon = std::make_shared<ImageComponent>(mWindow);
icon->setImage(getIconTexture(it->first.c_str()));
icon->setColorShift(mStyle.iconColor);
icon->setResize(0, height);
icons.push_back(icon);
auto lbl = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(it->second), font, mStyle.textColor); float width = 0;
labels.push_back(lbl); const float height = Math::round(font->getLetterHeight() * 1.25f);
width += icon->getSize().x() + lbl->getSize().x() + ICON_TEXT_SPACING + ENTRY_SPACING; for (auto it = mPrompts.cbegin(); it != mPrompts.cend(); it++) {
} auto icon = std::make_shared<ImageComponent>(mWindow);
icon->setImage(getIconTexture(it->first.c_str()));
icon->setColorShift(mStyle.iconColor);
icon->setResize(0, height);
icons.push_back(icon);
mGrid->setSize(width, height); auto lbl = std::make_shared<TextComponent>(mWindow,
for(unsigned int i = 0; i < icons.size(); i++) Utils::String::toUpper(it->second), font, mStyle.textColor);
{ labels.push_back(lbl);
const int col = i*4;
mGrid->setColWidthPerc(col, icons.at(i)->getSize().x() / width);
mGrid->setColWidthPerc(col + 1, ICON_TEXT_SPACING / width);
mGrid->setColWidthPerc(col + 2, labels.at(i)->getSize().x() / width);
mGrid->setEntry(icons.at(i), Vector2i(col, 0), false, false); width += icon->getSize().x() + lbl->getSize().x() + ICON_TEXT_SPACING + ENTRY_SPACING;
mGrid->setEntry(labels.at(i), Vector2i(col + 2, 0), false, false); }
}
mGrid->setPosition(Vector3f(mStyle.position.x(), mStyle.position.y(), 0.0f)); mGrid->setSize(width, height);
//mGrid->setPosition(OFFSET_X, Renderer::getScreenHeight() - mGrid->getSize().y() - OFFSET_Y);
mGrid->setOrigin(mStyle.origin); for (unsigned int i = 0; i < icons.size(); i++) {
const int col = i*4;
mGrid->setColWidthPerc(col, icons.at(i)->getSize().x() / width);
mGrid->setColWidthPerc(col + 1, ICON_TEXT_SPACING / width);
mGrid->setColWidthPerc(col + 2, labels.at(i)->getSize().x() / width);
mGrid->setEntry(icons.at(i), Vector2i(col, 0), false, false);
mGrid->setEntry(labels.at(i), Vector2i(col + 2, 0), false, false);
}
mGrid->setPosition(Vector3f(mStyle.position.x(), mStyle.position.y(), 0.0f));
//mGrid->setPosition(OFFSET_X, Renderer::getScreenHeight() - mGrid->getSize().y() - OFFSET_Y);
mGrid->setOrigin(mStyle.origin);
} }
std::shared_ptr<TextureResource> HelpComponent::getIconTexture(const char* name) std::shared_ptr<TextureResource> HelpComponent::getIconTexture(const char* name)
{ {
auto it = mIconCache.find(name); auto it = mIconCache.find(name);
if(it != mIconCache.cend()) if (it != mIconCache.cend())
return it->second; return it->second;
auto pathLookup = ICON_PATH_MAP.find(name); auto pathLookup = ICON_PATH_MAP.find(name);
if(pathLookup == ICON_PATH_MAP.cend()) if (pathLookup == ICON_PATH_MAP.cend()) {
{ LOG(LogError) << "Unknown help icon \"" << name << "\"!";
LOG(LogError) << "Unknown help icon \"" << name << "\"!"; return nullptr;
return nullptr; }
} if (!ResourceManager::getInstance()->fileExists(pathLookup->second)) {
if(!ResourceManager::getInstance()->fileExists(pathLookup->second)) LOG(LogError) << "Help icon \"" << name <<
{ "\" - corresponding image file \"" << pathLookup->second << "\" misisng!";
LOG(LogError) << "Help icon \"" << name << "\" - corresponding image file \"" << pathLookup->second << "\" misisng!"; return nullptr;
return nullptr; }
}
std::shared_ptr<TextureResource> tex = TextureResource::get(pathLookup->second); std::shared_ptr<TextureResource> tex = TextureResource::get(pathLookup->second);
mIconCache[std::string(name)] = tex; mIconCache[std::string(name)] = tex;
return tex; return tex;
} }
void HelpComponent::setOpacity(unsigned char opacity) void HelpComponent::setOpacity(unsigned char opacity)
{ {
GuiComponent::setOpacity(opacity); GuiComponent::setOpacity(opacity);
for(unsigned int i = 0; i < mGrid->getChildCount(); i++) for (unsigned int i = 0; i < mGrid->getChildCount(); i++)
{ mGrid->getChild(i)->setOpacity(opacity);
mGrid->getChild(i)->setOpacity(opacity);
}
} }
void HelpComponent::render(const Transform4x4f& parentTrans) void HelpComponent::render(const Transform4x4f& parentTrans)
{ {
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
if(mGrid) if (mGrid)
mGrid->render(trans); mGrid->render(trans);
} }

View file

@ -1,3 +1,9 @@
//
// HelpComponent.h
//
// Help information in icon and text pairs.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_HELP_COMPONENT_H #ifndef ES_CORE_COMPONENTS_HELP_COMPONENT_H
#define ES_CORE_COMPONENTS_HELP_COMPONENT_H #define ES_CORE_COMPONENTS_HELP_COMPONENT_H
@ -12,25 +18,25 @@ class TextureResource;
class HelpComponent : public GuiComponent class HelpComponent : public GuiComponent
{ {
public: public:
HelpComponent(Window* window); HelpComponent(Window* window);
void clearPrompts(); void clearPrompts();
void setPrompts(const std::vector<HelpPrompt>& prompts); void setPrompts(const std::vector<HelpPrompt>& prompts);
void render(const Transform4x4f& parent) override; void render(const Transform4x4f& parent) override;
void setOpacity(unsigned char opacity) override; void setOpacity(unsigned char opacity) override;
void setStyle(const HelpStyle& style); void setStyle(const HelpStyle& style);
private: private:
std::shared_ptr<TextureResource> getIconTexture(const char* name); std::shared_ptr<TextureResource> getIconTexture(const char* name);
std::map< std::string, std::shared_ptr<TextureResource> > mIconCache; std::map< std::string, std::shared_ptr<TextureResource> > mIconCache;
std::shared_ptr<ComponentGrid> mGrid; std::shared_ptr<ComponentGrid> mGrid;
void updateGrid(); void updateGrid();
std::vector<HelpPrompt> mPrompts; std::vector<HelpPrompt> mPrompts;
HelpStyle mStyle; HelpStyle mStyle;
}; };
#endif // ES_CORE_COMPONENTS_HELP_COMPONENT_H #endif // ES_CORE_COMPONENTS_HELP_COMPONENT_H

View file

@ -1,3 +1,9 @@
//
// IList.h
//
// Gamelist base class.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_ILIST_H #ifndef ES_CORE_COMPONENTS_ILIST_H
#define ES_CORE_COMPONENTS_ILIST_H #define ES_CORE_COMPONENTS_ILIST_H
@ -6,334 +12,341 @@
#include "resources/Font.h" #include "resources/Font.h"
#include "PowerSaver.h" #include "PowerSaver.h"
enum CursorState enum CursorState {
{ CURSOR_STOPPED,
CURSOR_STOPPED, CURSOR_SCROLLING
CURSOR_SCROLLING
}; };
enum ListLoopType enum ListLoopType {
{ LIST_ALWAYS_LOOP,
LIST_ALWAYS_LOOP, LIST_PAUSE_AT_END,
LIST_PAUSE_AT_END, LIST_NEVER_LOOP
LIST_NEVER_LOOP
}; };
struct ScrollTier struct ScrollTier {
{ int length; // How long we stay on this tier before going to the next.
int length; // how long we stay on this level before going to the next int scrollDelay; // How long between scrolls.
int scrollDelay; // how long between scrolls
}; };
struct ScrollTierList struct ScrollTierList {
{ const int count;
const int count; const ScrollTier* tiers;
const ScrollTier* tiers;
}; };
// default scroll tiers // Default scroll tiers.
const ScrollTier QUICK_SCROLL_TIERS[] = { const ScrollTier QUICK_SCROLL_TIERS[] = {
{500, 500}, {500, 500},
{2000, 114}, {2000, 114},
{4000, 32}, {4000, 32},
{0, 16} {0, 16}
};
const ScrollTierList LIST_SCROLL_STYLE_QUICK = {
4,
QUICK_SCROLL_TIERS
}; };
const ScrollTierList LIST_SCROLL_STYLE_QUICK = { 4, QUICK_SCROLL_TIERS };
const ScrollTier SLOW_SCROLL_TIERS[] = { const ScrollTier SLOW_SCROLL_TIERS[] = {
{500, 500}, {500, 500},
{0, 200} {0, 200}
}; };
const ScrollTierList LIST_SCROLL_STYLE_SLOW = { 2, SLOW_SCROLL_TIERS }; const ScrollTierList LIST_SCROLL_STYLE_SLOW = { 2, SLOW_SCROLL_TIERS };
template <typename EntryData, typename UserData> template <typename EntryData, typename UserData>
class IList : public GuiComponent class IList : public GuiComponent
{ {
public: public:
struct Entry struct Entry {
{ std::string name;
std::string name; UserData object;
UserData object; EntryData data;
EntryData data; };
};
protected: protected:
int mCursor; int mCursor;
int mScrollTier; int mScrollTier;
int mScrollVelocity; int mScrollVelocity;
int mScrollTierAccumulator; int mScrollTierAccumulator;
int mScrollCursorAccumulator; int mScrollCursorAccumulator;
unsigned char mTitleOverlayOpacity; unsigned char mTitleOverlayOpacity;
unsigned int mTitleOverlayColor; unsigned int mTitleOverlayColor;
ImageComponent mGradient; ImageComponent mGradient;
std::shared_ptr<Font> mTitleOverlayFont; std::shared_ptr<Font> mTitleOverlayFont;
const ScrollTierList& mTierList; const ScrollTierList& mTierList;
const ListLoopType mLoopType; const ListLoopType mLoopType;
std::vector<Entry> mEntries; std::vector<Entry> mEntries;
public: public:
IList(Window* window, const ScrollTierList& tierList = LIST_SCROLL_STYLE_QUICK, const ListLoopType& loopType = LIST_PAUSE_AT_END) : GuiComponent(window), IList(
mGradient(window), mTierList(tierList), mLoopType(loopType) Window* window,
{ const ScrollTierList& tierList = LIST_SCROLL_STYLE_QUICK,
mCursor = 0; const ListLoopType& loopType = LIST_PAUSE_AT_END)
mScrollTier = 0; : GuiComponent(window),
mScrollVelocity = 0; mGradient(window),
mScrollTierAccumulator = 0; mTierList(tierList),
mScrollCursorAccumulator = 0; mLoopType(loopType)
{
mCursor = 0;
mScrollTier = 0;
mScrollVelocity = 0;
mScrollTierAccumulator = 0;
mScrollCursorAccumulator = 0;
mTitleOverlayOpacity = 0x00; mTitleOverlayOpacity = 0x00;
mTitleOverlayColor = 0xFFFFFF00; mTitleOverlayColor = 0xFFFFFF00;
mGradient.setResize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); mGradient.setResize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
mGradient.setImage(":/graphics/scroll_gradient.png"); mGradient.setImage(":/graphics/scroll_gradient.png");
mTitleOverlayFont = Font::get(FONT_SIZE_LARGE); mTitleOverlayFont = Font::get(FONT_SIZE_LARGE);
} }
bool isScrolling() const bool isScrolling() const
{ {
return (mScrollVelocity != 0 && mScrollTier > 0); return (mScrollVelocity != 0 && mScrollTier > 0);
} }
int getScrollingVelocity() int getScrollingVelocity()
{ {
return mScrollVelocity; return mScrollVelocity;
} }
void stopScrolling() void stopScrolling()
{ {
listInput(0); listInput(0);
onCursorChanged(CURSOR_STOPPED); onCursorChanged(CURSOR_STOPPED);
} }
void clear() void clear()
{ {
mEntries.clear(); mEntries.clear();
mCursor = 0; mCursor = 0;
listInput(0); listInput(0);
onCursorChanged(CURSOR_STOPPED); onCursorChanged(CURSOR_STOPPED);
} }
inline const std::string& getSelectedName() inline const std::string& getSelectedName()
{ {
assert(size() > 0); assert(size() > 0);
return mEntries.at(mCursor).name; return mEntries.at(mCursor).name;
} }
inline const UserData& getSelected() const inline const UserData& getSelected() const
{ {
assert(size() > 0); assert(size() > 0);
return mEntries.at(mCursor).object; return mEntries.at(mCursor).object;
} }
inline const UserData& getFirst() const inline const UserData& getFirst() const
{ {
assert(size() > 0); assert(size() > 0);
return mEntries.front().object; return mEntries.front().object;
} }
inline const UserData& getLast() const inline const UserData& getLast() const
{ {
assert(size() > 0); assert(size() > 0);
return mEntries.back().object; return mEntries.back().object;
} }
void setCursor(typename std::vector<Entry>::const_iterator& it) void setCursor(typename std::vector<Entry>::const_iterator& it)
{ {
assert(it != mEntries.cend()); assert(it != mEntries.cend());
mCursor = it - mEntries.cbegin(); mCursor = it - mEntries.cbegin();
onCursorChanged(CURSOR_STOPPED); onCursorChanged(CURSOR_STOPPED);
} }
// returns true if successful (select is in our list), false if not // Returns true if successful (select is in our list), false if not.
bool setCursor(const UserData& obj) bool setCursor(const UserData& obj)
{ {
for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++) for (auto it = mEntries.cbegin(); it != mEntries.cend(); it++) {
{ if ((*it).object == obj) {
if((*it).object == obj) mCursor = (int)(it - mEntries.cbegin());
{ onCursorChanged(CURSOR_STOPPED);
mCursor = (int)(it - mEntries.cbegin()); return true;
onCursorChanged(CURSOR_STOPPED); }
return true; }
}
}
return false; return false;
} }
// entry management // Entry management.
void add(const Entry& e) void add(const Entry& e)
{ {
mEntries.push_back(e); mEntries.push_back(e);
} }
bool remove(const UserData& obj) bool remove(const UserData& obj)
{ {
for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++) for (auto it = mEntries.cbegin(); it != mEntries.cend(); it++) {
{ if ((*it).object == obj) {
if((*it).object == obj) remove(it);
{ return true;
remove(it); }
return true; }
}
}
return false; return false;
} }
inline int size() const { return (int)mEntries.size(); } inline int size() const { return (int)mEntries.size(); }
protected: protected:
void remove(typename std::vector<Entry>::const_iterator& it) void remove(typename std::vector<Entry>::const_iterator& it)
{ {
if(mCursor > 0 && it - mEntries.cbegin() <= mCursor) if (mCursor > 0 && it - mEntries.cbegin() <= mCursor) {
{ mCursor--;
mCursor--; onCursorChanged(CURSOR_STOPPED);
onCursorChanged(CURSOR_STOPPED); }
}
mEntries.erase(it); mEntries.erase(it);
} }
bool listFirstRow() bool listFirstRow()
{ {
mCursor = 0; mCursor = 0;
onCursorChanged(CURSOR_STOPPED); onCursorChanged(CURSOR_STOPPED);
onScroll(); onScroll();
return true; return true;
} }
bool listLastRow() bool listLastRow()
{ {
mCursor = mEntries.size() - 1; mCursor = mEntries.size() - 1;
onCursorChanged(CURSOR_STOPPED); onCursorChanged(CURSOR_STOPPED);
onScroll(); onScroll();
return true; return true;
} }
bool listInput(int velocity) // a velocity of 0 = stop scrolling
{
PowerSaver::setState(velocity == 0);
// generate an onCursorChanged event in the stopped state when the user lets go of the key bool listInput(int velocity) // A velocity of 0 = stop scrolling.
if(velocity == 0 && mScrollVelocity != 0) {
onCursorChanged(CURSOR_STOPPED); PowerSaver::setState(velocity == 0);
mScrollVelocity = velocity; // Generate an onCursorChanged event in the stopped state when the user
mScrollTier = 0; // lets go of the key.
mScrollTierAccumulator = 0; if (velocity == 0 && mScrollVelocity != 0)
mScrollCursorAccumulator = 0; onCursorChanged(CURSOR_STOPPED);
int prevCursor = mCursor; mScrollVelocity = velocity;
scroll(mScrollVelocity); mScrollTier = 0;
return (prevCursor != mCursor); mScrollTierAccumulator = 0;
} mScrollCursorAccumulator = 0;
void listUpdate(int deltaTime) int prevCursor = mCursor;
{ scroll(mScrollVelocity);
// update the title overlay opacity return (prevCursor != mCursor);
const int dir = (mScrollTier >= mTierList.count - 1) ? 1 : -1; // fade in if scroll tier is >= 1, otherwise fade out }
int op = mTitleOverlayOpacity + deltaTime*dir; // we just do a 1-to-1 time -> opacity, no scaling
if(op >= 255)
mTitleOverlayOpacity = 255;
else if(op <= 0)
mTitleOverlayOpacity = 0;
else
mTitleOverlayOpacity = (unsigned char)op;
if(mScrollVelocity == 0 || size() < 2) void listUpdate(int deltaTime)
return; {
// Update the title overlay opacity.
// Fade in if scroll tier is >= 1, otherwise fade out.
const int dir = (mScrollTier >= mTierList.count - 1) ? 1 : -1;
// We just do a 1-to-1 time -> opacity, no scaling.
int op = mTitleOverlayOpacity + deltaTime*dir;
if (op >= 255)
mTitleOverlayOpacity = 255;
else if (op <= 0)
mTitleOverlayOpacity = 0;
else
mTitleOverlayOpacity = (unsigned char)op;
mScrollCursorAccumulator += deltaTime; if (mScrollVelocity == 0 || size() < 2)
mScrollTierAccumulator += deltaTime; return;
// we delay scrolling until after scroll tier has updated so isScrolling() returns accurately during onCursorChanged callbacks mScrollCursorAccumulator += deltaTime;
// we don't just do scroll tier first because it would not catch the scrollDelay == tier length case mScrollTierAccumulator += deltaTime;
int scrollCount = 0;
while(mScrollCursorAccumulator >= mTierList.tiers[mScrollTier].scrollDelay)
{
mScrollCursorAccumulator -= mTierList.tiers[mScrollTier].scrollDelay;
scrollCount++;
}
// are we ready to go even FASTER? // We delay scrolling until after scroll tier has updated so isScrolling() returns
while(mScrollTier < mTierList.count - 1 && mScrollTierAccumulator >= mTierList.tiers[mScrollTier].length) // accurately during onCursorChanged callbacks. We don't just do scroll tier first
{ // because it would not catch the scrollDelay == tier length case.
mScrollTierAccumulator -= mTierList.tiers[mScrollTier].length; int scrollCount = 0;
mScrollTier++; while (mScrollCursorAccumulator >= mTierList.tiers[mScrollTier].scrollDelay) {
} mScrollCursorAccumulator -= mTierList.tiers[mScrollTier].scrollDelay;
scrollCount++;
}
// actually perform the scrolling // Are we ready to go even FASTER?
for(int i = 0; i < scrollCount; i++) while (mScrollTier < mTierList.count - 1 && mScrollTierAccumulator >=
scroll(mScrollVelocity); mTierList.tiers[mScrollTier].length) {
} mScrollTierAccumulator -= mTierList.tiers[mScrollTier].length;
mScrollTier++;
}
void listRenderTitleOverlay(const Transform4x4f& /*trans*/) // Actually perform the scrolling.
{ for (int i = 0; i < scrollCount; i++)
if(size() == 0 || !mTitleOverlayFont || mTitleOverlayOpacity == 0) scroll(mScrollVelocity);
return; }
// we don't bother caching this because it's only two letters and will change pretty much every frame if we're scrolling void listRenderTitleOverlay(const Transform4x4f& /*trans*/)
const std::string text = getSelectedName().size() >= 2 ? getSelectedName().substr(0, 2) : "??"; {
if (size() == 0 || !mTitleOverlayFont || mTitleOverlayOpacity == 0)
return;
Vector2f off = mTitleOverlayFont->sizeText(text); // We don't bother caching this because it's only two letters and will change pretty
off[0] = (Renderer::getScreenWidth() - off.x()) * 0.5f; // much every frame if we're scrolling.
off[1] = (Renderer::getScreenHeight() - off.y()) * 0.5f; const std::string text = getSelectedName().size() >= 2 ?
getSelectedName().substr(0, 2) : "??";
Transform4x4f identTrans = Transform4x4f::Identity(); Vector2f off = mTitleOverlayFont->sizeText(text);
off[0] = (Renderer::getScreenWidth() - off.x()) * 0.5f;
off[1] = (Renderer::getScreenHeight() - off.y()) * 0.5f;
mGradient.setOpacity(mTitleOverlayOpacity); Transform4x4f identTrans = Transform4x4f::Identity();
mGradient.render(identTrans);
TextCache* cache = mTitleOverlayFont->buildTextCache(text, off.x(), off.y(), 0xFFFFFF00 | mTitleOverlayOpacity); mGradient.setOpacity(mTitleOverlayOpacity);
mTitleOverlayFont->renderTextCache(cache); // relies on mGradient's render for Renderer::setMatrix() mGradient.render(identTrans);
delete cache;
}
void scroll(int amt) TextCache* cache = mTitleOverlayFont->buildTextCache(text, off.x(), off.y(),
{ 0xFFFFFF00 | mTitleOverlayOpacity);
if(mScrollVelocity == 0 || size() < 2) // Relies on mGradient's render for Renderer::setMatrix()
return; mTitleOverlayFont->renderTextCache(cache);
delete cache;
}
int cursor = mCursor + amt; void scroll(int amt)
int absAmt = amt < 0 ? -amt : amt; {
if (mScrollVelocity == 0 || size() < 2)
return;
// stop at the end if we've been holding down the button for a long time or int cursor = mCursor + amt;
// we're scrolling faster than one item at a time (e.g. page up/down) int absAmt = amt < 0 ? -amt : amt;
// otherwise, loop around
if((mLoopType == LIST_PAUSE_AT_END && (mScrollTier > 0 || absAmt > 1)) ||
mLoopType == LIST_NEVER_LOOP)
{
if(cursor < 0)
{
cursor = 0;
mScrollVelocity = 0;
mScrollTier = 0;
}else if(cursor >= size())
{
cursor = size() - 1;
mScrollVelocity = 0;
mScrollTier = 0;
}
}else{
while(cursor < 0)
cursor += size();
while(cursor >= size())
cursor -= size();
}
if(cursor != mCursor) // Stop at the end if we've been holding down the button for a long time or
onScroll(); // we're scrolling faster than one item at a time (e.g. page up/down).
// Otherwise, loop around.
if ((mLoopType == LIST_PAUSE_AT_END && (mScrollTier > 0 || absAmt > 1)) ||
mLoopType == LIST_NEVER_LOOP) {
if (cursor < 0) {
cursor = 0;
mScrollVelocity = 0;
mScrollTier = 0;
}
else if (cursor >= size()) {
cursor = size() - 1;
mScrollVelocity = 0;
mScrollTier = 0;
}
}
else {
while (cursor < 0)
cursor += size();
while (cursor >= size())
cursor -= size();
}
mCursor = cursor; if (cursor != mCursor)
onCursorChanged((mScrollTier > 0) ? CURSOR_SCROLLING : CURSOR_STOPPED); onScroll();
}
virtual void onCursorChanged(const CursorState& /*state*/) {} mCursor = cursor;
virtual void onScroll() {} onCursorChanged((mScrollTier > 0) ? CURSOR_SCROLLING : CURSOR_STOPPED);
}
virtual void onCursorChanged(const CursorState& /*state*/) {}
virtual void onScroll() {}
}; };
#endif // ES_CORE_COMPONENTS_ILIST_H #endif // ES_CORE_COMPONENTS_ILIST_H

View file

@ -1,3 +1,9 @@
//
// ImageComponent.cpp
//
// Handles images: loading, resizing, cropping, color shifting etc.
//
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "resources/TextureResource.h" #include "resources/TextureResource.h"
@ -7,23 +13,39 @@
Vector2i ImageComponent::getTextureSize() const Vector2i ImageComponent::getTextureSize() const
{ {
if(mTexture) if (mTexture)
return mTexture->getSize(); return mTexture->getSize();
else else
return Vector2i::Zero(); return Vector2i::Zero();
} }
Vector2f ImageComponent::getSize() const Vector2f ImageComponent::getSize() const
{ {
return GuiComponent::getSize() * (mBottomRightCrop - mTopLeftCrop); return GuiComponent::getSize() * (mBottomRightCrop - mTopLeftCrop);
} }
ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic) : GuiComponent(window), ImageComponent::ImageComponent(
mTargetIsMax(false), mTargetIsMin(false), mFlipX(false), mFlipY(false), mTargetSize(0, 0), mColorShift(0xFFFFFFFF), Window* window,
mColorShiftEnd(0xFFFFFFFF), mColorGradientHorizontal(true), mForceLoad(forceLoad), mDynamic(dynamic), bool forceLoad,
mFadeOpacity(0), mFading(false), mRotateByTargetSize(false), mTopLeftCrop(0.0f, 0.0f), mBottomRightCrop(1.0f, 1.0f) bool dynamic)
: GuiComponent(window),
mTargetIsMax(false),
mTargetIsMin(false),
mFlipX(false),
mFlipY(false),
mTargetSize(0, 0),
mColorShift(0xFFFFFFFF),
mColorShiftEnd(0xFFFFFFFF),
mColorGradientHorizontal(true),
mForceLoad(forceLoad),
mDynamic(dynamic),
mFadeOpacity(0),
mFading(false),
mRotateByTargetSize(false),
mTopLeftCrop(0.0f, 0.0f),
mBottomRightCrop(1.0f, 1.0f)
{ {
updateColors(); updateColors();
} }
ImageComponent::~ImageComponent() ImageComponent::~ImageComponent()
@ -32,412 +54,406 @@ ImageComponent::~ImageComponent()
void ImageComponent::resize() void ImageComponent::resize()
{ {
if(!mTexture) if (!mTexture)
return; return;
const Vector2f textureSize = mTexture->getSourceImageSize(); const Vector2f textureSize = mTexture->getSourceImageSize();
if(textureSize == Vector2f::Zero()) if (textureSize == Vector2f::Zero())
return; return;
if(mTexture->isTiled()) if (mTexture->isTiled()) {
{ mSize = mTargetSize;
mSize = mTargetSize; }
}else{ else {
// SVG rasterization is determined by height (see SVGResource.cpp), and rasterization is done in terms of pixels // SVG rasterization is determined by height and rasterization is done in terms of pixels.
// if rounding is off enough in the rasterization step (for images with extreme aspect ratios), it can cause cutoff when the aspect ratio breaks // If rounding is off enough in the rasterization step (for images with extreme aspect
// so, we always make sure the resultant height is an integer to make sure cutoff doesn't happen, and scale width from that // ratios), it can cause cutoff when the aspect ratio breaks.
// (you'll see this scattered throughout the function) // So we always make sure the resultant height is an integer to make sure cutoff doesn't
// this is probably not the best way, so if you're familiar with this problem and have a better solution, please make a pull request! // happen, and scale width from that (you'll see this scattered throughout the function).
// This is probably not the best way, so if you're familiar with this problem and have a
// better solution, please make a pull request!
if (mTargetIsMax) {
mSize = textureSize;
if(mTargetIsMax) Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y()));
{
mSize = textureSize;
Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y())); if (resizeScale.x() < resizeScale.y()) {
// This will be mTargetSize.x(). We can't exceed it, nor be lower than it.
mSize[0] *= resizeScale.x();
// We need to make sure we're not creating an image larger than max size.
mSize[1] = Math::min(Math::round(mSize[1] *= resizeScale.x()), mTargetSize.y());
}
else {
// This will be mTargetSize.y(). We can't exceed it.
mSize[1] = Math::round(mSize[1] * resizeScale.y());
// For SVG rasterization, always calculate width from rounded height (see comment
// above). We need to make sure we're not creating an image larger than max size.
mSize[0] = Math::min((mSize[1] / textureSize.y()) * textureSize.x(),
mTargetSize.x());
}
}
else if (mTargetIsMin) {
mSize = textureSize;
if(resizeScale.x() < resizeScale.y()) Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y()));
{
mSize[0] *= resizeScale.x(); // this will be mTargetSize.x(). We can't exceed it, nor be lower than it.
// we need to make sure we're not creating an image larger than max size
mSize[1] = Math::min(Math::round(mSize[1] *= resizeScale.x()), mTargetSize.y());
}else{
mSize[1] = Math::round(mSize[1] * resizeScale.y()); // this will be mTargetSize.y(). We can't exceed it.
// for SVG rasterization, always calculate width from rounded height (see comment above) if (resizeScale.x() > resizeScale.y()) {
// we need to make sure we're not creating an image larger than max size mSize[0] *= resizeScale.x();
mSize[0] = Math::min((mSize[1] / textureSize.y()) * textureSize.x(), mTargetSize.x()); mSize[1] *= resizeScale.x();
}
}else if(mTargetIsMin)
{
mSize = textureSize;
Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y())); float cropPercent = (mSize.y() - mTargetSize.y()) / (mSize.y() * 2);
crop(0, cropPercent, 0, cropPercent);
}
else {
mSize[0] *= resizeScale.y();
mSize[1] *= resizeScale.y();
if(resizeScale.x() > resizeScale.y()) float cropPercent = (mSize.x() - mTargetSize.x()) / (mSize.x() * 2);
{ crop(cropPercent, 0, cropPercent, 0);
mSize[0] *= resizeScale.x(); }
mSize[1] *= resizeScale.x(); // For SVG rasterization, always calculate width from rounded height (see comment
// above). We need to make sure we're not creating an image smaller than min size.
mSize[1] = Math::max(Math::round(mSize[1]), mTargetSize.y());
mSize[0] = Math::max((mSize[1] / textureSize.y()) * textureSize.x(), mTargetSize.x());
float cropPercent = (mSize.y() - mTargetSize.y()) / (mSize.y() * 2); }
crop(0, cropPercent, 0, cropPercent); else {
}else{ // If both components are set, we just stretch.
mSize[0] *= resizeScale.y(); // If no components are set, we don't resize at all.
mSize[1] *= resizeScale.y(); mSize = mTargetSize == Vector2f::Zero() ? textureSize : mTargetSize;
float cropPercent = (mSize.x() - mTargetSize.x()) / (mSize.x() * 2); // If only one component is set, we resize in a way that maintains aspect ratio.
crop(cropPercent, 0, cropPercent, 0); // For SVG rasterization, we always calculate width from rounded height (see
} // comment above).
if (!mTargetSize.x() && mTargetSize.y()) {
mSize[1] = Math::round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
else if (mTargetSize.x() && !mTargetSize.y()) {
mSize[1] = Math::round((mTargetSize.x() / textureSize.x()) * textureSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
}
}
// for SVG rasterization, always calculate width from rounded height (see comment above) mSize[0] = Math::round(mSize.x());
// we need to make sure we're not creating an image smaller than min size mSize[1] = Math::round(mSize.y());
mSize[1] = Math::max(Math::round(mSize[1]), mTargetSize.y()); // mSize.y() should already be rounded.
mSize[0] = Math::max((mSize[1] / textureSize.y()) * textureSize.x(), mTargetSize.x()); mTexture->rasterizeAt((size_t)mSize.x(), (size_t)mSize.y());
}else{ onSizeChanged();
// if both components are set, we just stretch
// if no components are set, we don't resize at all
mSize = mTargetSize == Vector2f::Zero() ? textureSize : mTargetSize;
// if only one component is set, we resize in a way that maintains aspect ratio
// for SVG rasterization, we always calculate width from rounded height (see comment above)
if(!mTargetSize.x() && mTargetSize.y())
{
mSize[1] = Math::round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}else if(mTargetSize.x() && !mTargetSize.y())
{
mSize[1] = Math::round((mTargetSize.x() / textureSize.x()) * textureSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
}
}
mSize[0] = Math::round(mSize.x());
mSize[1] = Math::round(mSize.y());
// mSize.y() should already be rounded
mTexture->rasterizeAt((size_t)mSize.x(), (size_t)mSize.y());
onSizeChanged();
} }
void ImageComponent::onSizeChanged() void ImageComponent::onSizeChanged()
{ {
updateVertices(); updateVertices();
} }
void ImageComponent::setDefaultImage(std::string path) void ImageComponent::setDefaultImage(std::string path)
{ {
mDefaultPath = path; mDefaultPath = path;
} }
void ImageComponent::setImage(std::string path, bool tile) void ImageComponent::setImage(std::string path, bool tile)
{ {
if(path.empty() || !ResourceManager::getInstance()->fileExists(path)) if (path.empty() || !ResourceManager::getInstance()->fileExists(path)) {
{ if (mDefaultPath.empty() || !ResourceManager::getInstance()->fileExists(mDefaultPath))
if(mDefaultPath.empty() || !ResourceManager::getInstance()->fileExists(mDefaultPath)) mTexture.reset();
mTexture.reset(); else
else mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic);
mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic); }
} else { else {
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic); mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic);
} }
resize(); resize();
} }
void ImageComponent::setImage(const char* path, size_t length, bool tile) void ImageComponent::setImage(const char* path, size_t length, bool tile)
{ {
mTexture.reset(); mTexture.reset();
mTexture = TextureResource::get("", tile); mTexture = TextureResource::get("", tile);
mTexture->initFromMemory(path, length); mTexture->initFromMemory(path, length);
resize(); resize();
} }
void ImageComponent::setImage(const std::shared_ptr<TextureResource>& texture) void ImageComponent::setImage(const std::shared_ptr<TextureResource>& texture)
{ {
mTexture = texture; mTexture = texture;
resize(); resize();
} }
void ImageComponent::setResize(float width, float height) void ImageComponent::setResize(float width, float height)
{ {
mTargetSize = Vector2f(width, height); mTargetSize = Vector2f(width, height);
mTargetIsMax = false; mTargetIsMax = false;
mTargetIsMin = false; mTargetIsMin = false;
resize(); resize();
} }
void ImageComponent::setMaxSize(float width, float height) void ImageComponent::setMaxSize(float width, float height)
{ {
mTargetSize = Vector2f(width, height); mTargetSize = Vector2f(width, height);
mTargetIsMax = true; mTargetIsMax = true;
mTargetIsMin = false; mTargetIsMin = false;
resize(); resize();
} }
void ImageComponent::setMinSize(float width, float height) void ImageComponent::setMinSize(float width, float height)
{ {
mTargetSize = Vector2f(width, height); mTargetSize = Vector2f(width, height);
mTargetIsMax = false; mTargetIsMax = false;
mTargetIsMin = true; mTargetIsMin = true;
resize(); resize();
} }
Vector2f ImageComponent::getRotationSize() const Vector2f ImageComponent::getRotationSize() const
{ {
return mRotateByTargetSize ? mTargetSize : mSize; return mRotateByTargetSize ? mTargetSize : mSize;
} }
void ImageComponent::setRotateByTargetSize(bool rotate) void ImageComponent::setRotateByTargetSize(bool rotate)
{ {
mRotateByTargetSize = rotate; mRotateByTargetSize = rotate;
} }
void ImageComponent::cropLeft(float percent) void ImageComponent::cropLeft(float percent)
{ {
assert(percent >= 0.0f && percent <= 1.0f); assert(percent >= 0.0f && percent <= 1.0f);
mTopLeftCrop.x() = percent; mTopLeftCrop.x() = percent;
} }
void ImageComponent::cropTop(float percent) void ImageComponent::cropTop(float percent)
{ {
assert(percent >= 0.0f && percent <= 1.0f); assert(percent >= 0.0f && percent <= 1.0f);
mTopLeftCrop.y() = percent; mTopLeftCrop.y() = percent;
} }
void ImageComponent::cropRight(float percent) void ImageComponent::cropRight(float percent)
{ {
assert(percent >= 0.0f && percent <= 1.0f); assert(percent >= 0.0f && percent <= 1.0f);
mBottomRightCrop.x() = 1.0f - percent; mBottomRightCrop.x() = 1.0f - percent;
} }
void ImageComponent::cropBot(float percent) void ImageComponent::cropBot(float percent)
{ {
assert(percent >= 0.0f && percent <= 1.0f); assert(percent >= 0.0f && percent <= 1.0f);
mBottomRightCrop.y() = 1.0f - percent; mBottomRightCrop.y() = 1.0f - percent;
} }
void ImageComponent::crop(float left, float top, float right, float bot) void ImageComponent::crop(float left, float top, float right, float bot)
{ {
cropLeft(left); cropLeft(left);
cropTop(top); cropTop(top);
cropRight(right); cropRight(right);
cropBot(bot); cropBot(bot);
} }
void ImageComponent::uncrop() void ImageComponent::uncrop()
{ {
crop(0, 0, 0, 0); crop(0, 0, 0, 0);
} }
void ImageComponent::setFlipX(bool flip) void ImageComponent::setFlipX(bool flip)
{ {
mFlipX = flip; mFlipX = flip;
updateVertices(); updateVertices();
} }
void ImageComponent::setFlipY(bool flip) void ImageComponent::setFlipY(bool flip)
{ {
mFlipY = flip; mFlipY = flip;
updateVertices(); updateVertices();
} }
void ImageComponent::setColorShift(unsigned int color) void ImageComponent::setColorShift(unsigned int color)
{ {
mColorShift = color; mColorShift = color;
mColorShiftEnd = color; mColorShiftEnd = color;
updateColors(); updateColors();
} }
void ImageComponent::setColorShiftEnd(unsigned int color) void ImageComponent::setColorShiftEnd(unsigned int color)
{ {
mColorShiftEnd = color; mColorShiftEnd = color;
updateColors(); updateColors();
} }
void ImageComponent::setColorGradientHorizontal(bool horizontal) void ImageComponent::setColorGradientHorizontal(bool horizontal)
{ {
mColorGradientHorizontal = horizontal; mColorGradientHorizontal = horizontal;
updateColors(); updateColors();
} }
void ImageComponent::setOpacity(unsigned char opacity) void ImageComponent::setOpacity(unsigned char opacity)
{ {
mOpacity = opacity; mOpacity = opacity;
updateColors(); updateColors();
} }
void ImageComponent::updateVertices() void ImageComponent::updateVertices()
{ {
if(!mTexture || !mTexture->isInitialized()) if (!mTexture || !mTexture->isInitialized())
return; return;
// we go through this mess to make sure everything is properly rounded // We go through this mess to make sure everything is properly rounded.
// if we just round vertices at the end, edge cases occur near sizes of 0.5 // If we just round vertices at the end, edge cases occur near sizes of 0.5.
const Vector2f topLeft = { mSize * mTopLeftCrop }; const Vector2f topLeft = { mSize * mTopLeftCrop };
const Vector2f bottomRight = { mSize * mBottomRightCrop }; const Vector2f bottomRight = { mSize * mBottomRightCrop };
const float px = mTexture->isTiled() ? mSize.x() / getTextureSize().x() : 1.0f; const float px = mTexture->isTiled() ? mSize.x() / getTextureSize().x() : 1.0f;
const float py = mTexture->isTiled() ? mSize.y() / getTextureSize().y() : 1.0f; const float py = mTexture->isTiled() ? mSize.y() / getTextureSize().y() : 1.0f;
mVertices[0] = { { topLeft.x(), topLeft.y() }, { mTopLeftCrop.x(), py - mTopLeftCrop.y() }, 0 }; mVertices[0] = { { topLeft.x(), topLeft.y() }, { mTopLeftCrop.x(), py - mTopLeftCrop.y() }, 0 };
mVertices[1] = { { topLeft.x(), bottomRight.y() }, { mTopLeftCrop.x(), 1.0f - mBottomRightCrop.y() }, 0 }; mVertices[1] = { { topLeft.x(), bottomRight.y() }, { mTopLeftCrop.x(), 1.0f - mBottomRightCrop.y() }, 0 };
mVertices[2] = { { bottomRight.x(), topLeft.y() }, { mBottomRightCrop.x() * px, py - mTopLeftCrop.y() }, 0 }; mVertices[2] = { { bottomRight.x(), topLeft.y() }, { mBottomRightCrop.x() * px, py - mTopLeftCrop.y() }, 0 };
mVertices[3] = { { bottomRight.x(), bottomRight.y() }, { mBottomRightCrop.x() * px, 1.0f - mBottomRightCrop.y() }, 0 }; mVertices[3] = { { bottomRight.x(), bottomRight.y() }, { mBottomRightCrop.x() * px, 1.0f - mBottomRightCrop.y() }, 0 };
updateColors(); updateColors();
// round vertices // Round vertices.
for(int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
mVertices[i].pos.round(); mVertices[i].pos.round();
if(mFlipX) if (mFlipX) {
{ for (int i = 0; i < 4; ++i)
for(int i = 0; i < 4; ++i) mVertices[i].tex[0] = px - mVertices[i].tex[0];
mVertices[i].tex[0] = px - mVertices[i].tex[0]; }
}
if(mFlipY) if (mFlipY) {
{ for (int i = 0; i < 4; ++i)
for(int i = 0; i < 4; ++i) mVertices[i].tex[1] = py - mVertices[i].tex[1];
mVertices[i].tex[1] = py - mVertices[i].tex[1]; }
}
} }
void ImageComponent::updateColors() void ImageComponent::updateColors()
{ {
const float opacity = (mOpacity * (mFading ? mFadeOpacity / 255.0 : 1.0)) / 255.0; const float opacity = (mOpacity * (mFading ? mFadeOpacity / 255.0 : 1.0)) / 255.0;
const unsigned int color = Renderer::convertColor(mColorShift & 0xFFFFFF00 | (unsigned char)((mColorShift & 0xFF) * opacity)); const unsigned int color = Renderer::convertColor(mColorShift & 0xFFFFFF00 |
const unsigned int colorEnd = Renderer::convertColor(mColorShiftEnd & 0xFFFFFF00 | (unsigned char)((mColorShiftEnd & 0xFF) * opacity)); (unsigned char)((mColorShift & 0xFF) * opacity));
const unsigned int colorEnd = Renderer::convertColor(mColorShiftEnd & 0xFFFFFF00 |
(unsigned char)((mColorShiftEnd & 0xFF) * opacity));
mVertices[0].col = color; mVertices[0].col = color;
mVertices[1].col = mColorGradientHorizontal ? colorEnd : color; mVertices[1].col = mColorGradientHorizontal ? colorEnd : color;
mVertices[2].col = mColorGradientHorizontal ? color : colorEnd; mVertices[2].col = mColorGradientHorizontal ? color : colorEnd;
mVertices[3].col = colorEnd; mVertices[3].col = colorEnd;
} }
void ImageComponent::render(const Transform4x4f& parentTrans) void ImageComponent::render(const Transform4x4f& parentTrans)
{ {
if (!isVisible()) if (!isVisible())
return; return;
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
if(mTexture && mOpacity > 0) if (mTexture && mOpacity > 0) {
{ if (Settings::getInstance()->getBool("DebugImage")) {
if(Settings::getInstance()->getBool("DebugImage")) { Vector2f targetSizePos = (mTargetSize - mSize) * mOrigin * -1;
Vector2f targetSizePos = (mTargetSize - mSize) * mOrigin * -1; Renderer::drawRect(targetSizePos.x(), targetSizePos.y(), mTargetSize.x(),
Renderer::drawRect(targetSizePos.x(), targetSizePos.y(), mTargetSize.x(), mTargetSize.y(), 0xFF000033, 0xFF000033); mTargetSize.y(), 0xFF000033, 0xFF000033);
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000033, 0x00000033); Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000033, 0x00000033);
} }
if(mTexture->isInitialized()) if (mTexture->isInitialized()) {
{ // Actually draw the image.
// actually draw the image // The bind() function returns false if the texture is not currently loaded. A blank
// The bind() function returns false if the texture is not currently loaded. A blank // texture is bound in this case but we want to handle a fade so it doesn't just
// texture is bound in this case but we want to handle a fade so it doesn't just 'jump' in // 'jump' in when it finally loads.
// when it finally loads fadeIn(mTexture->bind());
fadeIn(mTexture->bind()); Renderer::drawTriangleStrips(&mVertices[0], 4);
Renderer::drawTriangleStrips(&mVertices[0], 4);
}else{ }
LOG(LogError) << "Image texture is not initialized!"; else {
mTexture.reset(); LOG(LogError) << "Image texture is not initialized!";
} mTexture.reset();
} }
}
GuiComponent::renderChildren(trans); GuiComponent::renderChildren(trans);
} }
void ImageComponent::fadeIn(bool textureLoaded) void ImageComponent::fadeIn(bool textureLoaded)
{ {
if (!mForceLoad) if (!mForceLoad) {
{ if (!textureLoaded) {
if (!textureLoaded) // Start the fade if this is the first time we've encountered the unloaded texture.
{ if (!mFading) {
// Start the fade if this is the first time we've encountered the unloaded texture // Start with a zero opacity and flag it as fading.
if (!mFading) mFadeOpacity = 0;
{ mFading = true;
// Start with a zero opacity and flag it as fading updateColors();
mFadeOpacity = 0; }
mFading = true; }
updateColors(); else if (mFading) {
} // The texture is loaded and we need to fade it in. The fade is based on the frame
} // rate and is 1/4 second if running at 60 frames per second although the actual
else if (mFading) // value is not that important.
{ int opacity = mFadeOpacity + 255 / 15;
// The texture is loaded and we need to fade it in. The fade is based on the frame rate // See if we've finished fading.
// and is 1/4 second if running at 60 frames per second although the actual value is not if (opacity >= 255) {
// that important mFadeOpacity = 255;
int opacity = mFadeOpacity + 255 / 15; mFading = false;
// See if we've finished fading }
if (opacity >= 255) else {
{ mFadeOpacity = (unsigned char)opacity;
mFadeOpacity = 255; }
mFading = false; updateColors();
} }
else }
{
mFadeOpacity = (unsigned char)opacity;
}
updateColors();
}
}
} }
bool ImageComponent::hasImage() bool ImageComponent::hasImage()
{ {
return (bool)mTexture; return (bool)mTexture;
} }
void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties)
{ {
using namespace ThemeFlags; using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element, (properties ^ SIZE) | ((properties & (SIZE | POSITION)) ? ORIGIN : 0)); GuiComponent::applyTheme(theme, view, element, (properties ^ SIZE) |
((properties & (SIZE | POSITION)) ? ORIGIN : 0));
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "image"); const ThemeData::ThemeElement* elem = theme->getElement(view, element, "image");
if(!elem) if (!elem)
return; return;
Vector2f scale = getParent() ? getParent()->getSize() : Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); Vector2f scale = getParent() ? getParent()->getSize() :
Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
if(properties & ThemeFlags::SIZE) if (properties & ThemeFlags::SIZE) {
{ if (elem->has("size"))
if(elem->has("size")) setResize(elem->get<Vector2f>("size") * scale);
setResize(elem->get<Vector2f>("size") * scale); else if (elem->has("maxSize"))
else if(elem->has("maxSize")) setMaxSize(elem->get<Vector2f>("maxSize") * scale);
setMaxSize(elem->get<Vector2f>("maxSize") * scale); else if (elem->has("minSize"))
else if(elem->has("minSize")) setMinSize(elem->get<Vector2f>("minSize") * scale);
setMinSize(elem->get<Vector2f>("minSize") * scale); }
}
if(elem->has("default")) if (elem->has("default"))
setDefaultImage(elem->get<std::string>("default")); setDefaultImage(elem->get<std::string>("default"));
if(properties & PATH && elem->has("path")) if (properties & PATH && elem->has("path")) {
{ bool tile = (elem->has("tile") && elem->get<bool>("tile"));
bool tile = (elem->has("tile") && elem->get<bool>("tile")); setImage(elem->get<std::string>("path"), tile);
setImage(elem->get<std::string>("path"), tile); }
}
if(properties & COLOR) if (properties & COLOR) {
{ if (elem->has("color"))
if(elem->has("color")) setColorShift(elem->get<unsigned int>("color"));
setColorShift(elem->get<unsigned int>("color")); if (elem->has("colorEnd"))
setColorShiftEnd(elem->get<unsigned int>("colorEnd"));
if (elem->has("colorEnd")) if (elem->has("gradientType"))
setColorShiftEnd(elem->get<unsigned int>("colorEnd")); setColorGradientHorizontal(!(elem->get<std::string>("gradientType").compare("horizontal")));
}
if (elem->has("gradientType"))
setColorGradientHorizontal(!(elem->get<std::string>("gradientType").compare("horizontal")));
}
} }
std::vector<HelpPrompt> ImageComponent::getHelpPrompts() std::vector<HelpPrompt> ImageComponent::getHelpPrompts()
{ {
std::vector<HelpPrompt> ret; std::vector<HelpPrompt> ret;
ret.push_back(HelpPrompt("a", "select")); ret.push_back(HelpPrompt("a", "select"));
return ret; return ret;
} }

View file

@ -1,3 +1,9 @@
//
// ImageComponent.h
//
// Handles images: loading, resizing, cropping, color shifting etc.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_IMAGE_COMPONENT_H #ifndef ES_CORE_COMPONENTS_IMAGE_COMPONENT_H
#define ES_CORE_COMPONENTS_IMAGE_COMPONENT_H #define ES_CORE_COMPONENTS_IMAGE_COMPONENT_H
@ -11,102 +17,108 @@ class TextureResource;
class ImageComponent : public GuiComponent class ImageComponent : public GuiComponent
{ {
public: public:
ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true); ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true);
virtual ~ImageComponent(); virtual ~ImageComponent();
void setDefaultImage(std::string path); void setDefaultImage(std::string path);
//Loads the image at the given filepath. Will tile if tile is true (retrieves texture as tiling, creates vertices accordingly). // Loads the image at the given filepath. Will tile if tile is true (retrieves texture
void setImage(std::string path, bool tile = false); // as tiling, creates vertices accordingly).
//Loads an image from memory. void setImage(std::string path, bool tile = false);
void setImage(const char* image, size_t length, bool tile = false); // Loads an image from memory.
//Use an already existing texture. void setImage(const char* image, size_t length, bool tile = false);
void setImage(const std::shared_ptr<TextureResource>& texture); // Use an already existing texture.
void setImage(const std::shared_ptr<TextureResource>& texture);
void onSizeChanged() override; void onSizeChanged() override;
void setOpacity(unsigned char opacity) override; void setOpacity(unsigned char opacity) override;
// Resize the image to fit this size. If one axis is zero, scale that axis to maintain aspect ratio. // Resize the image to fit this size. If one axis is zero, scale that axis to maintain
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing. // aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are
// Can be set before or after an image is loaded. // zero, don't do any resizing.
// setMaxSize() and setResize() are mutually exclusive. // Can be set before or after an image is loaded.
void setResize(float width, float height); // setMaxSize() and setResize() are mutually exclusive.
inline void setResize(const Vector2f& size) { setResize(size.x(), size.y()); } void setResize(float width, float height);
inline void setResize(const Vector2f& size) { setResize(size.x(), size.y()); }
// Resize the image to be as large as possible but fit within a box of this size. // Resize the image to be as large as possible but fit within a box of this size.
// Can be set before or after an image is loaded. // Can be set before or after an image is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive. // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
void setMaxSize(float width, float height); void setMaxSize(float width, float height);
inline void setMaxSize(const Vector2f& size) { setMaxSize(size.x(), size.y()); } inline void setMaxSize(const Vector2f& size) { setMaxSize(size.x(), size.y()); }
void setMinSize(float width, float height); void setMinSize(float width, float height);
inline void setMinSize(const Vector2f& size) { setMinSize(size.x(), size.y()); } inline void setMinSize(const Vector2f& size) { setMinSize(size.x(), size.y()); }
Vector2f getRotationSize() const override; Vector2f getRotationSize() const override;
// Applied AFTER image positioning and sizing // Applied AFTER image positioning and sizing.
// cropTop(0.2) will crop 20% of the top of the image. // cropTop(0.2) will crop 20% of the top of the image.
void cropLeft(float percent); void cropLeft(float percent);
void cropTop(float percent); void cropTop(float percent);
void cropRight(float percent); void cropRight(float percent);
void cropBot(float percent); void cropBot(float percent);
void crop(float left, float top, float right, float bot); void crop(float left, float top, float right, float bot);
void uncrop(); void uncrop();
// Multiply all pixels in the image by this color when rendering. // Multiply all pixels in the image by this color when rendering.
void setColorShift(unsigned int color) override; void setColorShift(unsigned int color) override;
void setColorShiftEnd(unsigned int color); void setColorShiftEnd(unsigned int color);
void setColorGradientHorizontal(bool horizontal); void setColorGradientHorizontal(bool horizontal);
void setFlipX(bool flip); // Mirror on the X axis. void setFlipX(bool flip); // Mirror on the X axis.
void setFlipY(bool flip); // Mirror on the Y axis. void setFlipY(bool flip); // Mirror on the Y axis.
void setRotateByTargetSize(bool rotate); // Flag indicating if rotation should be based on target size vs. actual size. // Flag indicating if rotation should be based on target size vs. actual size.
void setRotateByTargetSize(bool rotate);
// Returns the size of the current texture, or (0, 0) if none is loaded. May be different than drawn size (use getSize() for that). // Returns the size of the current texture, or (0, 0) if none is loaded.
Vector2i getTextureSize() const; // May be different than drawn size (use getSize() for that).
Vector2i getTextureSize() const;
Vector2f getSize() const override; Vector2f getSize() const override;
bool hasImage(); bool hasImage();
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override; virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
std::shared_ptr<TextureResource> getTexture() { return mTexture; };
std::shared_ptr<TextureResource> getTexture() { return mTexture; };
private: private:
Vector2f mTargetSize; Vector2f mTargetSize;
bool mFlipX, mFlipY, mTargetIsMax, mTargetIsMin; bool mFlipX, mFlipY, mTargetIsMax, mTargetIsMin;
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize). // Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change. // Used internally whenever the resizing parameters or texture change.
void resize(); void resize();
Renderer::Vertex mVertices[4]; Renderer::Vertex mVertices[4];
void updateVertices(); void updateVertices();
void updateColors(); void updateColors();
void fadeIn(bool textureLoaded); void fadeIn(bool textureLoaded);
unsigned int mColorShift; unsigned int mColorShift;
unsigned int mColorShiftEnd; unsigned int mColorShiftEnd;
bool mColorGradientHorizontal; bool mColorGradientHorizontal;
std::string mDefaultPath; std::string mDefaultPath;
std::shared_ptr<TextureResource> mTexture; std::shared_ptr<TextureResource> mTexture;
unsigned char mFadeOpacity; unsigned char mFadeOpacity;
bool mFading; bool mFading;
bool mForceLoad; bool mForceLoad;
bool mDynamic; bool mDynamic;
bool mRotateByTargetSize; bool mRotateByTargetSize;
Vector2f mTopLeftCrop; Vector2f mTopLeftCrop;
Vector2f mBottomRightCrop; Vector2f mBottomRightCrop;
}; };
#endif // ES_CORE_COMPONENTS_IMAGE_COMPONENT_H #endif // ES_CORE_COMPONENTS_IMAGE_COMPONENT_H

File diff suppressed because it is too large Load diff

View file

@ -1,168 +1,181 @@
//
// NinePatchComponent.cpp
//
// Breaks up an image into 3x3 patches to accomodate resizing without distortions.
//
#include "components/NinePatchComponent.h" #include "components/NinePatchComponent.h"
#include "resources/TextureResource.h" #include "resources/TextureResource.h"
#include "Log.h" #include "Log.h"
#include "ThemeData.h" #include "ThemeData.h"
NinePatchComponent::NinePatchComponent(Window* window, const std::string& path, unsigned int edgeColor, unsigned int centerColor) : GuiComponent(window), NinePatchComponent::NinePatchComponent(
mCornerSize(16, 16), Window* window,
mEdgeColor(edgeColor), mCenterColor(centerColor), const std::string& path,
mPath(path), unsigned int edgeColor,
mVertices(NULL) unsigned int centerColor)
: GuiComponent(window),
mCornerSize(16, 16),
mEdgeColor(edgeColor),
mCenterColor(centerColor),
mPath(path),
mVertices(nullptr)
{ {
if(!mPath.empty()) if (!mPath.empty())
buildVertices(); buildVertices();
} }
NinePatchComponent::~NinePatchComponent() NinePatchComponent::~NinePatchComponent()
{ {
if (mVertices != NULL) if (mVertices != nullptr)
delete[] mVertices; delete[] mVertices;
} }
void NinePatchComponent::updateColors() void NinePatchComponent::updateColors()
{ {
const unsigned int edgeColor = Renderer::convertColor(mEdgeColor); const unsigned int edgeColor = Renderer::convertColor(mEdgeColor);
const unsigned int centerColor = Renderer::convertColor(mCenterColor); const unsigned int centerColor = Renderer::convertColor(mCenterColor);
for(int i = 0; i < 6*9; ++i) for (int i = 0; i < 6*9; ++i)
mVertices[i].col = edgeColor; mVertices[i].col = edgeColor;
for(int i = 6*4; i < 6; ++i) for (int i = 6*4; i < 6; ++i)
mVertices[(6*4)+i].col = centerColor; mVertices[(6*4)+i].col = centerColor;
} }
void NinePatchComponent::buildVertices() void NinePatchComponent::buildVertices()
{ {
if(mVertices != NULL) if (mVertices != nullptr)
delete[] mVertices; delete[] mVertices;
mTexture = TextureResource::get(mPath); mTexture = TextureResource::get(mPath);
if(mTexture->getSize() == Vector2i::Zero()) if (mTexture->getSize() == Vector2i::Zero()) {
{ mVertices = nullptr;
mVertices = NULL; LOG(LogWarning) << "NinePatchComponent missing texture!";
LOG(LogWarning) << "NinePatchComponent missing texture!"; return;
return; }
}
mVertices = new Renderer::Vertex[6 * 9]; mVertices = new Renderer::Vertex[6 * 9];
const Vector2f texSize = Vector2f((float)mTexture->getSize().x(), (float)mTexture->getSize().y()); const Vector2f texSize = Vector2f((float)mTexture->getSize().x(),
(float)mTexture->getSize().y());
const float imgSizeX[3] = { mCornerSize.x(), mSize.x() - mCornerSize.x() * 2, mCornerSize.x()}; const float imgSizeX[3] = { mCornerSize.x(), mSize.x() - mCornerSize.x() * 2, mCornerSize.x()};
const float imgSizeY[3] = { mCornerSize.y(), mSize.y() - mCornerSize.y() * 2, mCornerSize.y()}; const float imgSizeY[3] = { mCornerSize.y(), mSize.y() - mCornerSize.y() * 2, mCornerSize.y()};
const float imgPosX[3] = { 0, imgSizeX[0], imgSizeX[0] + imgSizeX[1]}; const float imgPosX[3] = { 0, imgSizeX[0], imgSizeX[0] + imgSizeX[1]};
const float imgPosY[3] = { 0, imgSizeY[0], imgSizeY[0] + imgSizeY[1]}; const float imgPosY[3] = { 0, imgSizeY[0], imgSizeY[0] + imgSizeY[1]};
//the "1 +" in posY and "-" in sizeY is to deal with texture coordinates having a bottom left corner origin vs. verticies having a top left origin // The "1 +" in posY and "-" in sizeY is to deal with texture coordinates having a bottom
const float texSizeX[3] = { mCornerSize.x() / texSize.x(), (texSize.x() - mCornerSize.x() * 2) / texSize.x(), mCornerSize.x() / texSize.x() }; // left corner origin vs. verticies having a top left origin.
const float texSizeY[3] = { -mCornerSize.y() / texSize.y(), -(texSize.y() - mCornerSize.y() * 2) / texSize.y(), -mCornerSize.y() / texSize.y() }; const float texSizeX[3] = { mCornerSize.x() / texSize.x(), (texSize.x() - mCornerSize.x() * 2) / texSize.x(), mCornerSize.x() / texSize.x() };
const float texPosX[3] = { 0, texSizeX[0], texSizeX[0] + texSizeX[1] }; const float texSizeY[3] = { -mCornerSize.y() / texSize.y(), -(texSize.y() - mCornerSize.y() * 2) / texSize.y(), -mCornerSize.y() / texSize.y() };
const float texPosY[3] = { 1, 1 + texSizeY[0], 1 + texSizeY[0] + texSizeY[1] }; const float texPosX[3] = { 0, texSizeX[0], texSizeX[0] + texSizeX[1] };
const float texPosY[3] = { 1, 1 + texSizeY[0], 1 + texSizeY[0] + texSizeY[1] };
int v = 0; int v = 0;
for(int slice = 0; slice < 9; slice++)
{
const int sliceX = slice % 3;
const int sliceY = slice / 3;
const Vector2f imgPos = Vector2f(imgPosX[sliceX], imgPosY[sliceY]);
const Vector2f imgSize = Vector2f(imgSizeX[sliceX], imgSizeY[sliceY]);
const Vector2f texPos = Vector2f(texPosX[sliceX], texPosY[sliceY]);
const Vector2f texSize = Vector2f(texSizeX[sliceX], texSizeY[sliceY]);
mVertices[v + 1] = { { imgPos.x() , imgPos.y() }, { texPos.x(), texPos.y() }, 0 }; for (int slice = 0; slice < 9; slice++) {
mVertices[v + 2] = { { imgPos.x() , imgPos.y() + imgSize.y() }, { texPos.x(), texPos.y() + texSize.y() }, 0 }; const int sliceX = slice % 3;
mVertices[v + 3] = { { imgPos.x() + imgSize.x(), imgPos.y() }, { texPos.x() + texSize.x(), texPos.y() }, 0 }; const int sliceY = slice / 3;
mVertices[v + 4] = { { imgPos.x() + imgSize.x(), imgPos.y() + imgSize.y() }, { texPos.x() + texSize.x(), texPos.y() + texSize.y() }, 0 }; const Vector2f imgPos = Vector2f(imgPosX[sliceX], imgPosY[sliceY]);
const Vector2f imgSize = Vector2f(imgSizeX[sliceX], imgSizeY[sliceY]);
const Vector2f texPos = Vector2f(texPosX[sliceX], texPosY[sliceY]);
const Vector2f texSize = Vector2f(texSizeX[sliceX], texSizeY[sliceY]);
// round vertices mVertices[v + 1] = { { imgPos.x() , imgPos.y() }, { texPos.x(), texPos.y() }, 0 };
for(int i = 1; i < 5; ++i) mVertices[v + 2] = { { imgPos.x() , imgPos.y() + imgSize.y() }, { texPos.x(), texPos.y() + texSize.y() }, 0 };
mVertices[v + i].pos.round(); mVertices[v + 3] = { { imgPos.x() + imgSize.x(), imgPos.y() }, { texPos.x() + texSize.x(), texPos.y() }, 0 };
mVertices[v + 4] = { { imgPos.x() + imgSize.x(), imgPos.y() + imgSize.y() }, { texPos.x() + texSize.x(), texPos.y() + texSize.y() }, 0 };
// make duplicates of first and last vertex so this can be rendered as a triangle strip // Round vertices.
mVertices[v + 0] = mVertices[v + 1]; for (int i = 1; i < 5; ++i)
mVertices[v + 5] = mVertices[v + 4]; mVertices[v + i].pos.round();
v += 6; // Make duplicates of first and last vertex so this can be rendered as a triangle strip.
} mVertices[v + 0] = mVertices[v + 1];
mVertices[v + 5] = mVertices[v + 4];
updateColors(); v += 6;
}
updateColors();
} }
void NinePatchComponent::render(const Transform4x4f& parentTrans) void NinePatchComponent::render(const Transform4x4f& parentTrans)
{ {
if (!isVisible()) if (!isVisible())
return; return;
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
if(mTexture && mVertices != NULL) if (mTexture && mVertices != nullptr) {
{ Renderer::setMatrix(trans);
Renderer::setMatrix(trans);
mTexture->bind(); mTexture->bind();
Renderer::drawTriangleStrips(&mVertices[0], 6*9); Renderer::drawTriangleStrips(&mVertices[0], 6*9);
} }
renderChildren(trans); renderChildren(trans);
} }
void NinePatchComponent::onSizeChanged() void NinePatchComponent::onSizeChanged()
{ {
buildVertices(); buildVertices();
} }
const Vector2f& NinePatchComponent::getCornerSize() const const Vector2f& NinePatchComponent::getCornerSize() const
{ {
return mCornerSize; return mCornerSize;
} }
void NinePatchComponent::setCornerSize(int sizeX, int sizeY) void NinePatchComponent::setCornerSize(int sizeX, int sizeY)
{ {
mCornerSize = Vector2f(sizeX, sizeY); mCornerSize = Vector2f(sizeX, sizeY);
buildVertices(); buildVertices();
} }
void NinePatchComponent::fitTo(Vector2f size, Vector3f position, Vector2f padding) void NinePatchComponent::fitTo(Vector2f size, Vector3f position, Vector2f padding)
{ {
size += padding; size += padding;
position[0] -= padding.x() / 2; position[0] -= padding.x() / 2;
position[1] -= padding.y() / 2; position[1] -= padding.y() / 2;
setSize(size + mCornerSize * 2); setSize(size + mCornerSize * 2);
setPosition(position.x() + Math::lerp(-mCornerSize.x(), mCornerSize.x(), mOrigin.x()), setPosition(position.x() + Math::lerp(-mCornerSize.x(), mCornerSize.x(), mOrigin.x()),
position.y() + Math::lerp(-mCornerSize.y(), mCornerSize.y(), mOrigin.y())); position.y() + Math::lerp(-mCornerSize.y(), mCornerSize.y(), mOrigin.y()));
} }
void NinePatchComponent::setImagePath(const std::string& path) void NinePatchComponent::setImagePath(const std::string& path)
{ {
mPath = path; mPath = path;
buildVertices(); buildVertices();
} }
void NinePatchComponent::setEdgeColor(unsigned int edgeColor) void NinePatchComponent::setEdgeColor(unsigned int edgeColor)
{ {
mEdgeColor = edgeColor; mEdgeColor = edgeColor;
updateColors(); updateColors();
} }
void NinePatchComponent::setCenterColor(unsigned int centerColor) void NinePatchComponent::setCenterColor(unsigned int centerColor)
{ {
mCenterColor = centerColor; mCenterColor = centerColor;
updateColors(); updateColors();
} }
void NinePatchComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) void NinePatchComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view, const std::string& element, unsigned int properties)
{ {
GuiComponent::applyTheme(theme, view, element, properties); GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags; using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "ninepatch");
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "ninepatch"); if (!elem)
if(!elem) return;
return;
if(properties & PATH && elem->has("path")) if (properties & PATH && elem->has("path"))
setImagePath(elem->get<std::string>("path")); setImagePath(elem->get<std::string>("path"));
} }

View file

@ -1,3 +1,9 @@
//
// NinePatchComponent.h
//
// Breaks up an image into 3x3 patches to accomodate resizing without distortions.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_NINE_PATCH_COMPONENT_H #ifndef ES_CORE_COMPONENTS_NINE_PATCH_COMPONENT_H
#define ES_CORE_COMPONENTS_NINE_PATCH_COMPONENT_H #define ES_CORE_COMPONENTS_NINE_PATCH_COMPONENT_H
@ -7,13 +13,14 @@
class TextureResource; class TextureResource;
// Display an image in a way so that edges don't get too distorted no matter the final size. Useful for UI elements like backgrounds, buttons, etc. // Display an image in a way so that edges don't get too distorted no matter the final size.
// Useful for UI elements like backgrounds, buttons, etc.
// This is accomplished by splitting an image into 9 pieces: // This is accomplished by splitting an image into 9 pieces:
// ___________ // ___________
// |_1_|_2_|_3_| // |_1_|_2_|_3_|
// |_4_|_5_|_6_| // |_4_|_5_|_6_|
// |_7_|_8_|_9_| // |_7_|_8_|_9_|
//
// Corners (1, 3, 7, 9) will not be stretched at all. // Corners (1, 3, 7, 9) will not be stretched at all.
// Borders (2, 4, 6, 8) will be stretched along one axis (2 and 8 horizontally, 4 and 6 vertically). // Borders (2, 4, 6, 8) will be stretched along one axis (2 and 8 horizontally, 4 and 6 vertically).
// The center (5) will be stretched. // The center (5) will be stretched.
@ -21,36 +28,41 @@ class TextureResource;
class NinePatchComponent : public GuiComponent class NinePatchComponent : public GuiComponent
{ {
public: public:
NinePatchComponent(Window* window, const std::string& path = "", unsigned int edgeColor = 0xFFFFFFFF, unsigned int centerColor = 0xFFFFFFFF); NinePatchComponent(Window* window, const std::string& path = "",
virtual ~NinePatchComponent(); unsigned int edgeColor = 0xFFFFFFFF, unsigned int centerColor = 0xFFFFFFFF);
virtual ~NinePatchComponent();
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
void onSizeChanged() override; void onSizeChanged() override;
void fitTo(Vector2f size, Vector3f position = Vector3f::Zero(), Vector2f padding = Vector2f::Zero()); void fitTo(Vector2f size, Vector3f position = Vector3f::Zero(),
Vector2f padding = Vector2f::Zero());
void setImagePath(const std::string& path); void setImagePath(const std::string& path);
void setEdgeColor(unsigned int edgeColor); // Apply a color shift to the "edge" parts of the ninepatch. // Apply a color shift to the "edge" parts of the ninepatch.
void setCenterColor(unsigned int centerColor); // Apply a color shift to the "center" part of the ninepatch. void setEdgeColor(unsigned int edgeColor);
// Apply a color shift to the "center" part of the ninepatch.
void setCenterColor(unsigned int centerColor);
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override; virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties) override;
const Vector2f& getCornerSize() const; const Vector2f& getCornerSize() const;
void setCornerSize(int sizeX, int sizeY); void setCornerSize(int sizeX, int sizeY);
inline void setCornerSize(const Vector2f& size) { setCornerSize(size.x(), size.y()); } inline void setCornerSize(const Vector2f& size) { setCornerSize(size.x(), size.y()); }
private: private:
void buildVertices(); void buildVertices();
void updateColors(); void updateColors();
Renderer::Vertex* mVertices; Renderer::Vertex* mVertices;
std::string mPath; std::string mPath;
Vector2f mCornerSize; Vector2f mCornerSize;
unsigned int mEdgeColor; unsigned int mEdgeColor;
unsigned int mCenterColor; unsigned int mCenterColor;
std::shared_ptr<TextureResource> mTexture; std::shared_ptr<TextureResource> mTexture;
}; };
#endif // ES_CORE_COMPONENTS_NINE_PATCH_COMPONENT_H #endif // ES_CORE_COMPONENTS_NINE_PATCH_COMPONENT_H

View file

@ -1,8 +1,8 @@
// //
// OptionListComponent.h // OptionListComponent.h
// //
// Provides a list of options. // Provides a list of options.
// Supports various types using templates. // Supports various types using templates.
// //
#pragma once #pragma once
@ -28,354 +28,354 @@ template<typename T>
class OptionListComponent : public GuiComponent class OptionListComponent : public GuiComponent
{ {
public: public:
OptionListComponent( OptionListComponent(
Window* window, Window* window,
const HelpStyle& helpstyle, const HelpStyle& helpstyle,
const std::string& name, const std::string& name,
bool multiSelect = false) bool multiSelect = false)
: GuiComponent(window), : GuiComponent(window),
mHelpStyle(helpstyle), mHelpStyle(helpstyle),
mMultiSelect(multiSelect), mMultiSelect(multiSelect),
mName(name), mName(name),
mText(window), mText(window),
mLeftArrow(window), mLeftArrow(window),
mRightArrow(window) mRightArrow(window)
{ {
auto font = Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT); auto font = Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT);
mText.setFont(font); mText.setFont(font);
mText.setColor(0x777777FF); mText.setColor(0x777777FF);
mText.setHorizontalAlignment(ALIGN_CENTER); mText.setHorizontalAlignment(ALIGN_CENTER);
addChild(&mText); addChild(&mText);
mLeftArrow.setResize(0, mText.getFont()->getLetterHeight()); mLeftArrow.setResize(0, mText.getFont()->getLetterHeight());
mRightArrow.setResize(0, mText.getFont()->getLetterHeight()); mRightArrow.setResize(0, mText.getFont()->getLetterHeight());
if(mMultiSelect) { if(mMultiSelect) {
mRightArrow.setImage(":/graphics/arrow.svg"); mRightArrow.setImage(":/graphics/arrow.svg");
addChild(&mRightArrow); addChild(&mRightArrow);
} }
else { else {
mLeftArrow.setImage(":/graphics/option_arrow.svg"); mLeftArrow.setImage(":/graphics/option_arrow.svg");
mLeftArrow.setFlipX(true); mLeftArrow.setFlipX(true);
addChild(&mLeftArrow); addChild(&mLeftArrow);
mRightArrow.setImage(":/graphics/option_arrow.svg"); mRightArrow.setImage(":/graphics/option_arrow.svg");
addChild(&mRightArrow); addChild(&mRightArrow);
} }
setSize(mLeftArrow.getSize().x() + mRightArrow.getSize().x(), font->getHeight()); setSize(mLeftArrow.getSize().x() + mRightArrow.getSize().x(), font->getHeight());
} }
// Handles positioning/resizing of text and arrows. // Handles positioning/resizing of text and arrows.
void onSizeChanged() override void onSizeChanged() override
{ {
mLeftArrow.setResize(0, mText.getFont()->getLetterHeight()); mLeftArrow.setResize(0, mText.getFont()->getLetterHeight());
mRightArrow.setResize(0, mText.getFont()->getLetterHeight()); mRightArrow.setResize(0, mText.getFont()->getLetterHeight());
if(mSize.x() < (mLeftArrow.getSize().x() + mRightArrow.getSize().x())) { if(mSize.x() < (mLeftArrow.getSize().x() + mRightArrow.getSize().x())) {
LOG(LogWarning) << "OptionListComponent too narrow!"; LOG(LogWarning) << "OptionListComponent too narrow!";
} }
mText.setSize(mSize.x() - mLeftArrow.getSize().x() - mText.setSize(mSize.x() - mLeftArrow.getSize().x() -
mRightArrow.getSize().x(), mText.getFont()->getHeight()); mRightArrow.getSize().x(), mText.getFont()->getHeight());
// Position. // Position.
mLeftArrow.setPosition(0, (mSize.y() - mLeftArrow.getSize().y()) / 2); mLeftArrow.setPosition(0, (mSize.y() - mLeftArrow.getSize().y()) / 2);
mText.setPosition(mLeftArrow.getPosition().x() + mLeftArrow.getSize().x(), mText.setPosition(mLeftArrow.getPosition().x() + mLeftArrow.getSize().x(),
(mSize.y() - mText.getSize().y()) / 2); (mSize.y() - mText.getSize().y()) / 2);
mRightArrow.setPosition(mText.getPosition().x() + mText.getSize().x(), mRightArrow.setPosition(mText.getPosition().x() + mText.getSize().x(),
(mSize.y() - mRightArrow.getSize().y()) / 2); (mSize.y() - mRightArrow.getSize().y()) / 2);
} }
bool input(InputConfig* config, Input input) override bool input(InputConfig* config, Input input) override
{ {
if(input.value != 0) { if(input.value != 0) {
if(config->isMappedTo("a", input)) { if(config->isMappedTo("a", input)) {
open(); open();
return true; return true;
} }
if(!mMultiSelect) { if(!mMultiSelect) {
if(config->isMappedLike("left", input)) { if(config->isMappedLike("left", input)) {
// Move selection to previous. // Move selection to previous.
unsigned int i = getSelectedId(); unsigned int i = getSelectedId();
int next = (int)i - 1; int next = (int)i - 1;
if(next < 0) if(next < 0)
next += (int)mEntries.size(); next += (int)mEntries.size();
mEntries.at(i).selected = false; mEntries.at(i).selected = false;
mEntries.at(next).selected = true; mEntries.at(next).selected = true;
onSelectedChanged(); onSelectedChanged();
return true; return true;
} }
else if(config->isMappedLike("right", input)) { else if(config->isMappedLike("right", input)) {
// Move selection to next. // Move selection to next.
unsigned int i = getSelectedId(); unsigned int i = getSelectedId();
int next = (i + 1) % mEntries.size(); int next = (i + 1) % mEntries.size();
mEntries.at(i).selected = false; mEntries.at(i).selected = false;
mEntries.at(next).selected = true; mEntries.at(next).selected = true;
onSelectedChanged(); onSelectedChanged();
return true; return true;
} }
} }
} }
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
std::vector<T> getSelectedObjects() std::vector<T> getSelectedObjects()
{ {
std::vector<T> ret; std::vector<T> ret;
for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++) { for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++) {
if(it->selected) if(it->selected)
ret.push_back(it->object); ret.push_back(it->object);
} }
return ret; return ret;
} }
T getSelected() T getSelected()
{ {
assert(mMultiSelect == false); assert(mMultiSelect == false);
auto selected = getSelectedObjects(); auto selected = getSelectedObjects();
assert(selected.size() == 1); assert(selected.size() == 1);
return selected.at(0); return selected.at(0);
} }
void add(const std::string& name, const T& obj, bool selected) void add(const std::string& name, const T& obj, bool selected)
{ {
OptionListData e; OptionListData e;
e.name = name; e.name = name;
e.object = obj; e.object = obj;
e.selected = selected; e.selected = selected;
mEntries.push_back(e); mEntries.push_back(e);
onSelectedChanged(); onSelectedChanged();
} }
bool selectEntry(unsigned int entry) bool selectEntry(unsigned int entry)
{ {
if (entry > mEntries.size()) { if (entry > mEntries.size()) {
return false; return false;
} }
else { else {
mEntries.at(entry).selected = true; mEntries.at(entry).selected = true;
onSelectedChanged(); onSelectedChanged();
return true; return true;
} }
} }
bool unselectEntry(unsigned int entry) bool unselectEntry(unsigned int entry)
{ {
if (entry > mEntries.size()) { if (entry > mEntries.size()) {
return false; return false;
} }
else { else {
mEntries.at(entry).selected = false; mEntries.at(entry).selected = false;
onSelectedChanged(); onSelectedChanged();
return true; return true;
} }
} }
void selectAll() void selectAll()
{ {
for(unsigned int i = 0; i < mEntries.size(); i++) for(unsigned int i = 0; i < mEntries.size(); i++)
mEntries.at(i).selected = true; mEntries.at(i).selected = true;
onSelectedChanged(); onSelectedChanged();
} }
void selectNone() void selectNone()
{ {
for(unsigned int i = 0; i < mEntries.size(); i++) for(unsigned int i = 0; i < mEntries.size(); i++)
mEntries.at(i).selected = false; mEntries.at(i).selected = false;
onSelectedChanged(); onSelectedChanged();
} }
HelpStyle getHelpStyle() override { return mHelpStyle; }; HelpStyle getHelpStyle() override { return mHelpStyle; };
private: private:
struct OptionListData { struct OptionListData {
std::string name; std::string name;
T object; T object;
bool selected; bool selected;
}; };
HelpStyle mHelpStyle; HelpStyle mHelpStyle;
unsigned int getSelectedId() unsigned int getSelectedId()
{ {
assert(mMultiSelect == false); assert(mMultiSelect == false);
for(unsigned int i = 0; i < mEntries.size(); i++) { for(unsigned int i = 0; i < mEntries.size(); i++) {
if(mEntries.at(i).selected) if(mEntries.at(i).selected)
return i; return i;
} }
LOG(LogWarning) << "OptionListComponent::getSelectedId() - " LOG(LogWarning) << "OptionListComponent::getSelectedId() - "
"no selected element found, defaulting to 0"; "no selected element found, defaulting to 0";
return 0; return 0;
} }
void open() void open()
{ {
mWindow->pushGui(new OptionListPopup(mWindow, getHelpStyle(), this, mName)); mWindow->pushGui(new OptionListPopup(mWindow, getHelpStyle(), this, mName));
} }
void onSelectedChanged() void onSelectedChanged()
{ {
if(mMultiSelect) { if(mMultiSelect) {
// Display # selected. // Display # selected.
std::stringstream ss; std::stringstream ss;
ss << getSelectedObjects().size() << " SELECTED"; ss << getSelectedObjects().size() << " SELECTED";
mText.setText(ss.str()); mText.setText(ss.str());
mText.setSize(0, mText.getSize().y()); mText.setSize(0, mText.getSize().y());
setSize(mText.getSize().x() + mRightArrow.getSize().x() + 24, mText.getSize().y()); setSize(mText.getSize().x() + mRightArrow.getSize().x() + 24, mText.getSize().y());
if(mParent) // Hack since theres no "on child size changed" callback atm... if(mParent) // Hack since theres no "on child size changed" callback atm...
mParent->onSizeChanged(); mParent->onSizeChanged();
} }
else { else {
// Display currently selected + l/r cursors. // Display currently selected + l/r cursors.
for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++) { for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++) {
if(it->selected) { if(it->selected) {
mText.setText(Utils::String::toUpper(it->name)); mText.setText(Utils::String::toUpper(it->name));
mText.setSize(0, mText.getSize().y()); mText.setSize(0, mText.getSize().y());
setSize(mText.getSize().x() + mLeftArrow.getSize().x() + setSize(mText.getSize().x() + mLeftArrow.getSize().x() +
mRightArrow.getSize().x() + 24, mText.getSize().y()); mRightArrow.getSize().x() + 24, mText.getSize().y());
if(mParent) // Hack since theres no "on child size changed" callback atm... if(mParent) // Hack since theres no "on child size changed" callback atm...
mParent->onSizeChanged(); mParent->onSizeChanged();
break; break;
} }
} }
} }
} }
std::vector<HelpPrompt> getHelpPrompts() override std::vector<HelpPrompt> getHelpPrompts() override
{ {
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
if(!mMultiSelect) if(!mMultiSelect)
prompts.push_back(HelpPrompt("left/right", "change value")); prompts.push_back(HelpPrompt("left/right", "change value"));
prompts.push_back(HelpPrompt("a", "select")); prompts.push_back(HelpPrompt("a", "select"));
return prompts; return prompts;
} }
bool mMultiSelect; bool mMultiSelect;
std::string mName; std::string mName;
TextComponent mText; TextComponent mText;
ImageComponent mLeftArrow; ImageComponent mLeftArrow;
ImageComponent mRightArrow; ImageComponent mRightArrow;
std::vector<OptionListData> mEntries; std::vector<OptionListData> mEntries;
// Subclass to OptionListComponent. // Subclass to OptionListComponent.
class OptionListPopup : public GuiComponent class OptionListPopup : public GuiComponent
{ {
public: public:
OptionListPopup( OptionListPopup(
Window* window, Window* window,
const HelpStyle& helpstyle, const HelpStyle& helpstyle,
OptionListComponent<T>* parent, OptionListComponent<T>* parent,
const std::string& title) const std::string& title)
: GuiComponent(window), : GuiComponent(window),
mHelpStyle(helpstyle), mHelpStyle(helpstyle),
mMenu(window, title.c_str()), mMenu(window, title.c_str()),
mParent(parent) mParent(parent)
{ {
auto font = Font::get(FONT_SIZE_MEDIUM); auto font = Font::get(FONT_SIZE_MEDIUM);
ComponentListRow row; ComponentListRow row;
// For select all/none. // For select all/none.
std::vector<ImageComponent*> checkboxes; std::vector<ImageComponent*> checkboxes;
for(auto it = mParent->mEntries.begin(); it != mParent->mEntries.end(); it++) { for(auto it = mParent->mEntries.begin(); it != mParent->mEntries.end(); it++) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent> row.addElement(std::make_shared<TextComponent>
(mWindow, Utils::String::toUpper(it->name), font, 0x777777FF), true); (mWindow, Utils::String::toUpper(it->name), font, 0x777777FF), true);
OptionListData& e = *it; OptionListData& e = *it;
if(mParent->mMultiSelect) { if(mParent->mMultiSelect) {
// Add checkbox. // Add checkbox.
auto checkbox = std::make_shared<ImageComponent>(mWindow); auto checkbox = std::make_shared<ImageComponent>(mWindow);
checkbox->setImage(it->selected ? CHECKED_PATH : UNCHECKED_PATH); checkbox->setImage(it->selected ? CHECKED_PATH : UNCHECKED_PATH);
checkbox->setResize(0, font->getLetterHeight()); checkbox->setResize(0, font->getLetterHeight());
row.addElement(checkbox, false); row.addElement(checkbox, false);
// Input handler. // Input handler.
// Update checkbox state & selected value. // Update checkbox state & selected value.
row.makeAcceptInputHandler([this, &e, checkbox] { row.makeAcceptInputHandler([this, &e, checkbox] {
e.selected = !e.selected; e.selected = !e.selected;
checkbox->setImage(e.selected ? CHECKED_PATH : UNCHECKED_PATH); checkbox->setImage(e.selected ? CHECKED_PATH : UNCHECKED_PATH);
mParent->onSelectedChanged(); mParent->onSelectedChanged();
}); });
// For select all/none. // For select all/none.
checkboxes.push_back(checkbox.get()); checkboxes.push_back(checkbox.get());
} }
else { else {
// Input handler for non-multiselect. // Input handler for non-multiselect.
// Update selected value and close. // Update selected value and close.
row.makeAcceptInputHandler([this, &e] { row.makeAcceptInputHandler([this, &e] {
mParent->mEntries.at(mParent->getSelectedId()).selected = false; mParent->mEntries.at(mParent->getSelectedId()).selected = false;
e.selected = true; e.selected = true;
mParent->onSelectedChanged(); mParent->onSelectedChanged();
delete this; delete this;
}); });
} }
// Also set cursor to this row if we're not multi-select and this row is selected. // Also set cursor to this row if we're not multi-select and this row is selected.
mMenu.addRow(row, (!mParent->mMultiSelect && it->selected)); mMenu.addRow(row, (!mParent->mMultiSelect && it->selected));
} }
mMenu.addButton("BACK", "back", [this] { delete this; }); mMenu.addButton("BACK", "back", [this] { delete this; });
if(mParent->mMultiSelect) { if(mParent->mMultiSelect) {
mMenu.addButton("SELECT ALL", "select all", [this, checkboxes] { mMenu.addButton("SELECT ALL", "select all", [this, checkboxes] {
for(unsigned int i = 0; i < mParent->mEntries.size(); i++) { for(unsigned int i = 0; i < mParent->mEntries.size(); i++) {
mParent->mEntries.at(i).selected = true; mParent->mEntries.at(i).selected = true;
checkboxes.at(i)->setImage(CHECKED_PATH); checkboxes.at(i)->setImage(CHECKED_PATH);
} }
mParent->onSelectedChanged(); mParent->onSelectedChanged();
}); });
mMenu.addButton("SELECT NONE", "select none", [this, checkboxes] { mMenu.addButton("SELECT NONE", "select none", [this, checkboxes] {
for(unsigned int i = 0; i < mParent->mEntries.size(); i++) { for(unsigned int i = 0; i < mParent->mEntries.size(); i++) {
mParent->mEntries.at(i).selected = false; mParent->mEntries.at(i).selected = false;
checkboxes.at(i)->setImage(UNCHECKED_PATH); checkboxes.at(i)->setImage(UNCHECKED_PATH);
} }
mParent->onSelectedChanged(); mParent->onSelectedChanged();
}); });
} }
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2,
Renderer::getScreenHeight() * 0.15f); Renderer::getScreenHeight() * 0.15f);
addChild(&mMenu); addChild(&mMenu);
} }
bool input(InputConfig* config, Input input) override bool input(InputConfig* config, Input input) override
{ {
if(config->isMappedTo("b", input) && input.value != 0) { if(config->isMappedTo("b", input) && input.value != 0) {
delete this; delete this;
return true; return true;
} }
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
std::vector<HelpPrompt> getHelpPrompts() override std::vector<HelpPrompt> getHelpPrompts() override
{ {
auto prompts = mMenu.getHelpPrompts(); auto prompts = mMenu.getHelpPrompts();
prompts.push_back(HelpPrompt("a", "select")); prompts.push_back(HelpPrompt("a", "select"));
prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("b", "back"));
return prompts; return prompts;
} }
HelpStyle getHelpStyle() override { return mHelpStyle; }; HelpStyle getHelpStyle() override { return mHelpStyle; };
private: private:
MenuComponent mMenu; MenuComponent mMenu;
OptionListComponent<T>* mParent; OptionListComponent<T>* mParent;
HelpStyle mHelpStyle; HelpStyle mHelpStyle;
}; };
}; };
#endif // ES_CORE_COMPONENTS_OPTION_LIST_COMPONENT_H #endif // ES_CORE_COMPONENTS_OPTION_LIST_COMPONENT_H

View file

@ -1,133 +1,141 @@
//
// ScrollableContainer.cpp
//
// Area containing scrollable information, for example the game description
// text container in the detailed, video and grid views.
//
#include "components/ScrollableContainer.h" #include "components/ScrollableContainer.h"
#include "math/Vector2i.h" #include "math/Vector2i.h"
#include "renderers/Renderer.h" #include "renderers/Renderer.h"
#define AUTO_SCROLL_RESET_DELAY 3000 // ms to reset to top after we reach the bottom #define AUTO_SCROLL_RESET_DELAY 3000 // ms to reset to top after we reach the bottom.
#define AUTO_SCROLL_DELAY 1000 // ms to wait before we start to scroll #define AUTO_SCROLL_DELAY 1000 // ms to wait before we start to scroll.
#define AUTO_SCROLL_SPEED 50 // ms between scrolls #define AUTO_SCROLL_SPEED 100 // ms between scrolls.
ScrollableContainer::ScrollableContainer(Window* window) : GuiComponent(window), ScrollableContainer::ScrollableContainer(
mAutoScrollDelay(0), mAutoScrollSpeed(0), mAutoScrollAccumulator(0), mScrollPos(0, 0), mScrollDir(0, 0), mAutoScrollResetAccumulator(0) Window* window)
: GuiComponent(window),
mAutoScrollDelay(0),
mAutoScrollSpeed(0),
mAutoScrollAccumulator(0),
mScrollPos(0, 0),
mScrollDir(0, 0),
mAutoScrollResetAccumulator(0)
{ {
} }
void ScrollableContainer::render(const Transform4x4f& parentTrans) void ScrollableContainer::render(const Transform4x4f& parentTrans)
{ {
if (!isVisible()) if (!isVisible())
return; return;
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
Vector2i clipPos((int)trans.translation().x(), (int)trans.translation().y()); Vector2i clipPos((int)trans.translation().x(), (int)trans.translation().y());
Vector3f dimScaled = trans * Vector3f(mSize.x(), mSize.y(), 0); Vector3f dimScaled = trans * Vector3f(mSize.x(), mSize.y(), 0);
Vector2i clipDim((int)(dimScaled.x() - trans.translation().x()), (int)(dimScaled.y() - trans.translation().y())); Vector2i clipDim((int)(dimScaled.x() - trans.translation().x()),
(int)(dimScaled.y() - trans.translation().y()));
Renderer::pushClipRect(clipPos, clipDim); Renderer::pushClipRect(clipPos, clipDim);
trans.translate(-Vector3f(mScrollPos.x(), mScrollPos.y(), 0)); trans.translate(-Vector3f(mScrollPos.x(), mScrollPos.y(), 0));
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
GuiComponent::renderChildren(trans); GuiComponent::renderChildren(trans);
Renderer::popClipRect();
Renderer::popClipRect();
} }
void ScrollableContainer::setAutoScroll(bool autoScroll) void ScrollableContainer::setAutoScroll(bool autoScroll)
{ {
if(autoScroll) if (autoScroll) {
{ mScrollDir = Vector2f(0, 1);
mScrollDir = Vector2f(0, 1); mAutoScrollDelay = AUTO_SCROLL_DELAY;
mAutoScrollDelay = AUTO_SCROLL_DELAY; mAutoScrollSpeed = AUTO_SCROLL_SPEED;
mAutoScrollSpeed = AUTO_SCROLL_SPEED; reset();
reset(); }
}else{ else {
mScrollDir = Vector2f(0, 0); mScrollDir = Vector2f(0, 0);
mAutoScrollDelay = 0; mAutoScrollDelay = 0;
mAutoScrollSpeed = 0; mAutoScrollSpeed = 0;
mAutoScrollAccumulator = 0; mAutoScrollAccumulator = 0;
} }
} }
Vector2f ScrollableContainer::getScrollPos() const Vector2f ScrollableContainer::getScrollPos() const
{ {
return mScrollPos; return mScrollPos;
} }
void ScrollableContainer::setScrollPos(const Vector2f& pos) void ScrollableContainer::setScrollPos(const Vector2f& pos)
{ {
mScrollPos = pos; mScrollPos = pos;
} }
void ScrollableContainer::update(int deltaTime) void ScrollableContainer::update(int deltaTime)
{ {
if(mAutoScrollSpeed != 0) if (mAutoScrollSpeed != 0) {
{ mAutoScrollAccumulator += deltaTime;
mAutoScrollAccumulator += deltaTime;
//scale speed by our width! more text per line = slower scrolling // Scale speed by our width! more text per line = slower scrolling.
const float widthMod = (680.0f / getSize().x()); const float widthMod = (680.0f / getSize().x());
while(mAutoScrollAccumulator >= mAutoScrollSpeed) while (mAutoScrollAccumulator >= mAutoScrollSpeed) {
{ mScrollPos += mScrollDir;
mScrollPos += mScrollDir; mAutoScrollAccumulator -= mAutoScrollSpeed;
mAutoScrollAccumulator -= mAutoScrollSpeed; }
} }
}
//clip scrolling within bounds // Clip scrolling within bounds.
if(mScrollPos.x() < 0) if (mScrollPos.x() < 0)
mScrollPos[0] = 0; mScrollPos[0] = 0;
if(mScrollPos.y() < 0) if (mScrollPos.y() < 0)
mScrollPos[1] = 0; mScrollPos[1] = 0;
const Vector2f contentSize = getContentSize(); const Vector2f contentSize = getContentSize();
if(mScrollPos.x() + getSize().x() > contentSize.x()) if (mScrollPos.x() + getSize().x() > contentSize.x()) {
{ mScrollPos[0] = contentSize.x() - getSize().x();
mScrollPos[0] = contentSize.x() - getSize().x(); mAtEnd = true;
mAtEnd = true; }
}
if(contentSize.y() < getSize().y()) if (contentSize.y() < getSize().y()) {
{ mScrollPos[1] = 0;
mScrollPos[1] = 0; }
}else if(mScrollPos.y() + getSize().y() > contentSize.y()) else if (mScrollPos.y() + getSize().y() > contentSize.y()) {
{ mScrollPos[1] = contentSize.y() - getSize().y();
mScrollPos[1] = contentSize.y() - getSize().y(); mAtEnd = true;
mAtEnd = true; }
}
if(mAtEnd) if (mAtEnd) {
{ mAutoScrollResetAccumulator += deltaTime;
mAutoScrollResetAccumulator += deltaTime; if (mAutoScrollResetAccumulator >= AUTO_SCROLL_RESET_DELAY)
if(mAutoScrollResetAccumulator >= AUTO_SCROLL_RESET_DELAY) reset();
reset(); }
}
GuiComponent::update(deltaTime); GuiComponent::update(deltaTime);
} }
//this should probably return a box to allow for when controls don't start at 0,0 // This should probably return a box to allow for when controls don't start at 0,0.
Vector2f ScrollableContainer::getContentSize() Vector2f ScrollableContainer::getContentSize()
{ {
Vector2f max(0, 0); Vector2f max(0, 0);
for(unsigned int i = 0; i < mChildren.size(); i++) for (unsigned int i = 0; i < mChildren.size(); i++) {
{ Vector2f pos(mChildren.at(i)->getPosition()[0], mChildren.at(i)->getPosition()[1]);
Vector2f pos(mChildren.at(i)->getPosition()[0], mChildren.at(i)->getPosition()[1]); Vector2f bottomRight = mChildren.at(i)->getSize() + pos;
Vector2f bottomRight = mChildren.at(i)->getSize() + pos; if (bottomRight.x() > max.x())
if(bottomRight.x() > max.x()) max.x() = bottomRight.x();
max.x() = bottomRight.x(); if (bottomRight.y() > max.y())
if(bottomRight.y() > max.y()) max.y() = bottomRight.y();
max.y() = bottomRight.y(); }
}
return max; return max;
} }
void ScrollableContainer::reset() void ScrollableContainer::reset()
{ {
mScrollPos = Vector2f(0, 0); mScrollPos = Vector2f(0, 0);
mAutoScrollResetAccumulator = 0; mAutoScrollResetAccumulator = 0;
mAutoScrollAccumulator = -mAutoScrollDelay + mAutoScrollSpeed; mAutoScrollAccumulator = -mAutoScrollDelay + mAutoScrollSpeed;
mAtEnd = false; mAtEnd = false;
} }

View file

@ -1,3 +1,10 @@
//
// ScrollableContainer.h
//
// Area containing scrollable information, for example the game description
// text container in the detailed, video and grid views.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_SCROLLABLE_CONTAINER_H #ifndef ES_CORE_COMPONENTS_SCROLLABLE_CONTAINER_H
#define ES_CORE_COMPONENTS_SCROLLABLE_CONTAINER_H #define ES_CORE_COMPONENTS_SCROLLABLE_CONTAINER_H
@ -7,26 +14,26 @@
class ScrollableContainer : public GuiComponent class ScrollableContainer : public GuiComponent
{ {
public: public:
ScrollableContainer(Window* window); ScrollableContainer(Window* window);
Vector2f getScrollPos() const; Vector2f getScrollPos() const;
void setScrollPos(const Vector2f& pos); void setScrollPos(const Vector2f& pos);
void setAutoScroll(bool autoScroll); void setAutoScroll(bool autoScroll);
void reset(); void reset();
void update(int deltaTime) override; void update(int deltaTime) override;
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
private: private:
Vector2f getContentSize(); Vector2f getContentSize();
Vector2f mScrollPos; Vector2f mScrollPos;
Vector2f mScrollDir; Vector2f mScrollDir;
int mAutoScrollDelay; // ms to wait before starting to autoscroll int mAutoScrollDelay; // ms to wait before starting to autoscroll.
int mAutoScrollSpeed; // ms to wait before scrolling down by mScrollDir int mAutoScrollSpeed; // ms to wait before scrolling down by mScrollDir.
int mAutoScrollAccumulator; int mAutoScrollAccumulator;
bool mAtEnd; bool mAtEnd;
int mAutoScrollResetAccumulator; int mAutoScrollResetAccumulator;
}; };
#endif // ES_CORE_COMPONENTS_SCROLLABLE_CONTAINER_H #endif // ES_CORE_COMPONENTS_SCROLLABLE_CONTAINER_H

View file

@ -1,3 +1,9 @@
//
// SliderComponent.cpp
//
// Slider to set value in a predefined range.
//
#include "components/SliderComponent.h" #include "components/SliderComponent.h"
#include "resources/Font.h" #include "resources/Font.h"
@ -5,138 +11,149 @@
#define MOVE_REPEAT_DELAY 500 #define MOVE_REPEAT_DELAY 500
#define MOVE_REPEAT_RATE 40 #define MOVE_REPEAT_RATE 40
SliderComponent::SliderComponent(Window* window, float min, float max, float increment, const std::string& suffix) : GuiComponent(window), SliderComponent::SliderComponent(
mMin(min), mMax(max), mSingleIncrement(increment), mMoveRate(0), mKnob(window), mSuffix(suffix) Window* window,
float min,
float max,
float increment,
const std::string& suffix)
: GuiComponent(window),
mMin(min),
mMax(max),
mSingleIncrement(increment),
mMoveRate(0),
mKnob(window),
mSuffix(suffix)
{ {
assert((min - max) != 0); assert((min - max) != 0);
// some sane default value // Some sane default value.
mValue = (max + min) / 2; mValue = (max + min) / 2;
mKnob.setOrigin(0.5f, 0.5f); mKnob.setOrigin(0.5f, 0.5f);
mKnob.setImage(":/graphics/slider_knob.svg"); mKnob.setImage(":/graphics/slider_knob.svg");
setSize(Renderer::getScreenWidth() * 0.15f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()); setSize(Renderer::getScreenWidth() * 0.15f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight());
} }
bool SliderComponent::input(InputConfig* config, Input input) bool SliderComponent::input(InputConfig* config, Input input)
{ {
if(config->isMappedLike("left", input)) if (config->isMappedLike("left", input)) {
{ if (input.value)
if(input.value) setValue(mValue - mSingleIncrement);
setValue(mValue - mSingleIncrement);
mMoveRate = input.value ? -mSingleIncrement : 0; mMoveRate = input.value ? -mSingleIncrement : 0;
mMoveAccumulator = -MOVE_REPEAT_DELAY; mMoveAccumulator = -MOVE_REPEAT_DELAY;
return true; return true;
} }
if(config->isMappedLike("right", input)) if (config->isMappedLike("right", input)) {
{ if (input.value)
if(input.value) setValue(mValue + mSingleIncrement);
setValue(mValue + mSingleIncrement);
mMoveRate = input.value ? mSingleIncrement : 0; mMoveRate = input.value ? mSingleIncrement : 0;
mMoveAccumulator = -MOVE_REPEAT_DELAY; mMoveAccumulator = -MOVE_REPEAT_DELAY;
return true; return true;
} }
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
void SliderComponent::update(int deltaTime) void SliderComponent::update(int deltaTime)
{ {
if(mMoveRate != 0) if (mMoveRate != 0) {
{ mMoveAccumulator += deltaTime;
mMoveAccumulator += deltaTime; while (mMoveAccumulator >= MOVE_REPEAT_RATE) {
while(mMoveAccumulator >= MOVE_REPEAT_RATE) setValue(mValue + mMoveRate);
{ mMoveAccumulator -= MOVE_REPEAT_RATE;
setValue(mValue + mMoveRate); }
mMoveAccumulator -= MOVE_REPEAT_RATE; }
}
}
GuiComponent::update(deltaTime); GuiComponent::update(deltaTime);
} }
void SliderComponent::render(const Transform4x4f& parentTrans) void SliderComponent::render(const Transform4x4f& parentTrans)
{ {
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
// render suffix // Render suffix.
if(mValueCache) if (mValueCache)
mFont->renderTextCache(mValueCache.get()); mFont->renderTextCache(mValueCache.get());
float width = mSize.x() - mKnob.getSize().x() - (mValueCache ? mValueCache->metrics.size.x() + 4 : 0); float width = mSize.x() - mKnob.getSize().x() -
(mValueCache ? mValueCache->metrics.size.x() + 4 : 0);
//render line // Render line.
const float lineWidth = 2; const float lineWidth = 2;
Renderer::drawRect(mKnob.getSize().x() / 2, mSize.y() / 2 - lineWidth / 2, width, lineWidth, 0x777777FF, 0x777777FF); Renderer::drawRect(mKnob.getSize().x() / 2, mSize.y() / 2 -
lineWidth / 2, width, lineWidth, 0x777777FF, 0x777777FF);
//render knob // Render knob.
mKnob.render(trans); mKnob.render(trans);
GuiComponent::renderChildren(trans); GuiComponent::renderChildren(trans);
} }
void SliderComponent::setValue(float value) void SliderComponent::setValue(float value)
{ {
mValue = value; mValue = value;
if(mValue < mMin) if (mValue < mMin)
mValue = mMin; mValue = mMin;
else if(mValue > mMax) else if (mValue > mMax)
mValue = mMax; mValue = mMax;
onValueChanged(); onValueChanged();
} }
float SliderComponent::getValue() float SliderComponent::getValue()
{ {
return mValue; return mValue;
} }
void SliderComponent::onSizeChanged() void SliderComponent::onSizeChanged()
{ {
if(!mSuffix.empty()) if (!mSuffix.empty())
mFont = Font::get((int)(mSize.y()), FONT_PATH_LIGHT); mFont = Font::get((int)(mSize.y()), FONT_PATH_LIGHT);
onValueChanged(); onValueChanged();
} }
void SliderComponent::onValueChanged() void SliderComponent::onValueChanged()
{ {
// update suffix textcache // Update suffix textcache.
if(mFont) if (mFont) {
{ std::stringstream ss;
std::stringstream ss; ss << std::fixed;
ss << std::fixed; ss.precision(0);
ss.precision(0); ss << mValue;
ss << mValue; ss << mSuffix;
ss << mSuffix; const std::string val = ss.str();
const std::string val = ss.str();
ss.str(""); ss.str("");
ss.clear(); ss.clear();
ss << std::fixed; ss << std::fixed;
ss.precision(0); ss.precision(0);
ss << mMax; ss << mMax;
ss << mSuffix; ss << mSuffix;
const std::string max = ss.str(); const std::string max = ss.str();
Vector2f textSize = mFont->sizeText(max); Vector2f textSize = mFont->sizeText(max);
mValueCache = std::shared_ptr<TextCache>(mFont->buildTextCache(val, mSize.x() - textSize.x(), (mSize.y() - textSize.y()) / 2, 0x777777FF)); mValueCache = std::shared_ptr<TextCache>(mFont->buildTextCache(val, mSize.x() -
mValueCache->metrics.size[0] = textSize.x(); // fudge the width textSize.x(), (mSize.y() - textSize.y()) / 2, 0x777777FF));
} mValueCache->metrics.size[0] = textSize.x(); // Fudge the width.
}
// update knob position/size // Update knob position/size.
mKnob.setResize(0, mSize.y() * 0.7f); mKnob.setResize(0, mSize.y() * 0.7f);
float lineLength = mSize.x() - mKnob.getSize().x() - (mValueCache ? mValueCache->metrics.size.x() + 4 : 0); float lineLength = mSize.x() - mKnob.getSize().x() -
mKnob.setPosition(((mValue + mMin) / mMax) * lineLength + mKnob.getSize().x()/2, mSize.y() / 2); (mValueCache ? mValueCache->metrics.size.x() + 4 : 0);
mKnob.setPosition(((mValue + mMin) / mMax) * lineLength +
mKnob.getSize().x()/2, mSize.y() / 2);
} }
std::vector<HelpPrompt> SliderComponent::getHelpPrompts() std::vector<HelpPrompt> SliderComponent::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("left/right", "change value")); prompts.push_back(HelpPrompt("left/right", "change value"));
return prompts; return prompts;
} }

View file

@ -1,3 +1,9 @@
//
// SliderComponent.h
//
// Slider to set value in a predefined range.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_SLIDER_COMPONENT_H #ifndef ES_CORE_COMPONENTS_SLIDER_COMPONENT_H
#define ES_CORE_COMPONENTS_SLIDER_COMPONENT_H #define ES_CORE_COMPONENTS_SLIDER_COMPONENT_H
@ -12,34 +18,40 @@ class TextCache;
class SliderComponent : public GuiComponent class SliderComponent : public GuiComponent
{ {
public: public:
//Minimum value (far left of the slider), maximum value (far right of the slider), increment size (how much just pressing L/R moves by), unit to display (optional). // Minimum value (far left of the slider), maximum value (far right of the slider),
SliderComponent(Window* window, float min, float max, float increment, const std::string& suffix = ""); // increment size (how much just pressing L/R moves by), unit to display (optional).
SliderComponent(
Window* window,
float min,
float max,
float increment,
const std::string& suffix = "");
void setValue(float val); void setValue(float val);
float getValue(); float getValue();
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override; void update(int deltaTime) override;
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
void onSizeChanged() override; void onSizeChanged() override;
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
private: private:
void onValueChanged(); void onValueChanged();
float mMin, mMax; float mMin, mMax;
float mValue; float mValue;
float mSingleIncrement; float mSingleIncrement;
float mMoveRate; float mMoveRate;
int mMoveAccumulator; int mMoveAccumulator;
ImageComponent mKnob; ImageComponent mKnob;
std::string mSuffix; std::string mSuffix;
std::shared_ptr<Font> mFont; std::shared_ptr<Font> mFont;
std::shared_ptr<TextCache> mValueCache; std::shared_ptr<TextCache> mValueCache;
}; };
#endif // ES_CORE_COMPONENTS_SLIDER_COMPONENT_H #endif // ES_CORE_COMPONENTS_SLIDER_COMPONENT_H

View file

@ -1,295 +1,319 @@
//
// TextComponent.cpp
//
// Displays text.
//
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
TextComponent::TextComponent(Window* window) : GuiComponent(window), TextComponent::TextComponent(
mFont(Font::get(FONT_SIZE_MEDIUM)), mUppercase(false), mColor(0x000000FF), mAutoCalcExtent(true, true), Window* window)
mHorizontalAlignment(ALIGN_LEFT), mVerticalAlignment(ALIGN_CENTER), mLineSpacing(1.5f), mBgColor(0), : GuiComponent(window),
mRenderBackground(false) mFont(Font::get(FONT_SIZE_MEDIUM)),
mUppercase(false),
mColor(0x000000FF),
mAutoCalcExtent(true, true),
mHorizontalAlignment(ALIGN_LEFT),
mVerticalAlignment(ALIGN_CENTER),
mLineSpacing(1.5f),
mBgColor(0),
mRenderBackground(false)
{ {
} }
TextComponent::TextComponent(Window* window, const std::string& text, const std::shared_ptr<Font>& font, unsigned int color, Alignment align, TextComponent::TextComponent(
Vector3f pos, Vector2f size, unsigned int bgcolor) : GuiComponent(window), Window* window,
mFont(NULL), mUppercase(false), mColor(0x000000FF), mAutoCalcExtent(true, true), const std::string& text,
mHorizontalAlignment(align), mVerticalAlignment(ALIGN_CENTER), mLineSpacing(1.5f), mBgColor(0), const std::shared_ptr<Font>& font,
mRenderBackground(false) unsigned int color,
Alignment align,
Vector3f pos,
Vector2f size,
unsigned int bgcolor)
: GuiComponent(window),
mFont(nullptr),
mUppercase(false),
mColor(0x000000FF),
mAutoCalcExtent(true, true),
mHorizontalAlignment(align),
mVerticalAlignment(ALIGN_CENTER),
mLineSpacing(1.5f),
mBgColor(0),
mRenderBackground(false)
{ {
setFont(font); setFont(font);
setColor(color); setColor(color);
setBackgroundColor(bgcolor); setBackgroundColor(bgcolor);
setText(text); setText(text);
setPosition(pos); setPosition(pos);
setSize(size); setSize(size);
} }
void TextComponent::onSizeChanged() void TextComponent::onSizeChanged()
{ {
mAutoCalcExtent = Vector2i((getSize().x() == 0), (getSize().y() == 0)); mAutoCalcExtent = Vector2i((getSize().x() == 0), (getSize().y() == 0));
onTextChanged(); onTextChanged();
} }
void TextComponent::setFont(const std::shared_ptr<Font>& font) void TextComponent::setFont(const std::shared_ptr<Font>& font)
{ {
mFont = font; mFont = font;
onTextChanged(); onTextChanged();
} }
// Set the color of the font/text // Set the color of the font/text.
void TextComponent::setColor(unsigned int color) void TextComponent::setColor(unsigned int color)
{ {
mColor = color; mColor = color;
mColorOpacity = mColor & 0x000000FF; mColorOpacity = mColor & 0x000000FF;
onColorChanged(); onColorChanged();
} }
// Set the color of the background box // Set the color of the background box.
void TextComponent::setBackgroundColor(unsigned int color) void TextComponent::setBackgroundColor(unsigned int color)
{ {
mBgColor = color; mBgColor = color;
mBgColorOpacity = mBgColor & 0x000000FF; mBgColorOpacity = mBgColor & 0x000000FF;
} }
void TextComponent::setRenderBackground(bool render) void TextComponent::setRenderBackground(bool render)
{ {
mRenderBackground = render; mRenderBackground = render;
} }
// Scale the opacity // Scale the opacity.
void TextComponent::setOpacity(unsigned char opacity) void TextComponent::setOpacity(unsigned char opacity)
{ {
// This method is mostly called to do fading in-out of the Text component element. // This method is mostly called to do fading in-out of the Text component element.
// Therefore, we assume here that opacity is a fractional value (expressed as an int 0-255), // Therefore, we assume here that opacity is a fractional value (expressed as an int 0-255),
// of the opacity originally set with setColor() or setBackgroundColor(). // of the opacity originally set with setColor() or setBackgroundColor().
unsigned char o = (unsigned char)((float)opacity / 255.f * (float) mColorOpacity); unsigned char o = (unsigned char)((float)opacity / 255.f * (float) mColorOpacity);
mColor = (mColor & 0xFFFFFF00) | (unsigned char) o; mColor = (mColor & 0xFFFFFF00) | (unsigned char) o;
unsigned char bgo = (unsigned char)((float)opacity / 255.f * (float)mBgColorOpacity); unsigned char bgo = (unsigned char)((float)opacity / 255.f * (float)mBgColorOpacity);
mBgColor = (mBgColor & 0xFFFFFF00) | (unsigned char)bgo; mBgColor = (mBgColor & 0xFFFFFF00) | (unsigned char)bgo;
onColorChanged(); onColorChanged();
GuiComponent::setOpacity(opacity);
GuiComponent::setOpacity(opacity);
} }
unsigned char TextComponent::getOpacity() const unsigned char TextComponent::getOpacity() const
{ {
return mColor & 0x000000FF; return mColor & 0x000000FF;
} }
void TextComponent::setText(const std::string& text) void TextComponent::setText(const std::string& text)
{ {
mText = text; mText = text;
onTextChanged(); onTextChanged();
} }
void TextComponent::setUppercase(bool uppercase) void TextComponent::setUppercase(bool uppercase)
{ {
mUppercase = uppercase; mUppercase = uppercase;
onTextChanged(); onTextChanged();
} }
void TextComponent::render(const Transform4x4f& parentTrans) void TextComponent::render(const Transform4x4f& parentTrans)
{ {
if (!isVisible()) if (!isVisible())
return; return;
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
if (mRenderBackground) if (mRenderBackground) {
{ Renderer::setMatrix(trans);
Renderer::setMatrix(trans); Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), mBgColor, mBgColor);
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), mBgColor, mBgColor); }
}
if(mTextCache) if (mTextCache) {
{ const Vector2f& textSize = mTextCache->metrics.size;
const Vector2f& textSize = mTextCache->metrics.size; float yOff = 0;
float yOff = 0; switch (mVerticalAlignment) {
switch(mVerticalAlignment) case ALIGN_TOP:
{ yOff = 0;
case ALIGN_TOP: break;
yOff = 0; case ALIGN_BOTTOM:
break; yOff = (getSize().y() - textSize.y());
case ALIGN_BOTTOM: break;
yOff = (getSize().y() - textSize.y()); case ALIGN_CENTER:
break; yOff = (getSize().y() - textSize.y()) / 2.0f;
case ALIGN_CENTER: break;
yOff = (getSize().y() - textSize.y()) / 2.0f; default:
break; break;
default: }
break; Vector3f off(0, yOff, 0);
}
Vector3f off(0, yOff, 0);
if(Settings::getInstance()->getBool("DebugText")) if (Settings::getInstance()->getBool("DebugText")) {
{ // Draw the "textbox" area, what we are aligned within.
// draw the "textbox" area, what we are aligned within Renderer::setMatrix(trans);
Renderer::setMatrix(trans); Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0xFF000033, 0xFF000033);
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0xFF000033, 0xFF000033); }
}
trans.translate(off); trans.translate(off);
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
// draw the text area, where the text actually is going // Draw the text area, where the text actually is located.
if(Settings::getInstance()->getBool("DebugText")) if (Settings::getInstance()->getBool("DebugText")) {
{ switch (mHorizontalAlignment) {
switch(mHorizontalAlignment) case ALIGN_LEFT:
{ Renderer::drawRect(0.0f, 0.0f, mTextCache->metrics.size.x(),
case ALIGN_LEFT: mTextCache->metrics.size.y(), 0x00000033, 0x00000033);
Renderer::drawRect(0.0f, 0.0f, mTextCache->metrics.size.x(), mTextCache->metrics.size.y(), 0x00000033, 0x00000033); break;
break; case ALIGN_CENTER:
case ALIGN_CENTER: Renderer::drawRect((mSize.x() - mTextCache->metrics.size.x()) / 2.0f, 0.0f,
Renderer::drawRect((mSize.x() - mTextCache->metrics.size.x()) / 2.0f, 0.0f, mTextCache->metrics.size.x(), mTextCache->metrics.size.y(), 0x00000033, 0x00000033); mTextCache->metrics.size.x(), mTextCache->metrics.size.y(),
break; 0x00000033, 0x00000033);
case ALIGN_RIGHT: break;
Renderer::drawRect(mSize.x() - mTextCache->metrics.size.x(), 0.0f, mTextCache->metrics.size.x(), mTextCache->metrics.size.y(), 0x00000033, 0x00000033); case ALIGN_RIGHT:
break; Renderer::drawRect(mSize.x() - mTextCache->metrics.size.x(), 0.0f,
default: mTextCache->metrics.size.x(), mTextCache->metrics.size.y(),
break; 0x00000033, 0x00000033);
} break;
} default:
mFont->renderTextCache(mTextCache.get()); break;
} }
}
mFont->renderTextCache(mTextCache.get());
}
} }
void TextComponent::calculateExtent() void TextComponent::calculateExtent()
{ {
if(mAutoCalcExtent.x()) if (mAutoCalcExtent.x()) {
{ mSize = mFont->sizeText(mUppercase ? Utils::String::toUpper(mText) : mText, mLineSpacing);
mSize = mFont->sizeText(mUppercase ? Utils::String::toUpper(mText) : mText, mLineSpacing); }
}else{ else {
if(mAutoCalcExtent.y()) if (mAutoCalcExtent.y())
{ mSize[1] = mFont->sizeWrappedText(mUppercase ? Utils::String::toUpper(mText)
mSize[1] = mFont->sizeWrappedText(mUppercase ? Utils::String::toUpper(mText) : mText, getSize().x(), mLineSpacing).y(); : mText, getSize().x(), mLineSpacing).y();
} }
}
} }
void TextComponent::onTextChanged() void TextComponent::onTextChanged()
{ {
calculateExtent(); calculateExtent();
if(!mFont || mText.empty()) if (!mFont || mText.empty()) {
{ mTextCache.reset();
mTextCache.reset(); return;
return; }
}
std::string text = mUppercase ? Utils::String::toUpper(mText) : mText; std::string text = mUppercase ? Utils::String::toUpper(mText) : mText;
std::shared_ptr<Font> f = mFont; std::shared_ptr<Font> f = mFont;
const bool isMultiline = (mSize.y() == 0 || mSize.y() > f->getHeight()*1.2f); const bool isMultiline = (mSize.y() == 0 || mSize.y() > f->getHeight()*1.2f);
bool addAbbrev = false; bool addAbbrev = false;
if(!isMultiline) if (!isMultiline) {
{ size_t newline = text.find('\n');
size_t newline = text.find('\n'); // Single line of text - stop at the first newline since it'll mess everything up.
text = text.substr(0, newline); // single line of text - stop at the first newline since it'll mess everything up text = text.substr(0, newline);
addAbbrev = newline != std::string::npos; addAbbrev = newline != std::string::npos;
} }
Vector2f size = f->sizeText(text); Vector2f size = f->sizeText(text);
if(!isMultiline && mSize.x() && text.size() && (size.x() > mSize.x() || addAbbrev)) if (!isMultiline && mSize.x() && text.size() && (size.x() > mSize.x() || addAbbrev)) {
{ // Abbreviate text.
// abbreviate text const std::string abbrev = "...";
const std::string abbrev = "..."; Vector2f abbrevSize = f->sizeText(abbrev);
Vector2f abbrevSize = f->sizeText(abbrev);
while(text.size() && size.x() + abbrevSize.x() > mSize.x()) while (text.size() && size.x() + abbrevSize.x() > mSize.x()) {
{ size_t newSize = Utils::String::prevCursor(text, text.size());
size_t newSize = Utils::String::prevCursor(text, text.size()); text.erase(newSize, text.size() - newSize);
text.erase(newSize, text.size() - newSize); size = f->sizeText(text);
size = f->sizeText(text); }
}
text.append(abbrev); text.append(abbrev);
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(text, Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing)); mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(text, Vector2f(0, 0),
}else{ (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing));
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(f->wrapText(text, mSize.x()), Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing)); }
} else {
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(f->wrapText(
text, mSize.x()), Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(),
mHorizontalAlignment, mLineSpacing));
}
} }
void TextComponent::onColorChanged() void TextComponent::onColorChanged()
{ {
if(mTextCache) if (mTextCache)
{ mTextCache->setColor(mColor);
mTextCache->setColor(mColor);
}
} }
void TextComponent::setHorizontalAlignment(Alignment align) void TextComponent::setHorizontalAlignment(Alignment align)
{ {
mHorizontalAlignment = align; mHorizontalAlignment = align;
onTextChanged(); onTextChanged();
} }
void TextComponent::setVerticalAlignment(Alignment align) void TextComponent::setVerticalAlignment(Alignment align)
{ {
mVerticalAlignment = align; mVerticalAlignment = align;
} }
void TextComponent::setLineSpacing(float spacing) void TextComponent::setLineSpacing(float spacing)
{ {
mLineSpacing = spacing; mLineSpacing = spacing;
onTextChanged(); onTextChanged();
} }
void TextComponent::setValue(const std::string& value) void TextComponent::setValue(const std::string& value)
{ {
setText(value); setText(value);
} }
std::string TextComponent::getValue() const std::string TextComponent::getValue() const
{ {
return mText; return mText;
} }
void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties)
{ {
GuiComponent::applyTheme(theme, view, element, properties); GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags; using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "text"); const ThemeData::ThemeElement* elem = theme->getElement(view, element, "text");
if(!elem) if (!elem)
return; return;
if (properties & COLOR && elem->has("color")) if (properties & COLOR && elem->has("color"))
setColor(elem->get<unsigned int>("color")); setColor(elem->get<unsigned int>("color"));
setRenderBackground(false); setRenderBackground(false);
if (properties & COLOR && elem->has("backgroundColor")) { if (properties & COLOR && elem->has("backgroundColor")) {
setBackgroundColor(elem->get<unsigned int>("backgroundColor")); setBackgroundColor(elem->get<unsigned int>("backgroundColor"));
setRenderBackground(true); setRenderBackground(true);
} }
if(properties & ALIGNMENT && elem->has("alignment")) if (properties & ALIGNMENT && elem->has("alignment")) {
{ std::string str = elem->get<std::string>("alignment");
std::string str = elem->get<std::string>("alignment"); if (str == "left")
if(str == "left") setHorizontalAlignment(ALIGN_LEFT);
setHorizontalAlignment(ALIGN_LEFT); else if (str == "center")
else if(str == "center") setHorizontalAlignment(ALIGN_CENTER);
setHorizontalAlignment(ALIGN_CENTER); else if (str == "right")
else if(str == "right") setHorizontalAlignment(ALIGN_RIGHT);
setHorizontalAlignment(ALIGN_RIGHT); else
else LOG(LogError) << "Unknown text alignment string: " << str;
LOG(LogError) << "Unknown text alignment string: " << str; }
}
if(properties & TEXT && elem->has("text")) if (properties & TEXT && elem->has("text"))
setText(elem->get<std::string>("text")); setText(elem->get<std::string>("text"));
if(properties & FORCE_UPPERCASE && elem->has("forceUppercase")) if (properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
setUppercase(elem->get<bool>("forceUppercase")); setUppercase(elem->get<bool>("forceUppercase"));
if(properties & LINE_SPACING && elem->has("lineSpacing")) if (properties & LINE_SPACING && elem->has("lineSpacing"))
setLineSpacing(elem->get<float>("lineSpacing")); setLineSpacing(elem->get<float>("lineSpacing"));
setFont(Font::getFromTheme(elem, properties, mFont)); setFont(Font::getFromTheme(elem, properties, mFont));
} }

View file

@ -1,3 +1,9 @@
//
// TextComponent.h
//
// Displays text.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_TEXT_COMPONENT_H #ifndef ES_CORE_COMPONENTS_TEXT_COMPONENT_H
#define ES_CORE_COMPONENTS_TEXT_COMPONENT_H #define ES_CORE_COMPONENTS_TEXT_COMPONENT_H
@ -9,63 +15,73 @@ class ThemeData;
// Used to display text. // Used to display text.
// TextComponent::setSize(x, y) works a little differently than most components: // TextComponent::setSize(x, y) works a little differently than most components:
// * (0, 0) - will automatically calculate a size that fits the text on one line (expand horizontally) // * (0, 0) - Will automatically calculate a size that fits
// * (x != 0, 0) - wrap text so that it does not reach beyond x. Will automatically calculate a vertical size (expand vertically). // the text on one line (expand horizontally).
// * (x != 0, y <= fontHeight) - will truncate text so it fits within this box. // * (x != 0, 0) - Wrap text so that it does not reach beyond x. Will
// automatically calculate a vertical size (expand vertically).
// * (x != 0, y <= fontHeight) - Will truncate text so it fits within this box.
class TextComponent : public GuiComponent class TextComponent : public GuiComponent
{ {
public: public:
TextComponent(Window* window); TextComponent(Window* window);
TextComponent(Window* window, const std::string& text, const std::shared_ptr<Font>& font, unsigned int color = 0x000000FF, Alignment align = ALIGN_LEFT, TextComponent(
Vector3f pos = Vector3f::Zero(), Vector2f size = Vector2f::Zero(), unsigned int bgcolor = 0x00000000); Window* window,
const std::string& text,
const std::shared_ptr<Font>& font,
unsigned int color = 0x000000FF,
Alignment align = ALIGN_LEFT,
Vector3f pos = Vector3f::Zero(),
Vector2f size = Vector2f::Zero(),
unsigned int bgcolor = 0x00000000);
void setFont(const std::shared_ptr<Font>& font); void setFont(const std::shared_ptr<Font>& font);
void setUppercase(bool uppercase); void setUppercase(bool uppercase);
void onSizeChanged() override; void onSizeChanged() override;
void setText(const std::string& text); void setText(const std::string& text);
void setColor(unsigned int color) override; void setColor(unsigned int color) override;
void setHorizontalAlignment(Alignment align); void setHorizontalAlignment(Alignment align);
void setVerticalAlignment(Alignment align); void setVerticalAlignment(Alignment align);
void setLineSpacing(float spacing); void setLineSpacing(float spacing);
void setBackgroundColor(unsigned int color); void setBackgroundColor(unsigned int color);
void setRenderBackground(bool render); void setRenderBackground(bool render);
unsigned int getColor() const override { return mColor; }; unsigned int getColor() const override { return mColor; };
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
std::string getValue() const override; std::string getValue() const override;
void setValue(const std::string& value) override; void setValue(const std::string& value) override;
unsigned char getOpacity() const override; unsigned char getOpacity() const override;
void setOpacity(unsigned char opacity) override; void setOpacity(unsigned char opacity) override;
inline std::shared_ptr<Font> getFont() const { return mFont; } inline std::shared_ptr<Font> getFont() const { return mFont; }
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override; virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties) override;
protected: protected:
virtual void onTextChanged(); virtual void onTextChanged();
std::string mText; std::string mText;
std::shared_ptr<Font> mFont; std::shared_ptr<Font> mFont;
private: private:
void calculateExtent(); void calculateExtent();
void onColorChanged(); void onColorChanged();
unsigned int mColor; unsigned int mColor;
unsigned int mBgColor; unsigned int mBgColor;
unsigned char mColorOpacity; unsigned char mColorOpacity;
unsigned char mBgColorOpacity; unsigned char mBgColorOpacity;
bool mRenderBackground; bool mRenderBackground;
bool mUppercase; bool mUppercase;
Vector2i mAutoCalcExtent; Vector2i mAutoCalcExtent;
std::shared_ptr<TextCache> mTextCache; std::shared_ptr<TextCache> mTextCache;
Alignment mHorizontalAlignment; Alignment mHorizontalAlignment;
Alignment mVerticalAlignment; Alignment mVerticalAlignment;
float mLineSpacing; float mLineSpacing;
}; };
#endif // ES_CORE_COMPONENTS_TEXT_COMPONENT_H #endif // ES_CORE_COMPONENTS_TEXT_COMPONENT_H

View file

@ -1,7 +1,7 @@
// //
// TextListComponent.h // TextListComponent.h
// //
// Used for displaying and navigating the gamelists. // Used for displaying and navigating the gamelists.
// //
#pragma once #pragma once
@ -18,8 +18,8 @@
class TextCache; class TextCache;
struct TextListData { struct TextListData {
unsigned int colorId; unsigned int colorId;
std::shared_ptr<TextCache> textCache; std::shared_ptr<TextCache> textCache;
}; };
// A graphical list. Supports multiple colors for rows and scrolling. // A graphical list. Supports multiple colors for rows and scrolling.
@ -27,441 +27,441 @@ template <typename T>
class TextListComponent : public IList<TextListData, T> class TextListComponent : public IList<TextListData, T>
{ {
protected: protected:
using IList<TextListData, T>::mEntries; using IList<TextListData, T>::mEntries;
using IList<TextListData, T>::listUpdate; using IList<TextListData, T>::listUpdate;
using IList<TextListData, T>::listInput; using IList<TextListData, T>::listInput;
using IList<TextListData, T>::listRenderTitleOverlay; using IList<TextListData, T>::listRenderTitleOverlay;
using IList<TextListData, T>::getTransform; using IList<TextListData, T>::getTransform;
using IList<TextListData, T>::mSize; using IList<TextListData, T>::mSize;
using IList<TextListData, T>::mCursor; using IList<TextListData, T>::mCursor;
// The following change is required for compilation with Clang. // The following change is required for compilation with Clang.
// http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2070 // http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2070
// using IList<TextListData, T>::Entry; // using IList<TextListData, T>::Entry;
using IList<TextListData, T>::IList; using IList<TextListData, T>::IList;
public: public:
using IList<TextListData, T>::size; using IList<TextListData, T>::size;
using IList<TextListData, T>::isScrolling; using IList<TextListData, T>::isScrolling;
using IList<TextListData, T>::stopScrolling; using IList<TextListData, T>::stopScrolling;
TextListComponent(Window* window); TextListComponent(Window* window);
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override; void update(int deltaTime) override;
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties) override; const std::string& element, unsigned int properties) override;
void add(const std::string& name, const T& obj, unsigned int colorId); void add(const std::string& name, const T& obj, unsigned int colorId);
enum Alignment { enum Alignment {
ALIGN_LEFT, ALIGN_LEFT,
ALIGN_CENTER, ALIGN_CENTER,
ALIGN_RIGHT ALIGN_RIGHT
}; };
inline void setAlignment(Alignment align) { mAlignment = align; } inline void setAlignment(Alignment align) { mAlignment = align; }
inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func) inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func)
{ mCursorChangedCallback = func; } { mCursorChangedCallback = func; }
inline void setFont(const std::shared_ptr<Font>& font) inline void setFont(const std::shared_ptr<Font>& font)
{ {
mFont = font; mFont = font;
for (auto it = mEntries.begin(); it != mEntries.end(); it++) for (auto it = mEntries.begin(); it != mEntries.end(); it++)
it->data.textCache.reset(); it->data.textCache.reset();
} }
inline void setUppercase(bool /*uppercase*/) inline void setUppercase(bool /*uppercase*/)
{ {
mUppercase = true; mUppercase = true;
for (auto it = mEntries.begin(); it != mEntries.end(); it++) for (auto it = mEntries.begin(); it != mEntries.end(); it++)
it->data.textCache.reset(); it->data.textCache.reset();
} }
inline void setSelectorHeight(float selectorScale) { mSelectorHeight = selectorScale; } inline void setSelectorHeight(float selectorScale) { mSelectorHeight = selectorScale; }
inline void setSelectorOffsetY(float selectorOffsetY) { mSelectorOffsetY = selectorOffsetY; } inline void setSelectorOffsetY(float selectorOffsetY) { mSelectorOffsetY = selectorOffsetY; }
inline void setSelectorColor(unsigned int color) { mSelectorColor = color; } inline void setSelectorColor(unsigned int color) { mSelectorColor = color; }
inline void setSelectorColorEnd(unsigned int color) { mSelectorColorEnd = color; } inline void setSelectorColorEnd(unsigned int color) { mSelectorColorEnd = color; }
inline void setSelectorColorGradientHorizontal(bool horizontal) inline void setSelectorColorGradientHorizontal(bool horizontal)
{ mSelectorColorGradientHorizontal = horizontal; } { mSelectorColorGradientHorizontal = horizontal; }
inline void setSelectedColor(unsigned int color) { mSelectedColor = color; } inline void setSelectedColor(unsigned int color) { mSelectedColor = color; }
inline void setColor(unsigned int id, unsigned int color) { mColors[id] = color; } inline void setColor(unsigned int id, unsigned int color) { mColors[id] = color; }
inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; } inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; }
protected: protected:
virtual void onScroll() override { virtual void onScroll() override {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); } NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); }
virtual void onCursorChanged(const CursorState& state) override; virtual void onCursorChanged(const CursorState& state) override;
private: private:
int mMarqueeOffset; int mMarqueeOffset;
int mMarqueeOffset2; int mMarqueeOffset2;
int mMarqueeTime; int mMarqueeTime;
Alignment mAlignment; Alignment mAlignment;
float mHorizontalMargin; float mHorizontalMargin;
std::function<void(CursorState state)> mCursorChangedCallback; std::function<void(CursorState state)> mCursorChangedCallback;
std::shared_ptr<Font> mFont; std::shared_ptr<Font> mFont;
bool mUppercase; bool mUppercase;
float mLineSpacing; float mLineSpacing;
float mSelectorHeight; float mSelectorHeight;
float mSelectorOffsetY; float mSelectorOffsetY;
unsigned int mSelectorColor; unsigned int mSelectorColor;
unsigned int mSelectorColorEnd; unsigned int mSelectorColorEnd;
bool mSelectorColorGradientHorizontal = true; bool mSelectorColorGradientHorizontal = true;
unsigned int mSelectedColor; unsigned int mSelectedColor;
static const unsigned int COLOR_ID_COUNT = 2; static const unsigned int COLOR_ID_COUNT = 2;
unsigned int mColors[COLOR_ID_COUNT]; unsigned int mColors[COLOR_ID_COUNT];
ImageComponent mSelectorImage; ImageComponent mSelectorImage;
}; };
template <typename T> template <typename T>
TextListComponent<T>::TextListComponent(Window* window) : TextListComponent<T>::TextListComponent(Window* window) :
IList<TextListData, T>(window), mSelectorImage(window) IList<TextListData, T>(window), mSelectorImage(window)
{ {
mMarqueeOffset = 0; mMarqueeOffset = 0;
mMarqueeOffset2 = 0; mMarqueeOffset2 = 0;
mMarqueeTime = 0; mMarqueeTime = 0;
mHorizontalMargin = 0; mHorizontalMargin = 0;
mAlignment = ALIGN_CENTER; mAlignment = ALIGN_CENTER;
mFont = Font::get(FONT_SIZE_MEDIUM); mFont = Font::get(FONT_SIZE_MEDIUM);
mUppercase = false; mUppercase = false;
mLineSpacing = 1.5f; mLineSpacing = 1.5f;
mSelectorHeight = mFont->getSize() * 1.5f; mSelectorHeight = mFont->getSize() * 1.5f;
mSelectorOffsetY = 0; mSelectorOffsetY = 0;
mSelectorColor = 0x000000FF; mSelectorColor = 0x000000FF;
mSelectorColorEnd = 0x000000FF; mSelectorColorEnd = 0x000000FF;
mSelectorColorGradientHorizontal = true; mSelectorColorGradientHorizontal = true;
mSelectedColor = 0; mSelectedColor = 0;
mColors[0] = 0x0000FFFF; mColors[0] = 0x0000FFFF;
mColors[1] = 0x00FF00FF; mColors[1] = 0x00FF00FF;
} }
template <typename T> template <typename T>
void TextListComponent<T>::render(const Transform4x4f& parentTrans) void TextListComponent<T>::render(const Transform4x4f& parentTrans)
{ {
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
std::shared_ptr<Font>& font = mFont; std::shared_ptr<Font>& font = mFont;
if (size() == 0) if (size() == 0)
return; return;
const float entrySize = Math::max(font->getHeight(1.0), (float)font->getSize()) * mLineSpacing; const float entrySize = Math::max(font->getHeight(1.0), (float)font->getSize()) * mLineSpacing;
int startEntry = 0; int startEntry = 0;
// Number of entries that can fit on the screen simultaniously. // Number of entries that can fit on the screen simultaniously.
int screenCount = (int)(mSize.y() / entrySize + 0.5f); int screenCount = (int)(mSize.y() / entrySize + 0.5f);
if (size() >= screenCount) if (size() >= screenCount)
{ {
startEntry = mCursor - screenCount/2; startEntry = mCursor - screenCount/2;
if (startEntry < 0) if (startEntry < 0)
startEntry = 0; startEntry = 0;
if (startEntry >= size() - screenCount) if (startEntry >= size() - screenCount)
startEntry = size() - screenCount; startEntry = size() - screenCount;
} }
float y = 0; float y = 0;
int listCutoff = startEntry + screenCount; int listCutoff = startEntry + screenCount;
if (listCutoff > size()) if (listCutoff > size())
listCutoff = size(); listCutoff = size();
// Draw selector bar. // Draw selector bar.
if (startEntry < listCutoff) { if (startEntry < listCutoff) {
if (mSelectorImage.hasImage()) { if (mSelectorImage.hasImage()) {
mSelectorImage.setPosition(0.f, mSelectorImage.setPosition(0.f,
(mCursor - startEntry)*entrySize + mSelectorOffsetY, 0.f); (mCursor - startEntry)*entrySize + mSelectorOffsetY, 0.f);
mSelectorImage.render(trans); mSelectorImage.render(trans);
} }
else { else {
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
Renderer::drawRect( Renderer::drawRect(
0.0f, 0.0f,
(mCursor - startEntry)*entrySize + (mCursor - startEntry)*entrySize +
mSelectorOffsetY, mSelectorOffsetY,
mSize.x(), mSize.x(),
mSelectorHeight, mSelectorHeight,
mSelectorColor, mSelectorColor,
mSelectorColorEnd, mSelectorColorEnd,
mSelectorColorGradientHorizontal); mSelectorColorGradientHorizontal);
} }
} }
// Clip to inside margins. // Clip to inside margins.
Vector3f dim(mSize.x(), mSize.y(), 0); Vector3f dim(mSize.x(), mSize.y(), 0);
dim = trans * dim - trans.translation(); dim = trans * dim - trans.translation();
Renderer::pushClipRect(Vector2i((int)(trans.translation().x() + mHorizontalMargin), Renderer::pushClipRect(Vector2i((int)(trans.translation().x() + mHorizontalMargin),
(int)trans.translation().y()), Vector2i((int)(dim.x() - mHorizontalMargin*2), (int)trans.translation().y()), Vector2i((int)(dim.x() - mHorizontalMargin*2),
(int)dim.y())); (int)dim.y()));
for (int i = startEntry; i < listCutoff; i++) { for (int i = startEntry; i < listCutoff; i++) {
typename IList<TextListData, T>::Entry& entry = mEntries.at((unsigned int)i); typename IList<TextListData, T>::Entry& entry = mEntries.at((unsigned int)i);
unsigned int color; unsigned int color;
if (mCursor == i && mSelectedColor) if (mCursor == i && mSelectedColor)
color = mSelectedColor; color = mSelectedColor;
else else
color = mColors[entry.data.colorId]; color = mColors[entry.data.colorId];
if (!entry.data.textCache) if (!entry.data.textCache)
entry.data.textCache = std::unique_ptr<TextCache> entry.data.textCache = std::unique_ptr<TextCache>
(font->buildTextCache(mUppercase ? (font->buildTextCache(mUppercase ?
Utils::String::toUpper(entry.name) : entry.name, 0, 0, 0x000000FF)); Utils::String::toUpper(entry.name) : entry.name, 0, 0, 0x000000FF));
entry.data.textCache->setColor(color); entry.data.textCache->setColor(color);
Vector3f offset(0, y, 0); Vector3f offset(0, y, 0);
switch (mAlignment) { switch (mAlignment) {
case ALIGN_LEFT: case ALIGN_LEFT:
offset[0] = mHorizontalMargin; offset[0] = mHorizontalMargin;
break; break;
case ALIGN_CENTER: case ALIGN_CENTER:
offset[0] = (int)((mSize.x() - entry.data.textCache->metrics.size.x()) / 2); offset[0] = (int)((mSize.x() - entry.data.textCache->metrics.size.x()) / 2);
if (offset[0] < mHorizontalMargin) if (offset[0] < mHorizontalMargin)
offset[0] = mHorizontalMargin; offset[0] = mHorizontalMargin;
break; break;
case ALIGN_RIGHT: case ALIGN_RIGHT:
offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x()); offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x());
offset[0] -= mHorizontalMargin; offset[0] -= mHorizontalMargin;
if (offset[0] < mHorizontalMargin) if (offset[0] < mHorizontalMargin)
offset[0] = mHorizontalMargin; offset[0] = mHorizontalMargin;
break; break;
} }
// Render text. // Render text.
Transform4x4f drawTrans = trans; Transform4x4f drawTrans = trans;
// Currently selected item text might be scrolling. // Currently selected item text might be scrolling.
if ((mCursor == i) && (mMarqueeOffset > 0)) if ((mCursor == i) && (mMarqueeOffset > 0))
drawTrans.translate(offset - Vector3f((float)mMarqueeOffset, 0, 0)); drawTrans.translate(offset - Vector3f((float)mMarqueeOffset, 0, 0));
else else
drawTrans.translate(offset); drawTrans.translate(offset);
Renderer::setMatrix(drawTrans); Renderer::setMatrix(drawTrans);
font->renderTextCache(entry.data.textCache.get()); font->renderTextCache(entry.data.textCache.get());
// Render currently selected item text again if marquee is // Render currently selected item text again if marquee is
// scrolled far enough for it to repeat. // scrolled far enough for it to repeat.
if ((mCursor == i) && (mMarqueeOffset2 < 0)) { if ((mCursor == i) && (mMarqueeOffset2 < 0)) {
drawTrans = trans; drawTrans = trans;
drawTrans.translate(offset - Vector3f((float)mMarqueeOffset2, 0, 0)); drawTrans.translate(offset - Vector3f((float)mMarqueeOffset2, 0, 0));
Renderer::setMatrix(drawTrans); Renderer::setMatrix(drawTrans);
font->renderTextCache(entry.data.textCache.get()); font->renderTextCache(entry.data.textCache.get());
} }
y += entrySize; y += entrySize;
} }
Renderer::popClipRect(); Renderer::popClipRect();
listRenderTitleOverlay(trans); listRenderTitleOverlay(trans);
GuiComponent::renderChildren(trans); GuiComponent::renderChildren(trans);
} }
template <typename T> template <typename T>
bool TextListComponent<T>::input(InputConfig* config, Input input) bool TextListComponent<T>::input(InputConfig* config, Input input)
{ {
if (size() > 0) { if (size() > 0) {
if (input.value != 0) { if (input.value != 0) {
if (config->isMappedLike("down", input)) { if (config->isMappedLike("down", input)) {
listInput(1); listInput(1);
return true; return true;
} }
if (config->isMappedLike("up", input)) { if (config->isMappedLike("up", input)) {
listInput(-1); listInput(-1);
return true; return true;
} }
if (config->isMappedLike("rightshoulder", input)) { if (config->isMappedLike("rightshoulder", input)) {
listInput(10); listInput(10);
return true; return true;
} }
if (config->isMappedLike("leftshoulder", input)) { if (config->isMappedLike("leftshoulder", input)) {
listInput(-10); listInput(-10);
return true; return true;
} }
if (config->isMappedLike("righttrigger", input)) { if (config->isMappedLike("righttrigger", input)) {
return this->listLastRow(); return this->listLastRow();
} }
if (config->isMappedLike("lefttrigger", input)) { if (config->isMappedLike("lefttrigger", input)) {
return this->listFirstRow(); return this->listFirstRow();
} }
} }
else { else {
if (config->isMappedLike("down", input) || if (config->isMappedLike("down", input) ||
config->isMappedLike("up", input) || config->isMappedLike("up", input) ||
config->isMappedLike("rightshoulder", input) || config->isMappedLike("rightshoulder", input) ||
config->isMappedLike("leftshoulder", input) || config->isMappedLike("leftshoulder", input) ||
config->isMappedLike("lefttrigger", input) || config->isMappedLike("lefttrigger", input) ||
config->isMappedLike("righttrigger", input)) config->isMappedLike("righttrigger", input))
stopScrolling(); stopScrolling();
} }
} }
// Explicitly stop the scrolling, otherwise it will go forever in case // Explicitly stop the scrolling, otherwise it will go forever in case
// the menu was openened or another gamelist was selected using the // the menu was openened or another gamelist was selected using the
// quick system selector etc. // quick system selector etc.
stopScrolling(); stopScrolling();
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
template <typename T> template <typename T>
void TextListComponent<T>::update(int deltaTime) void TextListComponent<T>::update(int deltaTime)
{ {
listUpdate(deltaTime); listUpdate(deltaTime);
if (!isScrolling() && size() > 0) { if (!isScrolling() && size() > 0) {
// Always reset the marquee offsets. // Always reset the marquee offsets.
mMarqueeOffset = 0; mMarqueeOffset = 0;
mMarqueeOffset2 = 0; mMarqueeOffset2 = 0;
// If we're not scrolling and this object's text goes outside our size, marquee it! // If we're not scrolling and this object's text goes outside our size, marquee it!
const float textLength = mFont->sizeText(mEntries.at((unsigned int)mCursor).name).x(); const float textLength = mFont->sizeText(mEntries.at((unsigned int)mCursor).name).x();
const float limit = mSize.x() - mHorizontalMargin * 2; const float limit = mSize.x() - mHorizontalMargin * 2;
if (textLength > limit) { if (textLength > limit) {
// Loop. // Loop.
// Pixels per second (based on nes-mini font at 1920x1080 to produce a speed of 200). // Pixels per second (based on nes-mini font at 1920x1080 to produce a speed of 200).
const float speed = mFont->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x() * 0.247f; const float speed = mFont->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x() * 0.247f;
const float delay = 3000; const float delay = 3000;
const float scrollLength = textLength; const float scrollLength = textLength;
const float returnLength = speed * 1.5f; const float returnLength = speed * 1.5f;
const float scrollTime = (scrollLength * 1000) / speed; const float scrollTime = (scrollLength * 1000) / speed;
const float returnTime = (returnLength * 1000) / speed; const float returnTime = (returnLength * 1000) / speed;
const int maxTime = (int)(delay + scrollTime + returnTime); const int maxTime = (int)(delay + scrollTime + returnTime);
mMarqueeTime += deltaTime; mMarqueeTime += deltaTime;
while (mMarqueeTime > maxTime) while (mMarqueeTime > maxTime)
mMarqueeTime -= maxTime; mMarqueeTime -= maxTime;
mMarqueeOffset = (int)(Math::Scroll::loop(delay, scrollTime + returnTime, mMarqueeOffset = (int)(Math::Scroll::loop(delay, scrollTime + returnTime,
(float)mMarqueeTime, scrollLength + returnLength)); (float)mMarqueeTime, scrollLength + returnLength));
if (mMarqueeOffset > (scrollLength - (limit - returnLength))) if (mMarqueeOffset > (scrollLength - (limit - returnLength)))
mMarqueeOffset2 = (int)(mMarqueeOffset - (scrollLength + returnLength)); mMarqueeOffset2 = (int)(mMarqueeOffset - (scrollLength + returnLength));
} }
} }
GuiComponent::update(deltaTime); GuiComponent::update(deltaTime);
} }
// List management stuff. // List management stuff.
template <typename T> template <typename T>
void TextListComponent<T>::add(const std::string& name, const T& obj, unsigned int color) void TextListComponent<T>::add(const std::string& name, const T& obj, unsigned int color)
{ {
assert(color < COLOR_ID_COUNT); assert(color < COLOR_ID_COUNT);
typename IList<TextListData, T>::Entry entry; typename IList<TextListData, T>::Entry entry;
entry.name = name; entry.name = name;
entry.object = obj; entry.object = obj;
entry.data.colorId = color; entry.data.colorId = color;
static_cast<IList< TextListData, T >*>(this)->add(entry); static_cast<IList< TextListData, T >*>(this)->add(entry);
} }
template <typename T> template <typename T>
void TextListComponent<T>::onCursorChanged(const CursorState& state) void TextListComponent<T>::onCursorChanged(const CursorState& state)
{ {
mMarqueeOffset = 0; mMarqueeOffset = 0;
mMarqueeOffset2 = 0; mMarqueeOffset2 = 0;
mMarqueeTime = 0; mMarqueeTime = 0;
if (mCursorChangedCallback) if (mCursorChangedCallback)
mCursorChangedCallback(state); mCursorChangedCallback(state);
} }
template <typename T> template <typename T>
void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme, void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view, const std::string& element, unsigned int properties) const std::string& view, const std::string& element, unsigned int properties)
{ {
GuiComponent::applyTheme(theme, view, element, properties); GuiComponent::applyTheme(theme, view, element, properties);
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "textlist"); const ThemeData::ThemeElement* elem = theme->getElement(view, element, "textlist");
if (!elem) if (!elem)
return; return;
using namespace ThemeFlags; using namespace ThemeFlags;
if (properties & COLOR) { if (properties & COLOR) {
if (elem->has("selectorColor")) { if (elem->has("selectorColor")) {
setSelectorColor(elem->get<unsigned int>("selectorColor")); setSelectorColor(elem->get<unsigned int>("selectorColor"));
setSelectorColorEnd(elem->get<unsigned int>("selectorColor")); setSelectorColorEnd(elem->get<unsigned int>("selectorColor"));
} }
if (elem->has("selectorColorEnd")) if (elem->has("selectorColorEnd"))
setSelectorColorEnd(elem->get<unsigned int>("selectorColorEnd")); setSelectorColorEnd(elem->get<unsigned int>("selectorColorEnd"));
if (elem->has("selectorGradientType")) if (elem->has("selectorGradientType"))
setSelectorColorGradientHorizontal(!(elem->get<std::string> setSelectorColorGradientHorizontal(!(elem->get<std::string>
("selectorGradientType").compare("horizontal"))); ("selectorGradientType").compare("horizontal")));
if (elem->has("selectedColor")) if (elem->has("selectedColor"))
setSelectedColor(elem->get<unsigned int>("selectedColor")); setSelectedColor(elem->get<unsigned int>("selectedColor"));
if (elem->has("primaryColor")) if (elem->has("primaryColor"))
setColor(0, elem->get<unsigned int>("primaryColor")); setColor(0, elem->get<unsigned int>("primaryColor"));
if (elem->has("secondaryColor")) if (elem->has("secondaryColor"))
setColor(1, elem->get<unsigned int>("secondaryColor")); setColor(1, elem->get<unsigned int>("secondaryColor"));
} }
setFont(Font::getFromTheme(elem, properties, mFont)); setFont(Font::getFromTheme(elem, properties, mFont));
const float selectorHeight = Math::max(mFont->getHeight(1.0), const float selectorHeight = Math::max(mFont->getHeight(1.0),
(float)mFont->getSize()) * mLineSpacing; (float)mFont->getSize()) * mLineSpacing;
setSelectorHeight(selectorHeight); setSelectorHeight(selectorHeight);
if (properties & ALIGNMENT) { if (properties & ALIGNMENT) {
if (elem->has("alignment")) { if (elem->has("alignment")) {
const std::string& str = elem->get<std::string>("alignment"); const std::string& str = elem->get<std::string>("alignment");
if (str == "left") if (str == "left")
setAlignment(ALIGN_LEFT); setAlignment(ALIGN_LEFT);
else if (str == "center") else if (str == "center")
setAlignment(ALIGN_CENTER); setAlignment(ALIGN_CENTER);
else if (str == "right") else if (str == "right")
setAlignment(ALIGN_RIGHT); setAlignment(ALIGN_RIGHT);
else else
LOG(LogError) << "Unknown TextListComponent alignment \"" << str << "\"!"; LOG(LogError) << "Unknown TextListComponent alignment \"" << str << "\"!";
} }
if (elem->has("horizontalMargin")) { if (elem->has("horizontalMargin")) {
mHorizontalMargin = elem->get<float>("horizontalMargin") * mHorizontalMargin = elem->get<float>("horizontalMargin") *
(this->mParent ? this->mParent->getSize().x() : (this->mParent ? this->mParent->getSize().x() :
(float)Renderer::getScreenWidth()); (float)Renderer::getScreenWidth());
} }
} }
if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) if (properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
setUppercase(elem->get<bool>("forceUppercase")); setUppercase(elem->get<bool>("forceUppercase"));
if (properties & LINE_SPACING) { if (properties & LINE_SPACING) {
if (elem->has("lineSpacing")) if (elem->has("lineSpacing"))
setLineSpacing(elem->get<float>("lineSpacing")); setLineSpacing(elem->get<float>("lineSpacing"));
if (elem->has("selectorHeight")) if (elem->has("selectorHeight"))
setSelectorHeight(elem->get<float>("selectorHeight") * Renderer::getScreenHeight()); setSelectorHeight(elem->get<float>("selectorHeight") * Renderer::getScreenHeight());
if (elem->has("selectorOffsetY")) { if (elem->has("selectorOffsetY")) {
float scale = this->mParent ? this->mParent->getSize().y() : (float)Renderer::getScreenHeight(); float scale = this->mParent ? this->mParent->getSize().y() : (float)Renderer::getScreenHeight();
setSelectorOffsetY(elem->get<float>("selectorOffsetY") * scale); setSelectorOffsetY(elem->get<float>("selectorOffsetY") * scale);
} }
else { else {
setSelectorOffsetY(0.0); setSelectorOffsetY(0.0);
} }
} }
if (elem->has("selectorImagePath")) { if (elem->has("selectorImagePath")) {
std::string path = elem->get<std::string>("selectorImagePath"); std::string path = elem->get<std::string>("selectorImagePath");
bool tile = elem->has("selectorImageTile") && elem->get<bool>("selectorImageTile"); bool tile = elem->has("selectorImageTile") && elem->get<bool>("selectorImageTile");
mSelectorImage.setImage(path, tile); mSelectorImage.setImage(path, tile);
mSelectorImage.setSize(mSize.x(), mSelectorHeight); mSelectorImage.setSize(mSize.x(), mSelectorHeight);
mSelectorImage.setColorShift(mSelectorColor); mSelectorImage.setColorShift(mSelectorColor);
mSelectorImage.setColorShiftEnd(mSelectorColorEnd); mSelectorImage.setColorShiftEnd(mSelectorColorEnd);
} }
else { else {
mSelectorImage.setImage(""); mSelectorImage.setImage("");
} }
} }
#endif // ES_APP_COMPONENTS_TEXT_LIST_COMPONENT_H #endif // ES_APP_COMPONENTS_TEXT_LIST_COMPONENT_H

View file

@ -1,3 +1,9 @@
//
// VideoComponent.cpp
//
// Base class for playing videos.
//
#include "components/VideoComponent.h" #include "components/VideoComponent.h"
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
@ -15,228 +21,230 @@
#define FADE_TIME_MS 200 #define FADE_TIME_MS 200
std::string getTitlePath() { std::string getTitlePath() {
std::string titleFolder = getTitleFolder(); std::string titleFolder = getTitleFolder();
return titleFolder + "last_title.srt"; return titleFolder + "last_title.srt";
} }
std::string getTitleFolder() { std::string getTitleFolder() {
std::string home = Utils::FileSystem::getHomePath(); std::string home = Utils::FileSystem::getHomePath();
return home + "/.emulationstation/tmp/"; return home + "/.emulationstation/tmp/";
} }
void writeSubtitle(const char* gameName, const char* systemName, bool always) void writeSubtitle(const char* gameName, const char* systemName, bool always)
{ {
FILE* file = fopen(getTitlePath().c_str(), "w"); FILE* file = fopen(getTitlePath().c_str(), "w");
int end = (int)(Settings::getInstance()->getInt("ScreenSaverSwapVideoTimeout") / (1000)); int end = (int)(Settings::getInstance()->getInt("ScreenSaverSwapVideoTimeout") / (1000));
if (always) {
fprintf(file, "1\n00:00:01,000 --> 00:00:%d,000\n", end);
}
else
{
fprintf(file, "1\n00:00:01,000 --> 00:00:08,000\n");
}
fprintf(file, "%s\n", gameName);
fprintf(file, "<i>%s</i>\n\n", systemName);
if (!always) { if (always)
if (end > 12) fprintf(file, "1\n00:00:01,000 --> 00:00:%d,000\n", end);
{ else
fprintf(file, "2\n00:00:%d,000 --> 00:00:%d,000\n%s\n<i>%s</i>\n", end-4, end, gameName, systemName); fprintf(file, "1\n00:00:01,000 --> 00:00:08,000\n");
}
}
fflush(file); fprintf(file, "%s\n", gameName);
fclose(file); fprintf(file, "<i>%s</i>\n\n", systemName);
file = NULL;
if (!always) {
if (end > 12)
fprintf(file, "2\n00:00:%d,000 --> 00:00:%d,000\n%s\n<i>%s</i>\n",
end-4, end, gameName, systemName);
}
fflush(file);
fclose(file);
file = nullptr;
} }
void VideoComponent::setScreensaverMode(bool isScreensaver) void VideoComponent::setScreensaverMode(bool isScreensaver)
{ {
mScreensaverMode = isScreensaver; mScreensaverMode = isScreensaver;
} }
VideoComponent::VideoComponent(Window* window) : VideoComponent::VideoComponent(
GuiComponent(window), Window* window)
mStaticImage(window), : GuiComponent(window),
mVideoHeight(0), mStaticImage(window),
mVideoWidth(0), mVideoHeight(0),
mStartDelayed(false), mVideoWidth(0),
mIsPlaying(false), mStartDelayed(false),
mShowing(false), mIsPlaying(false),
mScreensaverActive(false), mShowing(false),
mDisable(false), mScreensaverActive(false),
mScreensaverMode(false), mDisable(false),
mTargetIsMax(false), mScreensaverMode(false),
mTargetSize(0, 0) mTargetIsMax(false),
mTargetSize(0, 0)
{ {
// Setup the default configuration // Setup the default configuration.
mConfig.showSnapshotDelay = false; mConfig.showSnapshotDelay = false;
mConfig.showSnapshotNoVideo = false; mConfig.showSnapshotNoVideo = false;
mConfig.startDelay = 0; mConfig.startDelay = 0;
if (mWindow->getGuiStackSize() > 1) {
topWindow(false);
}
std::string path = getTitleFolder(); if (mWindow->getGuiStackSize() > 1)
if(!Utils::FileSystem::exists(path)) topWindow(false);
Utils::FileSystem::createDirectory(path);
std::string path = getTitleFolder();
if (!Utils::FileSystem::exists(path))
Utils::FileSystem::createDirectory(path);
} }
VideoComponent::~VideoComponent() VideoComponent::~VideoComponent()
{ {
// Stop any currently running video // Stop any currently running video.
stopVideo(); stopVideo();
// Delete subtitle file, if existing // Delete subtitle file, if existing.
remove(getTitlePath().c_str()); remove(getTitlePath().c_str());
} }
void VideoComponent::onOriginChanged() void VideoComponent::onOriginChanged()
{ {
// Update the embeded static image // Update the embeded static image.
mStaticImage.setOrigin(mOrigin); mStaticImage.setOrigin(mOrigin);
} }
void VideoComponent::onPositionChanged() void VideoComponent::onPositionChanged()
{ {
// Update the embeded static image // Update the embeded static image.
mStaticImage.setPosition(mPosition); mStaticImage.setPosition(mPosition);
} }
void VideoComponent::onSizeChanged() void VideoComponent::onSizeChanged()
{ {
// Update the embeded static image // Update the embeded static image.
mStaticImage.onSizeChanged(); mStaticImage.onSizeChanged();
} }
bool VideoComponent::setVideo(std::string path) bool VideoComponent::setVideo(std::string path)
{ {
// Convert the path into a generic format // Convert the path into a generic format.
std::string fullPath = Utils::FileSystem::getCanonicalPath(path); std::string fullPath = Utils::FileSystem::getCanonicalPath(path);
// Check that it's changed // Check that it's changed.
if (fullPath == mVideoPath) if (fullPath == mVideoPath)
return !path.empty(); return !path.empty();
// Store the path // Store the path.
mVideoPath = fullPath; mVideoPath = fullPath;
// If the file exists then set the new video // If the file exists then set the new video.
if (!fullPath.empty() && ResourceManager::getInstance()->fileExists(fullPath)) if (!fullPath.empty() && ResourceManager::getInstance()->fileExists(fullPath)) {
{ // Return true to show that we are going to attempt to play a video.
// Return true to show that we are going to attempt to play a video return true;
return true; }
}
// Return false to show that no video will be displayed // Return false to show that no video will be displayed.
return false; return false;
} }
void VideoComponent::setImage(std::string path) void VideoComponent::setImage(std::string path)
{ {
// Check that the image has changed // Check that the image has changed.
if (path == mStaticImagePath) if (path == mStaticImagePath)
return; return;
mStaticImage.setImage(path); mStaticImage.setImage(path);
mFadeIn = 0.0f; mFadeIn = 0.0f;
mStaticImagePath = path; mStaticImagePath = path;
} }
void VideoComponent::setDefaultVideo() void VideoComponent::setDefaultVideo()
{ {
setVideo(mConfig.defaultVideoPath); setVideo(mConfig.defaultVideoPath);
} }
void VideoComponent::setOpacity(unsigned char opacity) void VideoComponent::setOpacity(unsigned char opacity)
{ {
mOpacity = opacity; mOpacity = opacity;
// Update the embeded static image // Update the embeded static image.
mStaticImage.setOpacity(opacity); mStaticImage.setOpacity(opacity);
} }
void VideoComponent::render(const Transform4x4f& parentTrans) void VideoComponent::render(const Transform4x4f& parentTrans)
{ {
if (!isVisible()) if (!isVisible())
return; return;
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
GuiComponent::renderChildren(trans); GuiComponent::renderChildren(trans);
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
// Handle the case where the video is delayed // Handle the case where the video is delayed.
handleStartDelay(); handleStartDelay();
// Handle looping of the video // Handle looping of the video.
handleLooping(); handleLooping();
} }
void VideoComponent::renderSnapshot(const Transform4x4f& parentTrans) void VideoComponent::renderSnapshot(const Transform4x4f& parentTrans)
{ {
// This is the case where the video is not currently being displayed. Work out // This is the case where the video is not currently being displayed. Work out
// if we need to display a static image // if we need to display a static image.
if ((mConfig.showSnapshotNoVideo && mVideoPath.empty()) || (mStartDelayed && mConfig.showSnapshotDelay)) if ((mConfig.showSnapshotNoVideo && mVideoPath.empty()) ||
{ (mStartDelayed && mConfig.showSnapshotDelay)) {
// Display the static image instead // Display the static image instead.
mStaticImage.setOpacity((unsigned char)(mFadeIn * 255.0f)); mStaticImage.setOpacity((unsigned char)(mFadeIn * 255.0f));
mStaticImage.render(parentTrans); mStaticImage.render(parentTrans);
} }
} }
void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties)
{ {
using namespace ThemeFlags; using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element, (properties ^ SIZE) | ((properties & (SIZE | POSITION)) ? ORIGIN : 0)); GuiComponent::applyTheme(theme, view, element, (properties ^ SIZE) |
((properties & (SIZE | POSITION)) ? ORIGIN : 0));
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "video"); const ThemeData::ThemeElement* elem = theme->getElement(view, element, "video");
if(!elem)
return;
Vector2f scale = getParent() ? getParent()->getSize() : Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); if (!elem)
return;
if(properties & ThemeFlags::SIZE) Vector2f scale = getParent() ? getParent()->getSize()
{ : Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
if(elem->has("size"))
setResize(elem->get<Vector2f>("size") * scale);
else if(elem->has("maxSize"))
setMaxSize(elem->get<Vector2f>("maxSize") * scale);
}
if(elem->has("default")) if (properties & ThemeFlags::SIZE) {
mConfig.defaultVideoPath = elem->get<std::string>("default"); if (elem->has("size"))
setResize(elem->get<Vector2f>("size") * scale);
else if (elem->has("maxSize"))
setMaxSize(elem->get<Vector2f>("maxSize") * scale);
}
if((properties & ThemeFlags::DELAY) && elem->has("delay")) if (elem->has("default"))
mConfig.startDelay = (unsigned)(elem->get<float>("delay") * 1000.0f); mConfig.defaultVideoPath = elem->get<std::string>("default");
if (elem->has("showSnapshotNoVideo")) if ((properties & ThemeFlags::DELAY) && elem->has("delay"))
mConfig.showSnapshotNoVideo = elem->get<bool>("showSnapshotNoVideo"); mConfig.startDelay = (unsigned)(elem->get<float>("delay") * 1000.0f);
if (elem->has("showSnapshotDelay")) if (elem->has("showSnapshotNoVideo"))
mConfig.showSnapshotDelay = elem->get<bool>("showSnapshotDelay"); mConfig.showSnapshotNoVideo = elem->get<bool>("showSnapshotNoVideo");
if (elem->has("showSnapshotDelay"))
mConfig.showSnapshotDelay = elem->get<bool>("showSnapshotDelay");
} }
std::vector<HelpPrompt> VideoComponent::getHelpPrompts() std::vector<HelpPrompt> VideoComponent::getHelpPrompts()
{ {
std::vector<HelpPrompt> ret; std::vector<HelpPrompt> ret;
ret.push_back(HelpPrompt("a", "select")); ret.push_back(HelpPrompt("a", "select"));
return ret; return ret;
} }
void VideoComponent::handleStartDelay() void VideoComponent::handleStartDelay()
{ {
// Only play if any delay has timed out // Only play if any delay has timed out.
if (mStartDelayed) if (mStartDelayed) {
{ if (mStartTime > SDL_GetTicks()) {
if (mStartTime > SDL_GetTicks()) // Timeout not yet completed.
{ return;
// Timeout not yet completed }
return; // Completed.
} mStartDelayed = false;
// Completed // Clear the playing flag so startVideo works.
mStartDelayed = false; mIsPlaying = false;
// Clear the playing flag so startVideo works startVideo();
mIsPlaying = false; }
startVideo();
}
} }
void VideoComponent::handleLooping() void VideoComponent::handleLooping()
@ -245,119 +253,105 @@ void VideoComponent::handleLooping()
void VideoComponent::startVideoWithDelay() void VideoComponent::startVideoWithDelay()
{ {
// If not playing then either start the video or initiate the delay // If not playing then either start the video or initiate the delay.
if (!mIsPlaying) if (!mIsPlaying) {
{ // Set the video that we are going to be playing so we don't attempt to restart it.
// Set the video that we are going to be playing so we don't attempt to restart it mPlayingVideoPath = mVideoPath;
mPlayingVideoPath = mVideoPath;
if (mConfig.startDelay == 0 || PowerSaver::getMode() == PowerSaver::INSTANT) if (mConfig.startDelay == 0 || PowerSaver::getMode() == PowerSaver::INSTANT) {
{ // No delay. Just start the video.
// No delay. Just start the video mStartDelayed = false;
mStartDelayed = false; startVideo();
startVideo(); }
} else {
else // Configure the start delay.
{ mStartDelayed = true;
// Configure the start delay mFadeIn = 0.0f;
mStartDelayed = true; mStartTime = SDL_GetTicks() + mConfig.startDelay;
mFadeIn = 0.0f; }
mStartTime = SDL_GetTicks() + mConfig.startDelay; mIsPlaying = true;
} }
mIsPlaying = true;
}
} }
void VideoComponent::update(int deltaTime) void VideoComponent::update(int deltaTime)
{ {
manageState(); manageState();
// If the video start is delayed and there is less than the fade time then set the image fade // If the video start is delayed and there is less than the fade time, then set
// accordingly // the image fade accordingly.
if (mStartDelayed) if (mStartDelayed) {
{ Uint32 ticks = SDL_GetTicks();
Uint32 ticks = SDL_GetTicks(); if (mStartTime > ticks) {
if (mStartTime > ticks) Uint32 diff = mStartTime - ticks;
{ if (diff < FADE_TIME_MS) {
Uint32 diff = mStartTime - ticks; mFadeIn = (float)diff / (float)FADE_TIME_MS;
if (diff < FADE_TIME_MS) return;
{ }
mFadeIn = (float)diff / (float)FADE_TIME_MS; }
return; }
} // If the fade in is less than 1 then increment it.
} if (mFadeIn < 1.0f) {
} mFadeIn += deltaTime / (float)FADE_TIME_MS;
// If the fade in is less than 1 then increment it if (mFadeIn > 1.0f)
if (mFadeIn < 1.0f) mFadeIn = 1.0f;
{ }
mFadeIn += deltaTime / (float)FADE_TIME_MS; GuiComponent::update(deltaTime);
if (mFadeIn > 1.0f)
mFadeIn = 1.0f;
}
GuiComponent::update(deltaTime);
} }
void VideoComponent::manageState() void VideoComponent::manageState()
{ {
// We will only show if the component is on display and the screensaver // We will only show if the component is on display and the screensaver
// is not active // is not active.
bool show = mShowing && !mScreensaverActive && !mDisable; bool show = mShowing && !mScreensaverActive && !mDisable;
// See if we're already playing // See if we're already playing.
if (mIsPlaying) if (mIsPlaying) {
{ // If we are not on display then stop the video from playing.
// If we are not on display then stop the video from playing if (!show) {
if (!show) stopVideo();
{ }
stopVideo(); else {
} if (mVideoPath != mPlayingVideoPath) {
else // Path changed. Stop the video. We will start it again below because
{ // mIsPlaying will be modified by stopVideo to be false.
if (mVideoPath != mPlayingVideoPath) stopVideo();
{ }
// Path changed. Stop the video. We will start it again below because }
// mIsPlaying will be modified by stopVideo to be false }
stopVideo(); // Need to recheck variable rather than 'else' because it may be modified above.
} if (!mIsPlaying) {
} // If we are on display then see if we should start the video.
} if (show && !mVideoPath.empty())
// Need to recheck variable rather than 'else' because it may be modified above startVideoWithDelay();
if (!mIsPlaying) }
{
// If we are on display then see if we should start the video
if (show && !mVideoPath.empty())
{
startVideoWithDelay();
}
}
} }
void VideoComponent::onShow() void VideoComponent::onShow()
{ {
mShowing = true; mShowing = true;
manageState(); manageState();
} }
void VideoComponent::onHide() void VideoComponent::onHide()
{ {
mShowing = false; mShowing = false;
manageState(); manageState();
} }
void VideoComponent::onScreenSaverActivate() void VideoComponent::onScreenSaverActivate()
{ {
mScreensaverActive = true; mScreensaverActive = true;
manageState(); manageState();
} }
void VideoComponent::onScreenSaverDeactivate() void VideoComponent::onScreenSaverDeactivate()
{ {
mScreensaverActive = false; mScreensaverActive = false;
manageState(); manageState();
} }
void VideoComponent::topWindow(bool isTop) void VideoComponent::topWindow(bool isTop)
{ {
mDisable = !isTop; mDisable = !isTop;
manageState(); manageState();
} }

View file

@ -1,9 +1,16 @@
//
// VideoComponent.h
//
// Base class for playing videos.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_VIDEO_COMPONENT_H #ifndef ES_CORE_COMPONENTS_VIDEO_COMPONENT_H
#define ES_CORE_COMPONENTS_VIDEO_COMPONENT_H #define ES_CORE_COMPONENTS_VIDEO_COMPONENT_H
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "GuiComponent.h" #include "GuiComponent.h"
#include <string> #include <string>
class TextureResource; class TextureResource;
@ -14,101 +21,101 @@ void writeSubtitle(const char* gameName, const char* systemName, bool always);
class VideoComponent : public GuiComponent class VideoComponent : public GuiComponent
{ {
// Structure that groups together the configuration of the video component // Structure that groups together the configuration of the video component.
struct Configuration struct Configuration {
{ unsigned startDelay;
unsigned startDelay; bool showSnapshotNoVideo;
bool showSnapshotNoVideo; bool showSnapshotDelay;
bool showSnapshotDelay; std::string defaultVideoPath;
std::string defaultVideoPath; };
};
public: public:
VideoComponent(Window* window); VideoComponent(Window* window);
virtual ~VideoComponent(); virtual ~VideoComponent();
// Loads the video at the given filepath // Loads the video at the given filepath.
bool setVideo(std::string path); bool setVideo(std::string path);
// Loads a static image that is displayed if the video cannot be played // Loads a static image that is displayed if the video cannot be played.
void setImage(std::string path); void setImage(std::string path);
// Configures the component to show the default video // Configures the component to show the default video.
void setDefaultVideo(); void setDefaultVideo();
// sets whether it's going to render in screensaver mode // Sets whether it's going to render in screensaver mode.
void setScreensaverMode(bool isScreensaver); void setScreensaverMode(bool isScreensaver);
virtual void onShow() override; virtual void onShow() override;
virtual void onHide() override; virtual void onHide() override;
virtual void onScreenSaverActivate() override; virtual void onScreenSaverActivate() override;
virtual void onScreenSaverDeactivate() override; virtual void onScreenSaverDeactivate() override;
virtual void topWindow(bool isTop) override; virtual void topWindow(bool isTop) override;
void onOriginChanged() override; void onOriginChanged() override;
void onPositionChanged() override; void onPositionChanged() override;
void onSizeChanged() override; void onSizeChanged() override;
void setOpacity(unsigned char opacity) override; void setOpacity(unsigned char opacity) override;
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
void renderSnapshot(const Transform4x4f& parentTrans); void renderSnapshot(const Transform4x4f& parentTrans);
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override; virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view,
const std::string& element, unsigned int properties) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
virtual void update(int deltaTime) override; virtual void update(int deltaTime) override;
// Resize the video to fit this size. If one axis is zero, scale that axis to maintain aspect ratio. // Resize the video to fit this size. If one axis is zero, scale that axis to maintain
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing. // aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are
// Can be set before or after a video is loaded. // zero, no resizing. This can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive. // setMaxSize() and setResize() are mutually exclusive.
virtual void setResize(float width, float height) = 0; virtual void setResize(float width, float height) = 0;
inline void setResize(const Vector2f& size) { setResize(size.x(), size.y()); } inline void setResize(const Vector2f& size) { setResize(size.x(), size.y()); }
// Resize the video to be as large as possible but fit within a box of this size. // Resize the video to be as large as possible but fit within a box of this size.
// Can be set before or after a video is loaded. // This can be set before or after a video is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive. // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
virtual void setMaxSize(float width, float height) = 0; virtual void setMaxSize(float width, float height) = 0;
inline void setMaxSize(const Vector2f& size) { setMaxSize(size.x(), size.y()); } inline void setMaxSize(const Vector2f& size) { setMaxSize(size.x(), size.y()); }
private: private:
// Start the video Immediately // Start the video immediately.
virtual void startVideo() = 0; virtual void startVideo() = 0;
// Stop the video // Stop the video.
virtual void stopVideo() { }; virtual void stopVideo() { };
// Handle looping the video. Must be called periodically // Handle looping the video. Must be called periodically.
virtual void handleLooping(); virtual void handleLooping();
// Start the video after any configured delay // Start the video after any configured delay.
void startVideoWithDelay(); void startVideoWithDelay();
// Handle any delay to the start of playing the video clip. Must be called periodically // Handle any delay to the start of playing the video clip. Must be called periodically.
void handleStartDelay(); void handleStartDelay();
// Manage the playing state of the component // Manage the playing state of the component.
void manageState(); void manageState();
protected: protected:
unsigned mVideoWidth; unsigned mVideoWidth;
unsigned mVideoHeight; unsigned mVideoHeight;
Vector2f mTargetSize; Vector2f mTargetSize;
std::shared_ptr<TextureResource> mTexture; std::shared_ptr<TextureResource> mTexture;
float mFadeIn; float mFadeIn;
std::string mStaticImagePath; std::string mStaticImagePath;
ImageComponent mStaticImage; ImageComponent mStaticImage;
std::string mVideoPath; std::string mVideoPath;
std::string mPlayingVideoPath; std::string mPlayingVideoPath;
bool mStartDelayed; bool mStartDelayed;
unsigned mStartTime; unsigned mStartTime;
bool mIsPlaying; bool mIsPlaying;
bool mShowing; bool mShowing;
bool mDisable; bool mDisable;
bool mScreensaverActive; bool mScreensaverActive;
bool mScreensaverMode; bool mScreensaverMode;
bool mTargetIsMax; bool mTargetIsMax;
Configuration mConfig; Configuration mConfig;
}; };
#endif // ES_CORE_COMPONENTS_VIDEO_COMPONENT_H #endif // ES_CORE_COMPONENTS_VIDEO_COMPONENT_H

View file

@ -1,3 +1,9 @@
//
// VideoPlayerComponent.cpp
//
// OMXPlayer video playing for Raspberry Pi.
//
#ifdef _RPI_ #ifdef _RPI_
#include "components/VideoPlayerComponent.h" #include "components/VideoPlayerComponent.h"
@ -5,6 +11,7 @@
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "Settings.h" #include "Settings.h"
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <wait.h> #include <wait.h>
@ -12,250 +19,235 @@
class VolumeControl class VolumeControl
{ {
public: public:
static std::shared_ptr<VolumeControl> & getInstance(); static std::shared_ptr<VolumeControl> & getInstance();
int getVolume() const; int getVolume() const;
}; };
VideoPlayerComponent::VideoPlayerComponent(Window* window, std::string path) : VideoPlayerComponent::VideoPlayerComponent(Window* window, std::string path) :
VideoComponent(window), VideoComponent(window),
mPlayerPid(-1), mPlayerPid(-1),
subtitlePath(path) subtitlePath(path)
{ {
} }
VideoPlayerComponent::~VideoPlayerComponent() VideoPlayerComponent::~VideoPlayerComponent()
{ {
stopVideo(); stopVideo();
} }
void VideoPlayerComponent::render(const Transform4x4f& parentTrans) void VideoPlayerComponent::render(const Transform4x4f& parentTrans)
{ {
if (!isVisible()) if (!isVisible())
return; return;
VideoComponent::render(parentTrans); VideoComponent::render(parentTrans);
if (!mIsPlaying || mPlayerPid == -1) if (!mIsPlaying || mPlayerPid == -1)
VideoComponent::renderSnapshot(parentTrans); VideoComponent::renderSnapshot(parentTrans);
} }
void VideoPlayerComponent::setResize(float width, float height) void VideoPlayerComponent::setResize(float width, float height)
{ {
setSize(width, height); setSize(width, height);
mTargetSize = Vector2f(width, height); mTargetSize = Vector2f(width, height);
mTargetIsMax = false; mTargetIsMax = false;
mStaticImage.setResize(width, height); mStaticImage.setResize(width, height);
onSizeChanged(); onSizeChanged();
} }
void VideoPlayerComponent::setMaxSize(float width, float height) void VideoPlayerComponent::setMaxSize(float width, float height)
{ {
setSize(width, height); setSize(width, height);
mTargetSize = Vector2f(width, height); mTargetSize = Vector2f(width, height);
mTargetIsMax = true; mTargetIsMax = true;
mStaticImage.setMaxSize(width, height); mStaticImage.setMaxSize(width, height);
onSizeChanged(); onSizeChanged();
} }
void VideoPlayerComponent::startVideo() void VideoPlayerComponent::startVideo()
{ {
if (!mIsPlaying) if (!mIsPlaying) {
{ mVideoWidth = 0;
mVideoWidth = 0; mVideoHeight = 0;
mVideoHeight = 0;
std::string path(mVideoPath.c_str()); std::string path(mVideoPath.c_str());
// Make sure we have a video path // Make sure we have a video path.
if ((path.size() > 0) && (mPlayerPid == -1)) if ((path.size() > 0) && (mPlayerPid == -1)) {
{ // Set the video that we are going to be playing so we don't attempt to restart it.
// Set the video that we are going to be playing so we don't attempt to restart it mPlayingVideoPath = mVideoPath;
mPlayingVideoPath = mVideoPath;
// Disable AudioManager so video can play, in case we're requesting ALSA // Disable AudioManager so video can play, in case we're requesting ALSA.
if (Utils::String::startsWith(Settings::getInstance()->getString("OMXAudioDev").c_str(), "alsa")) if (Utils::String::startsWith(Settings::getInstance()->
{ getString("OMXAudioDev").c_str(), "alsa"))
AudioManager::getInstance()->deinit(); AudioManager::getInstance()->deinit();
}
// Start the player process // Start the player process.
pid_t pid = fork(); pid_t pid = fork();
if (pid == -1) if (pid == -1) {
{ // Failed.
// Failed mPlayingVideoPath = "";
mPlayingVideoPath = ""; }
} else if (pid > 0) {
else if (pid > 0) mPlayerPid = pid;
{ // Update the playing state.
mPlayerPid = pid; signal(SIGCHLD, catch_child);
// Update the playing state mIsPlaying = true;
signal(SIGCHLD, catch_child); mFadeIn = 0.0f;
mIsPlaying = true; }
mFadeIn = 0.0f; else {
} // Find out the pixel position of the video view and build a command line for
else // OMXPlayer to position it in the right place.
{ char buf1[32];
char buf2[32];
float x = mPosition.x() - (mOrigin.x() * mSize.x());
float y = mPosition.y() - (mOrigin.y() * mSize.y());
// Find out the pixel position of the video view and build a command line for // Fix x and y.
// omxplayer to position it in the right place switch (Renderer::getScreenRotate()) {
char buf1[32]; case 0: {
char buf2[32]; const int x1 = (int)(Renderer::getScreenOffsetX() + x);
float x = mPosition.x() - (mOrigin.x() * mSize.x()); const int y1 = (int)(Renderer::getScreenOffsetY() + y);
float y = mPosition.y() - (mOrigin.y() * mSize.y()); const int x2 = (int)(x1 + mSize.x());
const int y2 = (int)(y1 + mSize.y());
sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2);
}
break;
// fix x and y case 1: {
switch(Renderer::getScreenRotate()) const int x1 = (int)(Renderer::getWindowWidth() -
{ Renderer::getScreenOffsetY() - y - mSize.y());
case 0: const int y1 = (int)(Renderer::getScreenOffsetX() + x);
{ const int x2 = (int)(x1 + mSize.y());
const int x1 = (int)(Renderer::getScreenOffsetX() + x); const int y2 = (int)(y1 + mSize.x());
const int y1 = (int)(Renderer::getScreenOffsetY() + y); sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2);
const int x2 = (int)(x1 + mSize.x()); }
const int y2 = (int)(y1 + mSize.y()); break;
sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2);
}
break;
case 1: case 2: {
{ const int x1 = (int)(Renderer::getWindowWidth() -
const int x1 = (int)(Renderer::getWindowWidth() - Renderer::getScreenOffsetY() - y - mSize.y()); Renderer::getScreenOffsetX() - x - mSize.x());
const int y1 = (int)(Renderer::getScreenOffsetX() + x); const int y1 = (int)(Renderer::getWindowHeight() -
const int x2 = (int)(x1 + mSize.y()); Renderer::getScreenOffsetY() - y - mSize.y());
const int y2 = (int)(y1 + mSize.x()); const int x2 = (int)(x1 + mSize.x());
sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2); const int y2 = (int)(y1 + mSize.y());
} sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2);
break; }
break;
case 2: case 3: {
{ const int x1 = (int)(Renderer::getScreenOffsetY() + y);
const int x1 = (int)(Renderer::getWindowWidth() - Renderer::getScreenOffsetX() - x - mSize.x()); const int y1 = (int)(Renderer::getWindowHeight() -
const int y1 = (int)(Renderer::getWindowHeight() - Renderer::getScreenOffsetY() - y - mSize.y()); Renderer::getScreenOffsetX() - x - mSize.x());
const int x2 = (int)(x1 + mSize.x()); const int x2 = (int)(x1 + mSize.y());
const int y2 = (int)(y1 + mSize.y()); const int y2 = (int)(y1 + mSize.x());
sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2); sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2);
} }
break; break;
}
case 3: // Rotate the video.
{ switch (Renderer::getScreenRotate()) {
const int x1 = (int)(Renderer::getScreenOffsetY() + y); case 0: { sprintf(buf2, "%d", (int) 0); } break;
const int y1 = (int)(Renderer::getWindowHeight() - Renderer::getScreenOffsetX() - x - mSize.x()); case 1: { sprintf(buf2, "%d", (int) 90); } break;
const int x2 = (int)(x1 + mSize.y()); case 2: { sprintf(buf2, "%d", (int)180); } break;
const int y2 = (int)(y1 + mSize.x()); case 3: { sprintf(buf2, "%d", (int)270); } break;
sprintf(buf1, "%d,%d,%d,%d", x1, y1, x2, y2); }
}
break;
}
// rotate the video // We need to specify the layer of 10000 or above to ensure the video is
switch(Renderer::getScreenRotate()) // displayed on top of our SDL display.
{ const char* argv[] = { "", "--layer", "10010", "--loop", "--no-osd",
case 0: { sprintf(buf2, "%d", (int) 0); } break; "--aspect-mode", "letterbox", "--vol", "0", "-o", "both",
case 1: { sprintf(buf2, "%d", (int) 90); } break; "--win", buf1, "--orientation", buf2, "", "", "", "", "", "",
case 2: { sprintf(buf2, "%d", (int)180); } break; "", "", "", "", "", NULL };
case 3: { sprintf(buf2, "%d", (int)270); } break;
}
// We need to specify the layer of 10000 or above to ensure the video is displayed on top // Check if we want to mute the audio.
// of our SDL display if ((!Settings::getInstance()->getBool("VideoAudio") ||
(float)VolumeControl::getInstance()->getVolume() == 0) ||
(Settings::getInstance()->getBool("ScreenSaverVideoMute") &&
mScreensaverMode)) {
argv[8] = "-1000000";
}
else {
float percentVolume = (float)VolumeControl::getInstance()->getVolume();
int OMXVolume = (int)((percentVolume-98)*105);
argv[8] = std::to_string(OMXVolume).c_str();
}
const char* argv[] = { "", "--layer", "10010", "--loop", "--no-osd", "--aspect-mode", "letterbox", "--vol", "0", "-o", "both","--win", buf1, "--orientation", buf2, "", "", "", "", "", "", "", "", "", "", "", NULL }; // Test if there's a path for possible subtitles, meaning we're a screensaver video.
if (!subtitlePath.empty()) {
// If we are rendering a screensaver.
// Check if we want to stretch the image.
if (Settings::getInstance()->getBool("StretchVideoOnScreenSaver"))
argv[6] = "stretch";
// check if we want to mute the audio if (Settings::getInstance()->getString("ScreenSaverGameInfo") != "never") {
if ((!Settings::getInstance()->getBool("VideoAudio") || (float)VolumeControl::getInstance()->getVolume() == 0) || // If we have chosen to render subtitles.
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode)) argv[15] = "--subtitles";
{ argv[16] = subtitlePath.c_str();
argv[8] = "-1000000"; argv[17] = mPlayingVideoPath.c_str();
} argv[18] = "--font";
else argv[19] = Settings::getInstance()->getString("SubtitleFont").c_str();
{ argv[20] = "--italic-font";
float percentVolume = (float)VolumeControl::getInstance()->getVolume(); argv[21] = Settings::getInstance()->
int OMXVolume = (int)((percentVolume-98)*105); getString("SubtitleItalicFont").c_str();
argv[8] = std::to_string(OMXVolume).c_str(); argv[22] = "--font-size";
} argv[23] = std::to_string(Settings::getInstance()->
getInt("SubtitleSize")).c_str();
argv[24] = "--align";
argv[25] = Settings::getInstance()->
getString("SubtitleAlignment").c_str();
}
else {
// If we have chosen NOT to render subtitles in the screensaver.
argv[15] = mPlayingVideoPath.c_str();
}
}
else {
// If we are rendering a video gamelist.
if (!mTargetIsMax)
argv[6] = "stretch";
argv[15] = mPlayingVideoPath.c_str();
}
// test if there's a path for possible subtitles, meaning we're a screensaver video argv[10] = Settings::getInstance()->getString("OMXAudioDev").c_str();
if (!subtitlePath.empty())
{
// if we are rendering a screensaver
// check if we want to stretch the image //const char* argv[] = args;
if (Settings::getInstance()->getBool("StretchVideoOnScreenSaver")) const char* env[] = { "LD_LIBRARY_PATH=/opt/vc/libs:/usr/lib/omxplayer", NULL };
{
argv[6] = "stretch";
}
if (Settings::getInstance()->getString("ScreenSaverGameInfo") != "never") // Redirect stdout.
{ int fdin = open("/dev/null", O_RDONLY);
// if we have chosen to render subtitles int fdout = open("/dev/null", O_WRONLY);
argv[15] = "--subtitles"; dup2(fdin, 0);
argv[16] = subtitlePath.c_str(); dup2(fdout, 1);
argv[17] = mPlayingVideoPath.c_str(); // Run the OMXPlayer binary.
argv[18] = "--font"; execve("/usr/bin/omxplayer.bin", (char**)argv, (char**)env);
argv[19] = Settings::getInstance()->getString("SubtitleFont").c_str();
argv[20] = "--italic-font";
argv[21] = Settings::getInstance()->getString("SubtitleItalicFont").c_str();
argv[22] = "--font-size";
argv[23] = std::to_string(Settings::getInstance()->getInt("SubtitleSize")).c_str();
argv[24] = "--align";
argv[25] = Settings::getInstance()->getString("SubtitleAlignment").c_str();
}
else
{
// if we have chosen NOT to render subtitles in the screensaver
argv[15] = mPlayingVideoPath.c_str();
}
}
else
{
// if we are rendering a video gamelist
if (!mTargetIsMax)
{
argv[6] = "stretch";
}
argv[15] = mPlayingVideoPath.c_str();
}
argv[10] = Settings::getInstance()->getString("OMXAudioDev").c_str(); _exit(EXIT_FAILURE);
}
//const char* argv[] = args; }
const char* env[] = { "LD_LIBRARY_PATH=/opt/vc/libs:/usr/lib/omxplayer", NULL }; }
// Redirect stdout
int fdin = open("/dev/null", O_RDONLY);
int fdout = open("/dev/null", O_WRONLY);
dup2(fdin, 0);
dup2(fdout, 1);
// Run the omxplayer binary
execve("/usr/bin/omxplayer.bin", (char**)argv, (char**)env);
_exit(EXIT_FAILURE);
}
}
}
} }
void catch_child(int sig_num) void catch_child(int sig_num)
{ {
/* when we get here, we know there's a zombie child waiting */ // When we get here, we know there's a zombie child waiting.
int child_status; int child_status;
wait(&child_status); wait(&child_status);
} }
void VideoPlayerComponent::stopVideo() void VideoPlayerComponent::stopVideo()
{ {
mIsPlaying = false; mIsPlaying = false;
mStartDelayed = false; mStartDelayed = false;
// Stop the player process // Stop the player process.
if (mPlayerPid != -1) if (mPlayerPid != -1) {
{ int status;
int status; kill(mPlayerPid, SIGKILL);
kill(mPlayerPid, SIGKILL); waitpid(mPlayerPid, &status, WNOHANG);
waitpid(mPlayerPid, &status, WNOHANG); mPlayerPid = -1;
mPlayerPid = -1; }
}
} }
#endif #endif

View file

@ -1,3 +1,9 @@
//
// VideoPlayerComponent.h
//
// OMXPlayer video playing for Raspberry Pi.
//
#ifdef _RPI_ #ifdef _RPI_
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_VIDEO_PLAYER_COMPONENT_H #ifndef ES_CORE_COMPONENTS_VIDEO_PLAYER_COMPONENT_H
@ -10,31 +16,31 @@ void catch_child(int sig_num);
class VideoPlayerComponent : public VideoComponent class VideoPlayerComponent : public VideoComponent
{ {
public: public:
VideoPlayerComponent(Window* window, std::string path); VideoPlayerComponent(Window* window, std::string path);
virtual ~VideoPlayerComponent(); virtual ~VideoPlayerComponent();
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
// Resize the video to fit this size. If one axis is zero, scale that axis to maintain aspect ratio. // Resize the video to fit this size. If one axis is zero, scale that axis to maintain
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing. // aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are
// Can be set before or after a video is loaded. // zero, no resizing. This can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive. // setMaxSize() and setResize() are mutually exclusive.
void setResize(float width, float height); void setResize(float width, float height) override;
// Resize the video to be as large as possible but fit within a box of this size. // Resize the video to be as large as possible but fit within a box of this size.
// Can be set before or after a video is loaded. // This can be set before or after a video is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive. // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
void setMaxSize(float width, float height); void setMaxSize(float width, float height) override;
private: private:
// Start the video Immediately // Start the video Immediately.
virtual void startVideo(); virtual void startVideo() override;
// Stop the video // Stop the video.
virtual void stopVideo(); virtual void stopVideo() override;
private: private:
pid_t mPlayerPid; pid_t mPlayerPid;
std::string subtitlePath; std::string subtitlePath;
}; };
#endif // ES_CORE_COMPONENTS_VIDEO_PLAYER_COMPONENT_H #endif // ES_CORE_COMPONENTS_VIDEO_PLAYER_COMPONENT_H

View file

@ -1,3 +1,9 @@
//
// VideoVlcComponent.cpp
//
// Video playing using libVLC.
//
#include "components/VideoVlcComponent.h" #include "components/VideoVlcComponent.h"
#include "renderers/Renderer.h" #include "renderers/Renderer.h"
@ -20,337 +26,322 @@
#include <codecvt> #include <codecvt>
#endif #endif
libvlc_instance_t* VideoVlcComponent::mVLC = NULL; libvlc_instance_t* VideoVlcComponent::mVLC = nullptr;
// VLC prepares to render a video frame. // VLC prepares to render a video frame.
static void *lock(void *data, void **p_pixels) { static void* lock(void* data, void** p_pixels) {
struct VideoContext *c = (struct VideoContext *)data; struct VideoContext* c = (struct VideoContext*)data;
SDL_LockMutex(c->mutex); SDL_LockMutex(c->mutex);
SDL_LockSurface(c->surface); SDL_LockSurface(c->surface);
*p_pixels = c->surface->pixels; *p_pixels = c->surface->pixels;
return NULL; // Picture identifier, not needed here. return nullptr; // Picture identifier, not needed here.
} }
// VLC just rendered a video frame. // VLC just rendered a video frame.
static void unlock(void *data, void* /*id*/, void *const* /*p_pixels*/) { static void unlock(void* data, void* /*id*/, void *const* /*p_pixels*/) {
struct VideoContext *c = (struct VideoContext *)data; struct VideoContext* c = (struct VideoContext*)data;
SDL_UnlockSurface(c->surface); SDL_UnlockSurface(c->surface);
SDL_UnlockMutex(c->mutex); SDL_UnlockMutex(c->mutex);
} }
// VLC wants to display a video frame. // VLC wants to display a video frame.
static void display(void* /*data*/, void* /*id*/) { static void display(void* /*data*/, void* /*id*/) {
//Data to be displayed // Data to be displayed.
} }
VideoVlcComponent::VideoVlcComponent(Window* window, std::string subtitles) : VideoVlcComponent::VideoVlcComponent(Window* window, std::string subtitles)
VideoComponent(window), : VideoComponent(window), mMediaPlayer(nullptr)
mMediaPlayer(nullptr)
{ {
memset(&mContext, 0, sizeof(mContext)); memset(&mContext, 0, sizeof(mContext));
// Get an empty texture for rendering the video // Get an empty texture for rendering the video.
mTexture = TextureResource::get(""); mTexture = TextureResource::get("");
// Make sure VLC has been initialised // Make sure VLC has been initialized.
setupVLC(subtitles); setupVLC(subtitles);
} }
VideoVlcComponent::~VideoVlcComponent() VideoVlcComponent::~VideoVlcComponent()
{ {
stopVideo(); stopVideo();
} }
void VideoVlcComponent::setResize(float width, float height) void VideoVlcComponent::setResize(float width, float height)
{ {
mTargetSize = Vector2f(width, height); mTargetSize = Vector2f(width, height);
mTargetIsMax = false; mTargetIsMax = false;
mStaticImage.setResize(width, height); mStaticImage.setResize(width, height);
resize(); resize();
} }
void VideoVlcComponent::setMaxSize(float width, float height) void VideoVlcComponent::setMaxSize(float width, float height)
{ {
mTargetSize = Vector2f(width, height); mTargetSize = Vector2f(width, height);
mTargetIsMax = true; mTargetIsMax = true;
mStaticImage.setMaxSize(width, height); mStaticImage.setMaxSize(width, height);
resize(); resize();
} }
void VideoVlcComponent::resize() void VideoVlcComponent::resize()
{ {
if(!mTexture) if (!mTexture)
return; return;
const Vector2f textureSize((float)mVideoWidth, (float)mVideoHeight); const Vector2f textureSize((float)mVideoWidth, (float)mVideoHeight);
if(textureSize == Vector2f::Zero()) if (textureSize == Vector2f::Zero())
return; return;
// SVG rasterization is determined by height (see SVGResource.cpp), and rasterization is done in terms of pixels // SVG rasterization is determined by height and rasterization is done in terms of pixels.
// if rounding is off enough in the rasterization step (for images with extreme aspect ratios), it can cause cutoff when the aspect ratio breaks // If rounding is off enough in the rasterization step (for images with extreme aspect
// so, we always make sure the resultant height is an integer to make sure cutoff doesn't happen, and scale width from that // ratios), it can cause cutoff when the aspect ratio breaks.
// (you'll see this scattered throughout the function) // So we always make sure the resultant height is an integer to make sure cutoff doesn't
// this is probably not the best way, so if you're familiar with this problem and have a better solution, please make a pull request! // happen, and scale width from that (you'll see this scattered throughout the function).
// This is probably not the best way, so if you're familiar with this problem and have a
// better solution, please make a pull request!
if (mTargetIsMax) {
mSize = textureSize;
if(mTargetIsMax) Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y()));
{
mSize = textureSize; if (resizeScale.x() < resizeScale.y()) {
mSize[0] *= resizeScale.x();
mSize[1] *= resizeScale.x();
}
else {
mSize[0] *= resizeScale.y();
mSize[1] *= resizeScale.y();
}
Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y())); // For SVG rasterization, always calculate width from rounded height (see comment above).
mSize[1] = Math::round(mSize[1]);
mSize[0] = (mSize[1] / textureSize.y()) * textureSize.x();
if(resizeScale.x() < resizeScale.y()) }
{ else {
mSize[0] *= resizeScale.x(); // If both components are set, we just stretch.
mSize[1] *= resizeScale.x(); // If no components are set, we don't resize at all.
}else{ mSize = mTargetSize == Vector2f::Zero() ? textureSize : mTargetSize;
mSize[0] *= resizeScale.y();
mSize[1] *= resizeScale.y();
}
// for SVG rasterization, always calculate width from rounded height (see comment above) // If only one component is set, we resize in a way that maintains aspect ratio.
mSize[1] = Math::round(mSize[1]); // For SVG rasterization, we always calculate width from rounded height (see comment above).
mSize[0] = (mSize[1] / textureSize.y()) * textureSize.x(); if (!mTargetSize.x() && mTargetSize.y()) {
mSize[1] = Math::round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
else if (mTargetSize.x() && !mTargetSize.y()) {
mSize[1] = Math::round((mTargetSize.x() / textureSize.x()) * textureSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
}
}else{ // mSize.y() should already be rounded.
// if both components are set, we just stretch mTexture->rasterizeAt((size_t)Math::round(mSize.x()), (size_t)Math::round(mSize.y()));
// if no components are set, we don't resize at all
mSize = mTargetSize == Vector2f::Zero() ? textureSize : mTargetSize;
// if only one component is set, we resize in a way that maintains aspect ratio onSizeChanged();
// for SVG rasterization, we always calculate width from rounded height (see comment above)
if(!mTargetSize.x() && mTargetSize.y())
{
mSize[1] = Math::round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}else if(mTargetSize.x() && !mTargetSize.y())
{
mSize[1] = Math::round((mTargetSize.x() / textureSize.x()) * textureSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
}
// mSize.y() should already be rounded
mTexture->rasterizeAt((size_t)Math::round(mSize.x()), (size_t)Math::round(mSize.y()));
onSizeChanged();
} }
void VideoVlcComponent::render(const Transform4x4f& parentTrans) void VideoVlcComponent::render(const Transform4x4f& parentTrans)
{ {
if (!isVisible()) if (!isVisible())
return; return;
VideoComponent::render(parentTrans); VideoComponent::render(parentTrans);
Transform4x4f trans = parentTrans * getTransform(); Transform4x4f trans = parentTrans * getTransform();
GuiComponent::renderChildren(trans); GuiComponent::renderChildren(trans);
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
if (mIsPlaying && mContext.valid) if (mIsPlaying && mContext.valid) {
{ const unsigned int fadeIn = (unsigned int)(Math::clamp(0.0f, mFadeIn, 1.0f) * 255.0f);
const unsigned int fadeIn = (unsigned int)(Math::clamp(0.0f, mFadeIn, 1.0f) * 255.0f); const unsigned int color =
const unsigned int color = Renderer::convertColor((fadeIn << 24) | (fadeIn << 16) | (fadeIn << 8) | 255); Renderer::convertColor((fadeIn << 24) | (fadeIn << 16) | (fadeIn << 8) | 255);
Renderer::Vertex vertices[4]; Renderer::Vertex vertices[4];
vertices[0] = { { 0.0f , 0.0f }, { 0.0f, 0.0f }, color }; vertices[0] = { { 0.0f , 0.0f }, { 0.0f, 0.0f }, color };
vertices[1] = { { 0.0f , mSize.y() }, { 0.0f, 1.0f }, color }; vertices[1] = { { 0.0f , mSize.y() }, { 0.0f, 1.0f }, color };
vertices[2] = { { mSize.x(), 0.0f }, { 1.0f, 0.0f }, color }; vertices[2] = { { mSize.x(), 0.0f }, { 1.0f, 0.0f }, color };
vertices[3] = { { mSize.x(), mSize.y() }, { 1.0f, 1.0f }, color }; vertices[3] = { { mSize.x(), mSize.y() }, { 1.0f, 1.0f }, color };
// round vertices // Round vertices.
for(int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
vertices[i].pos.round(); vertices[i].pos.round();
// Build a texture for the video frame // Build a texture for the video frame.
mTexture->initFromPixels((unsigned char*)mContext.surface->pixels, mContext.surface->w, mContext.surface->h); mTexture->initFromPixels((unsigned char*)mContext.surface->pixels,
mTexture->bind(); mContext.surface->w, mContext.surface->h);
mTexture->bind();
// Render it // Render it.
Renderer::drawTriangleStrips(&vertices[0], 4); Renderer::drawTriangleStrips(&vertices[0], 4);
} }
else else {
{ VideoComponent::renderSnapshot(parentTrans);
VideoComponent::renderSnapshot(parentTrans); }
}
} }
void VideoVlcComponent::setupContext() void VideoVlcComponent::setupContext()
{ {
if (!mContext.valid) if (!mContext.valid) {
{ // Create an RGBA surface to render the video into.
// Create an RGBA surface to render the video into mContext.surface = SDL_CreateRGBSurface(SDL_SWSURFACE, (int)mVideoWidth,
mContext.surface = SDL_CreateRGBSurface(SDL_SWSURFACE, (int)mVideoWidth, (int)mVideoHeight, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff); (int)mVideoHeight, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
mContext.mutex = SDL_CreateMutex(); mContext.mutex = SDL_CreateMutex();
mContext.valid = true; mContext.valid = true;
resize(); resize();
} }
} }
void VideoVlcComponent::freeContext() void VideoVlcComponent::freeContext()
{ {
if (mContext.valid) if (mContext.valid) {
{ SDL_FreeSurface(mContext.surface);
SDL_FreeSurface(mContext.surface); SDL_DestroyMutex(mContext.mutex);
SDL_DestroyMutex(mContext.mutex); mContext.valid = false;
mContext.valid = false; }
}
} }
void VideoVlcComponent::setupVLC(std::string subtitles) void VideoVlcComponent::setupVLC(std::string subtitles)
{ {
// If VLC hasn't been initialised yet then do it now // If VLC hasn't been initialised yet then do it now.
if (!mVLC) if (!mVLC) {
{ const char** args;
const char** args; const char* newargs[] = { "--quiet", "--sub-file", subtitles.c_str() };
const char* newargs[] = { "--quiet", "--sub-file", subtitles.c_str() }; const char* singleargs[] = { "--quiet" };
const char* singleargs[] = { "--quiet" }; int argslen = 0;
int argslen = 0;
if (!subtitles.empty()) if (!subtitles.empty()) {
{ argslen = sizeof(newargs) / sizeof(newargs[0]);
argslen = sizeof(newargs) / sizeof(newargs[0]); args = newargs;
args = newargs; }
} else {
else argslen = sizeof(singleargs) / sizeof(singleargs[0]);
{ args = singleargs;
argslen = sizeof(singleargs) / sizeof(singleargs[0]); }
args = singleargs; mVLC = libvlc_new(argslen, args);
} }
mVLC = libvlc_new(argslen, args);
}
} }
void VideoVlcComponent::handleLooping() void VideoVlcComponent::handleLooping()
{ {
if (mIsPlaying && mMediaPlayer) if (mIsPlaying && mMediaPlayer) {
{ libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer); if (state == libvlc_Ended) {
if (state == libvlc_Ended) if (!Settings::getInstance()->getBool("VideoAudio") ||
{ (Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
if (!Settings::getInstance()->getBool("VideoAudio") || libvlc_audio_set_mute(mMediaPlayer, 1);
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
{ //libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_audio_set_mute(mMediaPlayer, 1); libvlc_media_player_set_media(mMediaPlayer, mMedia);
} libvlc_media_player_play(mMediaPlayer);
//libvlc_media_player_set_position(mMediaPlayer, 0.0f); }
libvlc_media_player_set_media(mMediaPlayer, mMedia); }
libvlc_media_player_play(mMediaPlayer);
}
}
} }
void VideoVlcComponent::startVideo() void VideoVlcComponent::startVideo()
{ {
if (!mIsPlaying) { if (!mIsPlaying) {
mVideoWidth = 0; mVideoWidth = 0;
mVideoHeight = 0; mVideoHeight = 0;
#ifdef WIN32 #ifdef WIN32
std::string path(Utils::String::replace(mVideoPath, "/", "\\")); std::string path(Utils::String::replace(mVideoPath, "/", "\\"));
#else #else
std::string path(mVideoPath); std::string path(mVideoPath);
#endif #endif
// Make sure we have a video path // Make sure we have a video path.
if (mVLC && (path.size() > 0)) if (mVLC && (path.size() > 0)) {
{ // Set the video that we are going to be playing so we don't attempt to restart it.
// Set the video that we are going to be playing so we don't attempt to restart it mPlayingVideoPath = mVideoPath;
mPlayingVideoPath = mVideoPath;
// Open the media // Open the media.
mMedia = libvlc_media_new_path(mVLC, path.c_str()); mMedia = libvlc_media_new_path(mVLC, path.c_str());
if (mMedia) if (mMedia) {
{ unsigned track_count;
unsigned track_count; int parseResult;
int parseResult; libvlc_event_t vlcEvent;
libvlc_event_t vlcEvent;
// Asynchronous media parsing // Asynchronous media parsing.
libvlc_event_attach(libvlc_media_event_manager(mMedia), libvlc_MediaParsedChanged, VlcMediaParseCallback, 0); libvlc_event_attach(libvlc_media_event_manager(
parseResult = libvlc_media_parse_with_options(mMedia, libvlc_media_parse_local, -1); mMedia), libvlc_MediaParsedChanged, VlcMediaParseCallback, 0);
parseResult = libvlc_media_parse_with_options(mMedia, libvlc_media_parse_local, -1);
if (!parseResult) if (!parseResult) {
{ // Wait for a maximum of 1 second for the media parsing.
// Wait for a maximum of 1 second for the media parsing for (int i = 0; i < 200; i++) {
for(int i=0; i<200; i++) if (libvlc_media_get_parsed_status(mMedia))
{ break;
if ((libvlc_media_get_parsed_status(mMedia))) SDL_Delay(5);
break; };
SDL_Delay(5); }
};
}
libvlc_media_track_t** tracks; libvlc_media_track_t** tracks;
track_count = libvlc_media_tracks_get(mMedia, &tracks); track_count = libvlc_media_tracks_get(mMedia, &tracks);
for (unsigned track = 0; track < track_count; ++track) for (unsigned track = 0; track < track_count; ++track) {
{ if (tracks[track]->i_type == libvlc_track_video) {
if (tracks[track]->i_type == libvlc_track_video) mVideoWidth = tracks[track]->video->i_width;
{ mVideoHeight = tracks[track]->video->i_height;
mVideoWidth = tracks[track]->video->i_width; break;
mVideoHeight = tracks[track]->video->i_height; }
break; }
} libvlc_media_tracks_release(tracks, track_count);
}
libvlc_media_tracks_release(tracks, track_count);
// Make sure we found a valid video track // Make sure we found a valid video track.
if ((mVideoWidth > 0) && (mVideoHeight > 0)) if ((mVideoWidth > 0) && (mVideoHeight > 0)) {
{ #ifndef _RPI_
#ifndef _RPI_ if (mScreensaverMode) {
if (mScreensaverMode) if (!Settings::getInstance()->getBool("CaptionsCompatibility")) {
{
if(!Settings::getInstance()->getBool("CaptionsCompatibility")) {
Vector2f resizeScale((Renderer::getScreenWidth() / (float)mVideoWidth), (Renderer::getScreenHeight() / (float)mVideoHeight)); Vector2f resizeScale((Renderer::getScreenWidth() / (float)mVideoWidth),
(Renderer::getScreenHeight() / (float)mVideoHeight));
if(resizeScale.x() < resizeScale.y()) if (resizeScale.x() < resizeScale.y()) {
{ mVideoWidth = (unsigned int) (mVideoWidth * resizeScale.x());
mVideoWidth = (unsigned int) (mVideoWidth * resizeScale.x()); mVideoHeight = (unsigned int) (mVideoHeight * resizeScale.x());
mVideoHeight = (unsigned int) (mVideoHeight * resizeScale.x()); }
}else{ else {
mVideoWidth = (unsigned int) (mVideoWidth * resizeScale.y()); mVideoWidth = (unsigned int) (mVideoWidth * resizeScale.y());
mVideoHeight = (unsigned int) (mVideoHeight * resizeScale.y()); mVideoHeight = (unsigned int) (mVideoHeight * resizeScale.y());
} }
} }
} }
#endif #endif
PowerSaver::pause(); PowerSaver::pause();
setupContext(); setupContext();
// Setup the media player // Setup the media player.
mMediaPlayer = libvlc_media_player_new_from_media(mMedia); mMediaPlayer = libvlc_media_player_new_from_media(mMedia);
if (!Settings::getInstance()->getBool("VideoAudio") || if (!Settings::getInstance()->getBool("VideoAudio") ||
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode)) (Settings::getInstance()->getBool("ScreenSaverVideoMute") &&
{ mScreensaverMode))
libvlc_audio_set_mute(mMediaPlayer, 1); libvlc_audio_set_mute(mMediaPlayer, 1);
}
libvlc_media_player_play(mMediaPlayer); libvlc_media_player_play(mMediaPlayer);
libvlc_video_set_callbacks(mMediaPlayer, lock, unlock, display, (void*)&mContext); libvlc_video_set_callbacks(mMediaPlayer, lock, unlock, display,
libvlc_video_set_format(mMediaPlayer, "RGBA", (int)mVideoWidth, (int)mVideoHeight, (int)mVideoWidth * 4); (void*)&mContext);
libvlc_video_set_format(mMediaPlayer, "RGBA", (int)mVideoWidth,
(int)mVideoHeight, (int)mVideoWidth * 4);
// Update the playing state // Update the playing state.
mIsPlaying = true; mIsPlaying = true;
mFadeIn = 0.0f; mFadeIn = 0.0f;
} }
} }
} }
} }
} }
void VideoVlcComponent::stopVideo() void VideoVlcComponent::stopVideo()
{ {
mIsPlaying = false; mIsPlaying = false;
mStartDelayed = false; mStartDelayed = false;
// Release the media player so it stops calling back to us // Release the media player so it stops calling back to us.
if (mMediaPlayer) if (mMediaPlayer) {
{ libvlc_media_player_stop(mMediaPlayer);
libvlc_media_player_stop(mMediaPlayer); libvlc_media_player_release(mMediaPlayer);
libvlc_media_player_release(mMediaPlayer); libvlc_media_release(mMedia);
libvlc_media_release(mMedia); mMediaPlayer = nullptr;
mMediaPlayer = NULL; freeContext();
freeContext(); PowerSaver::resume();
PowerSaver::resume(); }
}
} }

View file

@ -1,8 +1,15 @@
//
// VideoVlcComponent.h
//
// Video playing using libVLC.
//
#pragma once #pragma once
#ifndef ES_CORE_COMPONENTS_VIDEO_VLC_COMPONENT_H #ifndef ES_CORE_COMPONENTS_VIDEO_VLC_COMPONENT_H
#define ES_CORE_COMPONENTS_VIDEO_VLC_COMPONENT_H #define ES_CORE_COMPONENTS_VIDEO_VLC_COMPONENT_H
#include "VideoComponent.h" #include "VideoComponent.h"
#include <vlc/vlc.h> #include <vlc/vlc.h>
struct SDL_mutex; struct SDL_mutex;
@ -12,64 +19,62 @@ struct libvlc_media_t;
struct libvlc_media_player_t; struct libvlc_media_player_t;
struct VideoContext { struct VideoContext {
SDL_Surface* surface; SDL_Surface* surface;
SDL_mutex* mutex; SDL_mutex* mutex;
bool valid; bool valid;
}; };
class VideoVlcComponent : public VideoComponent class VideoVlcComponent : public VideoComponent
{ {
// Structure that groups together the configuration of the video component // Structure that groups together the configuration of the video component.
struct Configuration struct Configuration {
{ unsigned startDelay;
unsigned startDelay; bool showSnapshotNoVideo;
bool showSnapshotNoVideo; bool showSnapshotDelay;
bool showSnapshotDelay; std::string defaultVideoPath;
std::string defaultVideoPath; };
};
public: public:
static void setupVLC(std::string subtitles); static void setupVLC(std::string subtitles);
VideoVlcComponent(Window* window, std::string subtitles); VideoVlcComponent(Window* window, std::string subtitles);
virtual ~VideoVlcComponent(); virtual ~VideoVlcComponent();
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
// Resize the video to fit this size. If one axis is zero, scale that axis to maintain
// aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are
// zero, no resizing. This can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
void setResize(float width, float height) override;
// Resize the video to fit this size. If one axis is zero, scale that axis to maintain aspect ratio. // Resize the video to be as large as possible but fit within a box of this size.
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing. // This can be set before or after a video is loaded.
// Can be set before or after a video is loaded. // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
// setMaxSize() and setResize() are mutually exclusive. void setMaxSize(float width, float height) override;
void setResize(float width, float height) override;
// Resize the video to be as large as possible but fit within a box of this size.
// Can be set before or after a video is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
void setMaxSize(float width, float height) override;
private: private:
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize). // Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change. // Used internally whenever the resizing parameters or texture change.
void resize(); void resize();
// Start the video Immediately // Start the video immediately.
virtual void startVideo() override; virtual void startVideo() override;
// Stop the video // Stop the video.
virtual void stopVideo() override; virtual void stopVideo() override;
// Handle looping the video. Must be called periodically // Handle looping the video. Must be called periodically.
virtual void handleLooping() override; virtual void handleLooping() override;
void setupContext(); void setupContext();
void freeContext(); void freeContext();
static void VlcMediaParseCallback(const libvlc_event_t *event, void *user_data) {}; static void VlcMediaParseCallback(const libvlc_event_t *event, void *user_data) {};
private: private:
static libvlc_instance_t* mVLC; static libvlc_instance_t* mVLC;
libvlc_media_t* mMedia; libvlc_media_t* mMedia;
libvlc_media_player_t* mMediaPlayer; libvlc_media_player_t* mMediaPlayer;
VideoContext mContext; VideoContext mContext;
std::shared_ptr<TextureResource> mTexture; std::shared_ptr<TextureResource> mTexture;
}; };
#endif // ES_CORE_COMPONENTS_VIDEO_VLC_COMPONENT_H #endif // ES_CORE_COMPONENTS_VIDEO_VLC_COMPONENT_H

View file

@ -211,29 +211,29 @@ GuiInputConfig::GuiInputConfig(
Input input; Input input;
okFunction(); okFunction();
// Temporarily commented out, needs to be properly cleaned up later. // Temporarily commented out, needs to be properly cleaned up later.
// if (!mTargetConfig->getInputByName("HotKeyEnable", &input)) { // if (!mTargetConfig->getInputByName("HotKeyEnable", &input)) {
// mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), // mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
// "YOU DIDN'T CHOOSE A HOTKEY ENABLE BUTTON. THIS IS REQUIRED FOR EXITING GAMES " // "YOU DIDN'T CHOOSE A HOTKEY ENABLE BUTTON. THIS IS REQUIRED FOR EXITING GAMES "
// "WITH A CONTROLLER. DO YOU WANT TO USE THE SELECT BUTTON DEFAULT ? PLEASE ANSWER " // "WITH A CONTROLLER. DO YOU WANT TO USE THE SELECT BUTTON DEFAULT ? PLEASE ANSWER "
// "YES TO USE SELECT OR NO TO NOT SET A HOTKEY ENABLE BUTTON.", // "YES TO USE SELECT OR NO TO NOT SET A HOTKEY ENABLE BUTTON.",
// "YES", [this, okFunction] { // "YES", [this, okFunction] {
// Input input; // Input input;
// mTargetConfig->getInputByName("Select", &input); // mTargetConfig->getInputByName("Select", &input);
// mTargetConfig->mapInput("HotKeyEnable", input); // mTargetConfig->mapInput("HotKeyEnable", input);
// okFunction(); // okFunction();
// }, // },
// "NO", [this, okFunction] { // "NO", [this, okFunction] {
// // for a disabled hotkey enable button, set to a key with id 0, // // for a disabled hotkey enable button, set to a key with id 0,
// // so the input configuration script can be backwards compatible. // // so the input configuration script can be backwards compatible.
// mTargetConfig->mapInput("HotKeyEnable", Input(DEVICE_KEYBOARD, // mTargetConfig->mapInput("HotKeyEnable", Input(DEVICE_KEYBOARD,
// TYPE_KEY, 0, 1, true)); // TYPE_KEY, 0, 1, true));
// okFunction(); // okFunction();
// } // }
// )); // ));
// } // }
// else { // else {
// okFunction(); // okFunction();
// } // }
})); }));
mButtonGrid = makeButtonGrid(mWindow, buttons); mButtonGrid = makeButtonGrid(mWindow, buttons);

View file

@ -1,7 +1,7 @@
// //
// Renderer.cpp // Renderer.cpp
// //
// Rendering functions. // General rendering functions.
// //
#include "renderers/Renderer.h" #include "renderers/Renderer.h"

View file

@ -1,7 +1,7 @@
// //
// Renderer.h // Renderer.h
// //
// Rendering functions. // General rendering functions.
// //
#pragma once #pragma once

View file

@ -60,7 +60,7 @@ const ResourceData ResourceManager::getFileData(const std::string& path) const
} }
// If the file doesn't exist, return an "empty" ResourceData. // If the file doesn't exist, return an "empty" ResourceData.
ResourceData data = {NULL, 0}; ResourceData data = {nullptr, 0};
return data; return data;
} }

View file

@ -54,7 +54,7 @@ bool TextureData::initSVGFromMemory(const unsigned char* fileData, size_t length
// nsvgParse excepts a modifiable, null-terminated string. // nsvgParse excepts a modifiable, null-terminated string.
char* copy = (char*)malloc(length + 1); char* copy = (char*)malloc(length + 1);
assert(copy != NULL); assert(copy != nullptr);
memcpy(copy, fileData, length); memcpy(copy, fileData, length);
copy[length] = '\0'; copy[length] = '\0';