Files
swr-ini-tool/lib/borealis/library/lib/application.cpp
Niklas080208 617265f004 Initial commit
2026-02-11 20:33:01 +01:00

992 lines
29 KiB
C++

/*
Borealis, a Nintendo Switch UI Library
Copyright (C) 2019-2020 natinusala
Copyright (C) 2019 p-sam
Copyright (C) 2020 WerWolv
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <algorithm>
#include <borealis.hpp>
#include <string>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glad.h>
#define GLM_FORCE_PURE
#define GLM_ENABLE_EXPERIMENTAL
#include <nanovg.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#define NANOVG_GL3_IMPLEMENTATION
#include <nanovg_gl.h>
#ifdef __SWITCH__
#include <switch.h>
#endif
#include <chrono>
#include <set>
#include <thread>
// Constants used for scaling as well as
// creating a window of the right size on PC
constexpr uint32_t WINDOW_WIDTH = 1280;
constexpr uint32_t WINDOW_HEIGHT = 720;
#define DEFAULT_FPS 60
#define BUTTON_REPEAT_DELAY 15
#define BUTTON_REPEAT_CADENCY 5
// glfw code from the glfw hybrid app by fincs
// https://github.com/fincs/hybrid_app
namespace brls
{
// TODO: Use this instead of a glViewport each frame
static void windowFramebufferSizeCallback(GLFWwindow* window, int width, int height)
{
if (!width || !height)
return;
glViewport(0, 0, width, height);
Application::windowScale = (float)width / (float)WINDOW_WIDTH;
float contentHeight = ((float)height / (Application::windowScale * (float)WINDOW_HEIGHT)) * (float)WINDOW_HEIGHT;
Application::contentWidth = WINDOW_WIDTH;
Application::contentHeight = (unsigned)roundf(contentHeight);
Application::resizeNotificationManager();
Logger::info("Window size changed to %dx%d", width, height);
Logger::info("New scale factor is %f", Application::windowScale);
}
static void joystickCallback(int jid, int event)
{
if (event == GLFW_CONNECTED)
{
Logger::info("Joystick %d connected", jid);
if (glfwJoystickIsGamepad(jid))
Logger::info("Joystick %d is gamepad: \"%s\"", jid, glfwGetGamepadName(jid));
}
else if (event == GLFW_DISCONNECTED)
Logger::info("Joystick %d disconnected", jid);
}
static void errorCallback(int errorCode, const char* description)
{
Logger::error("[GLFW:%d] %s", errorCode, description);
}
static void windowKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (action == GLFW_PRESS)
{
// Check for toggle-fullscreen combo
if (key == GLFW_KEY_ENTER && mods == GLFW_MOD_ALT)
{
static int saved_x, saved_y, saved_width, saved_height;
if (!glfwGetWindowMonitor(window))
{
// Back up window position/size
glfwGetWindowPos(window, &saved_x, &saved_y);
glfwGetWindowSize(window, &saved_width, &saved_height);
// Switch to fullscreen mode
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
}
else
{
// Switch back to windowed mode
glfwSetWindowMonitor(window, nullptr, saved_x, saved_y, saved_width, saved_height, GLFW_DONT_CARE);
}
}
}
}
bool Application::init(std::string title)
{
return Application::init(title, Style::horizon(), Theme::horizon());
}
bool Application::init(std::string title, Style style, Theme theme)
{
// Init rng
std::srand(std::time(nullptr));
// Init managers
Application::taskManager = new TaskManager();
Application::notificationManager = new NotificationManager();
// Init static variables
Application::currentStyle = style;
Application::currentFocus = nullptr;
Application::oldGamepad = {};
Application::gamepad = {};
Application::title = title;
// Init theme to defaults
Application::setTheme(theme);
// Init glfw
glfwSetErrorCallback(errorCallback);
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
if (!glfwInit())
{
Logger::error("Failed to initialize glfw");
return false;
}
// Create window
#ifdef __APPLE__
// Explicitly ask for a 3.2 context on OS X
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Force scaling off to keep desired framebuffer size
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#endif
Application::window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, title.c_str(), nullptr, nullptr);
if (!window)
{
Logger::error("glfw: failed to create window\n");
glfwTerminate();
return false;
}
// Configure window
glfwSetInputMode(window, GLFW_STICKY_KEYS, GLFW_TRUE);
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, windowFramebufferSizeCallback);
glfwSetKeyCallback(window, windowKeyCallback);
glfwSetJoystickCallback(joystickCallback);
// Load OpenGL routines using glad
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
glfwSwapInterval(1);
Logger::info("GL Vendor: %s", glGetString(GL_VENDOR));
Logger::info("GL Renderer: %s", glGetString(GL_RENDERER));
Logger::info("GL Version: %s", glGetString(GL_VERSION));
if (glfwJoystickIsGamepad(GLFW_JOYSTICK_1))
{
GLFWgamepadstate state;
Logger::info("Gamepad detected: %s", glfwGetGamepadName(GLFW_JOYSTICK_1));
glfwGetGamepadState(GLFW_JOYSTICK_1, &state);
}
// Initialize the scene
Application::vg = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_ANTIALIAS);
if (!vg)
{
Logger::error("Unable to init nanovg");
glfwTerminate();
return false;
}
windowFramebufferSizeCallback(window, WINDOW_WIDTH, WINDOW_HEIGHT);
glfwSetTime(0.0);
// Load fonts
#ifdef __SWITCH__
{
PlFontData font;
// Standard font
Result rc = plGetSharedFontByType(&font, PlSharedFontType_Standard);
if (R_SUCCEEDED(rc))
{
Logger::info("Using Switch shared font");
Application::fontStash.regular = Application::loadFontFromMemory("regular", font.address, font.size, false);
}
// Korean font
rc = plGetSharedFontByType(&font, PlSharedFontType_KO);
if (R_SUCCEEDED(rc))
{
Logger::info("Adding Switch shared Korean font");
Application::fontStash.korean = Application::loadFontFromMemory("korean", font.address, font.size, false);
nvgAddFallbackFontId(Application::vg, Application::fontStash.regular, Application::fontStash.korean);
}
// Extented font
rc = plGetSharedFontByType(&font, PlSharedFontType_NintendoExt);
if (R_SUCCEEDED(rc))
{
Logger::info("Using Switch shared symbols font");
Application::fontStash.sharedSymbols = Application::loadFontFromMemory("symbols", font.address, font.size, false);
}
}
#else
// Use illegal font if available
if (access(BOREALIS_ASSET("Illegal-Font.ttf"), F_OK) != -1)
Application::fontStash.regular = Application::loadFont("regular", BOREALIS_ASSET("Illegal-Font.ttf"));
else
Application::fontStash.regular = Application::loadFont("regular", BOREALIS_ASSET("inter/Inter-Switch.ttf"));
if (access(BOREALIS_ASSET("Wingdings.ttf"), F_OK) != -1)
Application::fontStash.sharedSymbols = Application::loadFont("sharedSymbols", BOREALIS_ASSET("Wingdings.ttf"));
#endif
// Material font
if (access(BOREALIS_ASSET("material/MaterialIcons-Regular.ttf"), F_OK) != -1)
Application::fontStash.material = Application::loadFont("material", BOREALIS_ASSET("material/MaterialIcons-Regular.ttf"));
// Set symbols font as fallback
if (Application::fontStash.sharedSymbols)
{
Logger::info("Using shared symbols font");
nvgAddFallbackFontId(Application::vg, Application::fontStash.regular, Application::fontStash.sharedSymbols);
}
else
{
Logger::error("Shared symbols font not found");
}
// Set Material as fallback
if (Application::fontStash.material)
{
Logger::info("Using Material font");
nvgAddFallbackFontId(Application::vg, Application::fontStash.regular, Application::fontStash.material);
}
else
{
Logger::error("Material font not found");
}
// Load theme
#ifdef __SWITCH__
ColorSetId nxTheme;
setsysGetColorSetId(&nxTheme);
if (nxTheme == ColorSetId_Dark)
Application::currentThemeVariant = ThemeVariant_DARK;
else
Application::currentThemeVariant = ThemeVariant_LIGHT;
#else
char* themeEnv = getenv("BOREALIS_THEME");
if (themeEnv != nullptr && !strcasecmp(themeEnv, "DARK"))
Application::currentThemeVariant = ThemeVariant_DARK;
else
Application::currentThemeVariant = ThemeVariant_LIGHT;
#endif
// Init window size
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
Application::windowWidth = viewport[2];
Application::windowHeight = viewport[3];
// Init animations engine
menu_animation_init();
// Default FPS cap
Application::setMaximumFPS(DEFAULT_FPS);
return true;
}
bool Application::mainLoop()
{
// Frame start
retro_time_t frameStart = 0;
if (Application::frameTime > 0.0f)
frameStart = cpu_features_get_time_usec();
// glfw events
bool is_active;
do
{
is_active = !glfwGetWindowAttrib(Application::window, GLFW_ICONIFIED);
if (is_active)
glfwPollEvents();
else
glfwWaitEvents();
if (glfwWindowShouldClose(Application::window))
{
Application::exit();
return false;
}
} while (!is_active);
// libnx applet main loop
#ifdef __SWITCH__
if (!appletMainLoop())
{
Application::exit();
return false;
}
#endif
// Gamepad
if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &Application::gamepad))
{
// Keyboard -> DPAD Mapping
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT] = glfwGetKey(window, GLFW_KEY_LEFT);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] = glfwGetKey(window, GLFW_KEY_RIGHT);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP] = glfwGetKey(window, GLFW_KEY_UP);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN] = glfwGetKey(window, GLFW_KEY_DOWN);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_START] = glfwGetKey(window, GLFW_KEY_ESCAPE);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_BACK] = glfwGetKey(window, GLFW_KEY_F1);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_A] = glfwGetKey(window, GLFW_KEY_ENTER);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_B] = glfwGetKey(window, GLFW_KEY_BACKSPACE);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] = glfwGetKey(window, GLFW_KEY_L);
Application::gamepad.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] = glfwGetKey(window, GLFW_KEY_R);
}
// Trigger gamepad events
// TODO: Translate axis events to dpad events here
bool anyButtonPressed = false;
bool repeating = false;
static retro_time_t buttonPressTime = 0;
static int repeatingButtonTimer = 0;
for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++)
{
if (Application::gamepad.buttons[i] == GLFW_PRESS)
{
anyButtonPressed = true;
repeating = (repeatingButtonTimer > BUTTON_REPEAT_DELAY && repeatingButtonTimer % BUTTON_REPEAT_CADENCY == 0);
if (Application::oldGamepad.buttons[i] != GLFW_PRESS || repeating)
Application::onGamepadButtonPressed(i, repeating);
}
if (Application::gamepad.buttons[i] != Application::oldGamepad.buttons[i])
buttonPressTime = repeatingButtonTimer = 0;
}
if (anyButtonPressed && cpu_features_get_time_usec() - buttonPressTime > 1000)
{
buttonPressTime = cpu_features_get_time_usec();
repeatingButtonTimer++; // Increased once every ~1ms
}
Application::oldGamepad = Application::gamepad;
// Handle window size changes
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
unsigned newWidth = viewport[2];
unsigned newHeight = viewport[3];
if (Application::windowWidth != newWidth || Application::windowHeight != newHeight)
{
Application::windowWidth = newWidth;
Application::windowHeight = newHeight;
Application::onWindowSizeChanged();
}
// Animations
menu_animation_update();
// Tasks
Application::taskManager->frame();
// Render
Application::frame();
glfwSwapBuffers(window);
// Sleep if necessary
if (Application::frameTime > 0.0f)
{
retro_time_t currentFrameTime = cpu_features_get_time_usec() - frameStart;
retro_time_t frameTime = (retro_time_t)(Application::frameTime * 1000);
if (frameTime > currentFrameTime)
{
retro_time_t toSleep = frameTime - currentFrameTime;
std::this_thread::sleep_for(std::chrono::microseconds(toSleep));
}
}
return true;
}
void Application::quit()
{
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
void Application::navigate(FocusDirection direction)
{
View* currentFocus = Application::currentFocus;
// Do nothing if there is no current focus or if it doesn't have a parent
// (in which case there is nothing to traverse)
if (!currentFocus || !currentFocus->hasParent())
return;
// Get next view to focus by traversing the views tree upwards
View* nextFocus = currentFocus->getParent()->getNextFocus(direction, currentFocus->getParentUserData());
while (!nextFocus) // stop when we find a view to focus
{
if (!currentFocus->hasParent() || !currentFocus->getParent()->hasParent()) // stop when we reach the root of the tree
break;
currentFocus = currentFocus->getParent();
nextFocus = currentFocus->getParent()->getNextFocus(direction, currentFocus->getParentUserData());
}
// No view to focus at the end of the traversal: wiggle and return
if (!nextFocus)
{
Application::currentFocus->shakeHighlight(direction);
return;
}
// Otherwise give it focus
Application::giveFocus(nextFocus);
}
void Application::onGamepadButtonPressed(char button, bool repeating)
{
if (Application::blockInputsTokens != 0)
return;
if (repeating && Application::repetitionOldFocus == Application::currentFocus)
return;
Application::repetitionOldFocus = Application::currentFocus;
// Actions
if (Application::handleAction(button))
return;
// Navigation
// Only navigate if the button hasn't been consumed by an action
// (allows overriding DPAD buttons using actions)
switch (button)
{
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
Application::navigate(FocusDirection::DOWN);
break;
case GLFW_GAMEPAD_BUTTON_DPAD_UP:
Application::navigate(FocusDirection::UP);
break;
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT:
Application::navigate(FocusDirection::LEFT);
break;
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
Application::navigate(FocusDirection::RIGHT);
break;
default:
break;
}
}
View* Application::getCurrentFocus()
{
return Application::currentFocus;
}
bool Application::handleAction(char button)
{
View* hintParent = Application::currentFocus;
std::set<Key> consumedKeys;
while (hintParent != nullptr)
{
for (auto& action : hintParent->getActions())
{
if (action.key != static_cast<Key>(button))
continue;
if (consumedKeys.find(action.key) != consumedKeys.end())
continue;
if (action.available)
if (action.actionListener())
consumedKeys.insert(action.key);
}
hintParent = hintParent->getParent();
}
return !consumedKeys.empty();
}
void Application::frame()
{
// Frame context
FrameContext frameContext = FrameContext();
frameContext.pixelRatio = (float)Application::windowWidth / (float)Application::windowHeight;
frameContext.vg = Application::vg;
frameContext.fontStash = &Application::fontStash;
frameContext.theme = Application::getThemeValues();
nvgBeginFrame(Application::vg, Application::windowWidth, Application::windowHeight, frameContext.pixelRatio);
nvgScale(Application::vg, Application::windowScale, Application::windowScale);
// GL Clear
glClearColor(
frameContext.theme->backgroundColor[0],
frameContext.theme->backgroundColor[1],
frameContext.theme->backgroundColor[2],
1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
std::vector<View*> viewsToDraw;
// Draw all views in the stack
// until we find one that's not translucent
for (size_t i = 0; i < Application::viewStack.size(); i++)
{
View* view = Application::viewStack[Application::viewStack.size() - 1 - i];
viewsToDraw.push_back(view);
if (!view->isTranslucent())
break;
}
for (size_t i = 0; i < viewsToDraw.size(); i++)
{
View* view = viewsToDraw[viewsToDraw.size() - 1 - i];
view->frame(&frameContext);
}
// Framerate counter
if (Application::framerateCounter)
Application::framerateCounter->frame(&frameContext);
// Notifications
Application::notificationManager->frame(&frameContext);
// End frame
nvgResetTransform(Application::vg); // scale
nvgEndFrame(Application::vg);
}
void Application::exit()
{
Application::clear();
if (Application::vg)
nvgDeleteGL3(Application::vg);
glfwTerminate();
menu_animation_free();
if (Application::framerateCounter)
delete Application::framerateCounter;
delete Application::taskManager;
delete Application::notificationManager;
}
void Application::setDisplayFramerate(bool enabled)
{
if (!Application::framerateCounter && enabled)
{
Logger::info("Enabling framerate counter");
Application::framerateCounter = new FramerateCounter();
Application::resizeFramerateCounter();
}
else if (Application::framerateCounter && !enabled)
{
Logger::info("Disabling framerate counter");
delete Application::framerateCounter;
Application::framerateCounter = nullptr;
}
}
void Application::toggleFramerateDisplay()
{
Application::setDisplayFramerate(!Application::framerateCounter);
}
void Application::resizeFramerateCounter()
{
if (!Application::framerateCounter)
return;
Style* style = Application::getStyle();
unsigned framerateCounterWidth = style->FramerateCounter.width;
unsigned width = WINDOW_WIDTH;
Application::framerateCounter->setBoundaries(
width - framerateCounterWidth,
0,
framerateCounterWidth,
style->FramerateCounter.height);
Application::framerateCounter->invalidate();
}
void Application::resizeNotificationManager()
{
Application::notificationManager->setBoundaries(0, 0, Application::contentWidth, Application::contentHeight);
Application::notificationManager->invalidate();
}
void Application::notify(std::string text)
{
Application::notificationManager->notify(text);
}
NotificationManager* Application::getNotificationManager()
{
return Application::notificationManager;
}
void Application::giveFocus(View* view)
{
View* oldFocus = Application::currentFocus;
View* newFocus = view ? view->getDefaultFocus() : nullptr;
if (oldFocus != newFocus)
{
if (oldFocus)
oldFocus->onFocusLost();
Application::currentFocus = newFocus;
Application::globalFocusChangeEvent.fire(newFocus);
if (newFocus)
{
newFocus->onFocusGained();
Logger::debug("Giving focus to %s", newFocus->describe().c_str());
}
}
}
void Application::popView(ViewAnimation animation, std::function<void(void)> cb)
{
if (Application::viewStack.size() <= 1) // never pop the root view
return;
Application::blockInputs();
View* last = Application::viewStack[Application::viewStack.size() - 1];
last->willDisappear(true);
last->setForceTranslucent(true);
bool wait = animation == ViewAnimation::FADE; // wait for the new view animation to be done before showing the old one?
// Hide animation (and show previous view, if any)
last->hide([last, animation, wait, cb]() {
last->setForceTranslucent(false);
Application::viewStack.pop_back();
delete last;
// Animate the old view once the new one
// has ended its animation
if (Application::viewStack.size() > 0 && wait)
{
View* newLast = Application::viewStack[Application::viewStack.size() - 1];
if (newLast->isHidden())
{
newLast->willAppear(false);
newLast->show(cb, true, animation);
}
else
{
cb();
}
}
Application::unblockInputs();
},
true, animation);
// Animate the old view immediately
if (!wait && Application::viewStack.size() > 1)
{
View* toShow = Application::viewStack[Application::viewStack.size() - 2];
toShow->willAppear(false);
toShow->show(cb, true, animation);
}
// Focus
if (Application::focusStack.size() > 0)
{
View* newFocus = Application::focusStack[Application::focusStack.size() - 1];
Logger::debug("Giving focus to %s, and removing it from the focus stack", newFocus->describe().c_str());
Application::giveFocus(newFocus);
Application::focusStack.pop_back();
}
}
void Application::pushView(View* view, ViewAnimation animation)
{
Application::blockInputs();
// Call hide() on the previous view in the stack if no
// views are translucent, then call show() once the animation ends
View* last = nullptr;
if (Application::viewStack.size() > 0)
last = Application::viewStack[Application::viewStack.size() - 1];
bool fadeOut = last && !last->isTranslucent() && !view->isTranslucent(); // play the fade out animation?
bool wait = animation == ViewAnimation::FADE; // wait for the old view animation to be done before showing the new one?
view->registerAction("Exit", Key::PLUS, [] { Application::quit(); return true; });
view->registerAction(
"FPS", Key::MINUS, [] { Application::toggleFramerateDisplay(); return true; }, true);
// Fade out animation
if (fadeOut)
{
view->setForceTranslucent(true); // set the new view translucent until the fade out animation is done playing
// Animate the new view directly
if (!wait)
{
view->show([]() {
Application::unblockInputs();
},
true, animation);
}
last->hide([animation, wait]() {
View* newLast = Application::viewStack[Application::viewStack.size() - 1];
newLast->setForceTranslucent(false);
// Animate the new view once the old one
// has ended its animation
if (wait)
newLast->show([]() { Application::unblockInputs(); }, true, animation);
},
true, animation);
}
view->setBoundaries(0, 0, Application::contentWidth, Application::contentHeight);
if (!fadeOut)
view->show([]() { Application::unblockInputs(); }, true, animation);
else
view->alpha = 0.0f;
// Focus
if (Application::viewStack.size() > 0)
{
Logger::debug("Pushing %s to the focus stack", Application::currentFocus->describe().c_str());
Application::focusStack.push_back(Application::currentFocus);
}
// Layout and prepare view
view->invalidate(true);
view->willAppear(true);
Application::giveFocus(view->getDefaultFocus());
// And push it
Application::viewStack.push_back(view);
}
void Application::onWindowSizeChanged()
{
Logger::debug("Layout triggered");
for (View* view : Application::viewStack)
{
view->setBoundaries(0, 0, Application::contentWidth, Application::contentHeight);
view->invalidate();
view->onWindowSizeChanged();
}
Application::resizeNotificationManager();
Application::resizeFramerateCounter();
}
void Application::clear()
{
for (View* view : Application::viewStack)
{
view->willDisappear(true);
delete view;
}
Application::viewStack.clear();
}
Style* Application::getStyle()
{
return &Application::currentStyle;
}
void Application::setTheme(Theme theme)
{
Application::currentTheme = theme;
}
ThemeValues* Application::getThemeValues()
{
return &Application::currentTheme.colors[Application::currentThemeVariant];
}
ThemeValues* Application::getThemeValuesForVariant(ThemeVariant variant)
{
return &Application::currentTheme.colors[variant];
}
ThemeVariant Application::getThemeVariant()
{
return Application::currentThemeVariant;
}
int Application::loadFont(const char* fontName, const char* filePath)
{
return nvgCreateFont(Application::vg, fontName, filePath);
}
int Application::loadFontFromMemory(const char* fontName, void* address, size_t size, bool freeData)
{
return nvgCreateFontMem(Application::vg, fontName, (unsigned char*)address, size, freeData);
}
int Application::findFont(const char* fontName)
{
return nvgFindFont(Application::vg, fontName);
}
void Application::crash(std::string text)
{
CrashFrame* crashFrame = new CrashFrame(text);
Application::pushView(crashFrame);
}
void Application::blockInputs()
{
Application::blockInputsTokens += 1;
}
void Application::unblockInputs()
{
if (Application::blockInputsTokens > 0)
Application::blockInputsTokens -= 1;
}
NVGcontext* Application::getNVGContext()
{
return Application::vg;
}
TaskManager* Application::getTaskManager()
{
return Application::taskManager;
}
void Application::setCommonFooter(std::string footer)
{
Application::commonFooter = footer;
}
std::string* Application::getCommonFooter()
{
return &Application::commonFooter;
}
FramerateCounter::FramerateCounter()
: Label(LabelStyle::LIST_ITEM, "FPS: ---")
{
this->setColor(nvgRGB(255, 255, 255));
this->setVerticalAlign(NVG_ALIGN_MIDDLE);
this->setHorizontalAlign(NVG_ALIGN_RIGHT);
this->setBackground(Background::BACKDROP);
this->lastSecond = cpu_features_get_time_usec() / 1000;
}
void FramerateCounter::frame(FrameContext* ctx)
{
// Update counter
retro_time_t current = cpu_features_get_time_usec() / 1000;
if (current - this->lastSecond >= 1000)
{
char fps[10];
snprintf(fps, sizeof(fps), "FPS: %03d", this->frames);
this->setText(std::string(fps));
this->invalidate(); // update width for background
this->frames = 0;
this->lastSecond = current;
}
this->frames++;
// Regular frame
Label::frame(ctx);
}
void Application::setMaximumFPS(unsigned fps)
{
if (fps == 0)
Application::frameTime = 0.0f;
else
{
Application::frameTime = 1000 / (float)fps;
}
Logger::info("Maximum FPS set to %d - using a frame time of %.2f ms", fps, Application::frameTime);
}
std::string Application::getTitle()
{
return Application::title;
}
GenericEvent* Application::getGlobalFocusChangeEvent()
{
return &Application::globalFocusChangeEvent;
}
VoidEvent* Application::getGlobalHintsUpdateEvent()
{
return &Application::globalHintsUpdateEvent;
}
FontStash* Application::getFontStash()
{
return &Application::fontStash;
}
} // namespace brls