Added a system status component

This commit is contained in:
Leon Styhre 2025-02-25 23:15:12 +01:00
parent eeff59773d
commit 86a554d1b2
16 changed files with 606 additions and 8 deletions

View file

@ -18,6 +18,7 @@
#include "MameNames.h"
#include "Scripting.h"
#include "SystemData.h"
#include "SystemStatus.h"
#include "UIModeController.h"
#include "Window.h"
#include "utils/FileSystemUtil.h"
@ -2103,10 +2104,12 @@ returnValue = Utils::Platform::launchGameUnix(command, startDirectory, runInBack
}
// Unless we're running in the background while the game is launched, re-enable the text
// scrolling that was disabled in ViewController.
// scrolling that was disabled in ViewController. Also poll the system status immediately
// in case something changed while the game was running.
if (!runInBackground) {
window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true);
SystemStatus::getInstance().pollImmediately();
}
// Update number of times the game has been launched.

View file

@ -14,6 +14,7 @@
#include "FileFilterIndex.h"
#include "Settings.h"
#include "SystemData.h"
#include "SystemStatus.h"
#include "Window.h"
#include "components/HelpComponent.h"
#include "guis/GuiTextEditKeyboardPopup.h"
@ -36,6 +37,7 @@ GuiSettings::GuiSettings(std::string title)
, mNeedsGoToStart {false}
, mNeedsGoToSystem {false}
, mNeedsGoToGroupedCollections {false}
, mNeedsUpdateStatusComponents {false}
, mInvalidateCachedBackground {false}
{
addChild(&mMenu);
@ -143,6 +145,15 @@ void GuiSettings::save()
ViewController::getInstance()->goToSystem(SystemData::sSystemVector.front(), false);
}
if (mNeedsUpdateStatusComponents) {
SystemStatus::getInstance().setCheckFlags();
SystemStatus::getInstance().pollImmediately();
// If we're not done within this time window it's not the end of the world,
// the indicators will still get updated during the next poll.
SDL_Delay(100);
mWindow->updateSystemStatusComponents();
}
if (mNeedsCollectionsUpdate) {
auto state = ViewController::getInstance()->getState();
// If we're in any view other than the grouped custom collections, always jump to the

View file

@ -58,6 +58,7 @@ public:
mGoToSystem = goToSystem;
};
void setNeedsGoToGroupedCollections() { mNeedsGoToGroupedCollections = true; }
void setNeedsUpdateStatusComponents() { mNeedsUpdateStatusComponents = true; }
void setNeedsCloseMenu(std::function<void()> closeFunction)
{
mCloseMenuFunction = closeFunction;
@ -84,6 +85,7 @@ private:
bool mNeedsGoToStart;
bool mNeedsGoToSystem;
bool mNeedsGoToGroupedCollections;
bool mNeedsUpdateStatusComponents;
bool mInvalidateCachedBackground;
};

View file

@ -20,6 +20,7 @@
#include "components/LottieAnimComponent.h"
#include "components/RatingComponent.h"
#include "components/ScrollableContainer.h"
#include "components/SystemStatusComponent.h"
#include "components/TextComponent.h"
#include "components/VideoFFmpegComponent.h"
#include "components/primary/CarouselComponent.h"

View file

@ -92,10 +92,14 @@ void GamelistView::onShow()
video->stopVideoPlayer();
mWindow->passClockComponents(&mClockComponents);
mWindow->passSystemStatusComponents(&mSystemStatusComponents);
for (auto& clock : mClockComponents)
clock->update(500);
for (auto& systemstatus : mSystemStatusComponents)
systemstatus->update(SystemStatus::updateTime);
mLastUpdated = nullptr;
GuiComponent::onShow();
@ -365,6 +369,11 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mClockComponents.emplace_back(std::make_unique<DateTimeComponent>());
mClockComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
}
else if (element.second.type == "systemstatus") {
mSystemStatusComponents.emplace_back(std::make_unique<SystemStatusComponent>());
mSystemStatusComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
mSystemStatusComponents.back()->updateGrid();
}
}
}
@ -375,6 +384,14 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mClockComponents.back()->update(1000);
}
if (mSystemStatusComponents.empty()) {
// Apply a default systemstatus if the theme does not contain any configuration for it.
mSystemStatusComponents.emplace_back(std::make_unique<SystemStatusComponent>());
mSystemStatusComponents.back()->applyTheme(theme, "gamelist", "systemstatus_default",
ThemeFlags::ALL);
mSystemStatusComponents.back()->updateGrid();
}
if (mPrimary == nullptr) {
mTextList = std::make_unique<TextListComponent<FileData*>>();
mPrimary = mTextList.get();

View file

@ -138,6 +138,7 @@ private:
std::vector<std::unique_ptr<TextComponent>> mGamelistInfoComponents;
std::vector<std::unique_ptr<HelpComponent>> mHelpComponents;
std::vector<std::unique_ptr<DateTimeComponent>> mClockComponents;
std::vector<std::unique_ptr<SystemStatusComponent>> mSystemStatusComponents;
};
#endif // ES_APP_VIEWS_GAMELIST_VIEW_H

View file

@ -248,6 +248,8 @@ void SystemView::onCursorChanged(const CursorState& state)
{
mWindow->passHelpComponents(nullptr);
mWindow->passClockComponents(&mSystemElements[mPrimary->getCursor()].clockComponents);
mWindow->passSystemStatusComponents(
&mSystemElements[mPrimary->getCursor()].systemStatusComponents);
if (Settings::getInstance()->getBool("CustomEventScripts") &&
Settings::getInstance()->getBool("CustomEventScriptsBrowsing")) {
@ -260,6 +262,9 @@ void SystemView::onCursorChanged(const CursorState& state)
for (auto& clock : mSystemElements[mPrimary->getCursor()].clockComponents)
clock->update(1000);
for (auto& systemstatus : mSystemElements[mPrimary->getCursor()].systemStatusComponents)
systemstatus->update(SystemStatus::updateTime);
// Reset horizontally scrolling text.
for (auto& text : mSystemElements[mPrimary->getCursor()].gameCountComponents)
text->resetComponent();
@ -745,6 +750,13 @@ void SystemView::populate()
elements.clockComponents.back()->applyTheme(theme, "system", element.first,
ThemeFlags::ALL);
}
else if (element.second.type == "systemstatus") {
elements.systemStatusComponents.emplace_back(
std::make_unique<SystemStatusComponent>());
elements.systemStatusComponents.back()->applyTheme(
theme, "system", element.first, ThemeFlags::ALL);
elements.systemStatusComponents.back()->updateGrid();
}
}
}
@ -756,6 +768,14 @@ void SystemView::populate()
elements.clockComponents.back()->update(1000);
}
if (elements.systemStatusComponents.empty()) {
// Apply a default systemstatus if the theme does not contain any configuration for it.
elements.systemStatusComponents.emplace_back(std::make_unique<SystemStatusComponent>());
elements.systemStatusComponents.back()->applyTheme(
theme, "system", "systemstatus_default", ThemeFlags::ALL);
elements.systemStatusComponents.back()->updateGrid();
}
std::stable_sort(
elements.children.begin(), elements.children.end(),
[](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); });
@ -921,6 +941,8 @@ void SystemView::populate()
mWindow->passHelpComponents(&mSystemElements[mPrimary->getCursor()].helpComponents);
mWindow->passClockComponents(&mSystemElements[mPrimary->getCursor()].clockComponents);
mWindow->passSystemStatusComponents(
&mSystemElements[mPrimary->getCursor()].systemStatusComponents);
mFadeTransitions = (static_cast<ViewTransitionAnimation>(Settings::getInstance()->getInt(
"TransitionsSystemToSystem")) == ViewTransitionAnimation::FADE);

View file

@ -19,6 +19,7 @@
#include "components/LottieAnimComponent.h"
#include "components/RatingComponent.h"
#include "components/ScrollableContainer.h"
#include "components/SystemStatusComponent.h"
#include "components/TextComponent.h"
#include "components/VideoFFmpegComponent.h"
#include "components/primary/CarouselComponent.h"
@ -139,6 +140,7 @@ private:
std::vector<std::unique_ptr<RatingComponent>> ratingComponents;
std::vector<std::unique_ptr<HelpComponent>> helpComponents;
std::vector<std::unique_ptr<DateTimeComponent>> clockComponents;
std::vector<std::unique_ptr<SystemStatusComponent>> systemStatusComponents;
};
Renderer* mRenderer;

View file

@ -63,6 +63,7 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollIndicatorComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SystemStatusComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h
@ -139,6 +140,7 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SystemStatusComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp

View file

@ -95,6 +95,15 @@ void SystemStatus::setCheckFlags()
mCheckWifi = Settings::getInstance()->getBool("SystemStatusWifi");
mCheckCellular = Settings::getInstance()->getBool("SystemStatusCellular");
mCheckBattery = Settings::getInstance()->getBool("SystemStatusBattery");
if (!mCheckBluetooth)
mHasBluetooth = false;
if (!mCheckWifi)
mHasWifi = false;
if (!mCheckCellular)
mHasCellular = false;
if (!mCheckBattery)
mHasBattery = false;
}
void SystemStatus::setPolling(const bool state)
@ -116,13 +125,33 @@ void SystemStatus::setPolling(const bool state)
}
}
SystemStatus::Status SystemStatus::getStatus()
SystemStatus::Status SystemStatus::getStatus(const bool update)
{
#if defined(__ANDROID__)
getStatusBluetooth();
getStatusWifi();
getStatusCellular();
getStatusBattery();
if (update) {
getStatusBluetooth();
getStatusWifi();
getStatusCellular();
getStatusBattery();
#if (DEBUG_SYSTEM_STATUS)
std::string status {"Bluetooth "};
status.append(mHasBluetooth ? "enabled" : "disabled")
.append(", Wi-Fi ")
.append(mHasWifi ? "enabled" : "disabled")
.append(", cellular ")
.append(mHasCellular ? "enabled" : "disabled")
.append(", battery ")
.append(mHasBattery ? "enabled" : "disabled");
if (mHasBattery) {
status.append(" (")
.append(mBatteryCharging ? "charging" : "not charging")
.append(" and at ")
.append(std::to_string(mBatteryCapacity))
.append("% capacity)");
}
LOG(LogDebug) << "SystemStatus::getStatus(): " << status;
#endif
}
#endif
mStatus.hasBluetooth = mHasBluetooth;
@ -166,7 +195,7 @@ void SystemStatus::pollStatus()
#endif
int delayValue {0};
while (!mPollImmediately && !mExitPolling && delayValue < 3000) {
while (!mPollImmediately && !mExitPolling && delayValue < pollingTime) {
delayValue += 100;
SDL_Delay(100);
}

View file

@ -23,6 +23,7 @@ public:
void setCheckFlags();
void setPolling(const bool state);
void pollImmediately() { mPollImmediately = true; }
const bool getPollImmediately() { return mPollImmediately; }
struct Status {
bool hasBluetooth;
@ -42,7 +43,10 @@ public:
}
};
Status getStatus();
Status getStatus(const bool update = true);
static constexpr int updateTime {300};
static constexpr int pollingTime {2500};
private:
SystemStatus() noexcept;

View file

@ -134,6 +134,8 @@ std::map<std::string, std::map<std::string, std::string>> ThemeData::sPropertyAt
{"customControllerIcon", "controller"}}},
{"helpsystem",
{{"customButtonIcon", "button"}}},
{"systemstatus",
{{"customIcon", "icon"}}},
};
std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
@ -589,6 +591,22 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"lineSpacing", FLOAT},
{"format", STRING},
{"opacity", FLOAT}}},
{"systemstatus",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"fontPath", PATH},
{"textRelativeScale", FLOAT},
{"color", COLOR},
{"backgroundColor", COLOR},
{"backgroundColorEnd", COLOR},
{"backgroundGradientType", STRING},
{"backgroundPadding", NORMALIZED_PAIR},
{"backgroundCornerRadius", FLOAT},
{"entries", STRING},
{"entrySpacing", FLOAT},
{"customIcon", PATH},
{"opacity", FLOAT}}},
{"sound",
{{"path", PATH}}}};
// clang-format on

View file

@ -32,6 +32,7 @@ Window::Window() noexcept
: mRenderer {Renderer::getInstance()}
, mHelpComponents {nullptr}
, mClockComponents {nullptr}
, mSystemStatusComponents {nullptr}
, mSplashTextPositions {0.0f, 0.0f, 0.0f, 0.0f}
, mBackgroundOverlayOpacity {1.0f}
, mScreensaver {nullptr}
@ -465,6 +466,11 @@ void Window::update(int deltaTime)
clockComponent->update(deltaTime);
}
if (mSystemStatusComponents != nullptr) {
for (auto& systemStatusComponent : *mSystemStatusComponents)
systemStatusComponent->update(deltaTime);
}
#if defined(__ANDROID__) || defined(__IOS__)
if (Settings::getInstance()->getBool("InputTouchOverlay"))
InputOverlay::getInstance().update(deltaTime);
@ -692,6 +698,11 @@ void Window::render()
clockComponent->render(trans);
}
if (mSystemStatusComponents != nullptr) {
for (auto& systemStatusComponent : *mSystemStatusComponents)
systemStatusComponent->render(trans);
}
if (mInfoPopup)
mInfoPopup->render(trans);

View file

@ -14,9 +14,11 @@
#include "HelpPrompt.h"
#include "InputConfig.h"
#include "Settings.h"
#include "SystemStatus.h"
#include "components/DateTimeComponent.h"
#include "components/HelpComponent.h"
#include "components/ImageComponent.h"
#include "components/SystemStatusComponent.h"
#include "components/TextComponent.h"
#include "guis/GuiInfoPopup.h"
#include "resources/Font.h"
@ -179,6 +181,20 @@ public:
mClockComponents = clockComponents;
}
void passSystemStatusComponents(
std::vector<std::unique_ptr<SystemStatusComponent>>* systemstatusComponents)
{
mSystemStatusComponents = systemstatusComponents;
}
void updateSystemStatusComponents()
{
if (mSystemStatusComponents != nullptr) {
for (auto& systemStatusComponent : *mSystemStatusComponents)
systemStatusComponent->update(SystemStatus::pollingTime);
}
}
private:
Window() noexcept;
~Window();
@ -198,6 +214,7 @@ private:
std::vector<std::unique_ptr<HelpComponent>>* mHelpComponents;
std::unique_ptr<HelpComponent> mHelp;
std::vector<std::unique_ptr<DateTimeComponent>>* mClockComponents;
std::vector<std::unique_ptr<SystemStatusComponent>>* mSystemStatusComponents;
std::unique_ptr<ImageComponent> mBackgroundOverlay;
std::unique_ptr<ImageComponent> mSplash;
std::unique_ptr<TextComponent> mSplashTextScanning;

View file

@ -0,0 +1,391 @@
// SPDX-License-Identifier: MIT
//
// ES-DE Frontend
// SystemStatusComponent.cpp
//
// Displays system status information (Bluetooth, Wi-Fi, cellular and battery).
//
#include "components/SystemStatusComponent.h"
#include "SystemStatus.h"
#include "Window.h"
#include "utils/FileSystemUtil.h"
#define PREFIX "icon_"
SystemStatusComponent::SystemStatusComponent()
: mRenderer {Renderer::getInstance()}
, mHasBluetooth {false}
, mHasWifi {false}
, mHasCellular {false}
, mHasBattery {false}
, mBatteryCharging {false}
, mBatteryCapacity {100}
, mEntries {sAllowedEntries}
, mColorShift {0xFFFFFFFF}
, mBackgroundColor {0x00000000}
, mBackgroundColorEnd {0x00000000}
, mAccumulator {0}
, mAccumulatorAndroid {0}
, mBackgroundPadding {0.0f, 0.0f}
, mBackgroundCornerRadius {0.0f}
, mColorGradientHorizontal {true}
, mEntrySpacing {0.005f * mRenderer->getScreenWidth()}
{
}
void SystemStatusComponent::updateGrid()
{
mGrid.reset();
if (Settings::getInstance()->getBool("SystemStatusDisplayAll")) {
mHasBluetooth = true;
mHasWifi = true;
mHasCellular = true;
mHasBattery = true;
}
mDisplayEntries.clear();
if (mHasBluetooth && Settings::getInstance()->getBool("SystemStatusBluetooth") &&
std::find(mEntries.cbegin(), mEntries.cend(), "bluetooth") != mEntries.cend())
mDisplayEntries.emplace_back("bluetooth");
if (mHasWifi && Settings::getInstance()->getBool("SystemStatusWifi") &&
std::find(mEntries.cbegin(), mEntries.cend(), "wifi") != mEntries.cend())
mDisplayEntries.emplace_back("wifi");
if (mHasCellular && Settings::getInstance()->getBool("SystemStatusCellular") &&
std::find(mEntries.cbegin(), mEntries.cend(), "cellular") != mEntries.cend())
mDisplayEntries.emplace_back("cellular");
if (mHasBattery && Settings::getInstance()->getBool("SystemStatusBattery") &&
std::find(mEntries.cbegin(), mEntries.cend(), "battery") != mEntries.cend())
mDisplayEntries.emplace_back("battery");
if (mDisplayEntries.empty())
return;
const bool batteryText {Settings::getInstance()->getBool("SystemStatusBattery") &&
Settings::getInstance()->getBool("SystemStatusBatteryPercentage")};
int numEntries {static_cast<int>(mDisplayEntries.size())};
if (mEntrySpacing != 0.0f)
numEntries += numEntries - 1;
if (mHasBattery && batteryText)
++numEntries;
mGrid = std::make_shared<ComponentGrid>(glm::ivec2 {numEntries, 1});
mEntryMap.clear();
float width {0.0f};
int i {0};
for (auto it = mDisplayEntries.cbegin(); it != mDisplayEntries.cend(); ++it) {
if (*it == "battery") {
mBattery = std::make_shared<ImageComponent>(false, true);
mBattery->setImage(mIconPathMap["battery_full"]);
mBattery->setColorShift(mColorShift);
mBattery->setResize(0, mSize.y);
mBattery->setOpacity(mThemeOpacity);
width += std::round(mBattery->getSize().x);
mGrid->setEntry(mBattery, glm::ivec2 {i, 0}, false, false);
}
else {
std::shared_ptr<ImageComponent> icon {std::make_shared<ImageComponent>(false, true)};
icon->setImage(mIconPathMap[*it]);
icon->setColorShift(mColorShift);
icon->setResize(0, mSize.y);
icon->setOpacity(mThemeOpacity);
width += std::round(icon->getSize().x);
mGrid->setEntry(icon, glm::ivec2 {i, 0}, false, false);
}
mEntryMap[*it] = i;
++i;
if (mEntrySpacing != 0.0f && *it != mDisplayEntries.back()) {
++i;
width += mEntrySpacing;
mGrid->setEntry(std::make_shared<GuiComponent>(), glm::ivec2 {i, 0}, false, false);
}
}
if (mHasBattery && batteryText) {
// We set the initial value to "100%" to calculate the cell size based on this, as this
// will be the longest text that will ever be displayed for the battery capacity.
mBatteryPercentage = std::make_shared<TextComponent>(
"100%", mFont, 0xFFFFFFFF, ALIGN_LEFT, ALIGN_CENTER, glm::ivec2 {1, 0},
glm::vec3 {0.0f, 0.0f, 0.0f}, glm::vec2 {0.0f, 0.0f}, 0x00000000, 1.0f);
mBatteryPercentage->setColor(mColorShift);
mBatteryPercentage->setOpacity(mThemeOpacity);
width += mBatteryPercentage->getSize().x;
mEntryMap["batteryText"] = i;
mGrid->setEntry(mBatteryPercentage, glm::ivec2 {i, 0}, false, false);
}
for (int i {0}; i < static_cast<int>(mGrid->getChildCount()); ++i) {
mGrid->setColWidthPerc(i, mGrid->getChild(i)->getSize().x / width);
if (mHasBattery && batteryText && i == static_cast<int>(mGrid->getChildCount()) - 2)
continue;
if (mEntrySpacing != 0.0f && i != static_cast<int>(mGrid->getChildCount()) - 1) {
++i;
mGrid->setColWidthPerc(i, mEntrySpacing / width);
}
}
mGrid->setSize(width, mSize.y);
mGrid->setOrigin(mOrigin);
mSize.x = width;
}
void SystemStatusComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties)
{
// Apply default settings as the theme may not define any configuration.
const glm::vec2 scale {glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}};
mPosition = glm::vec3 {0.984f * scale.x, 0.016f * scale.y, 0.0f};
mSize = glm::vec2 {0.0f, 0.035f} * scale;
mOrigin = glm::vec2 {1.0f, 0.0f};
mColor = 0xFFFFFFFF;
mIconPathMap.clear();
mIconPathMap["bluetooth"] = ":/graphics/systemstatus/bluetooth.svg";
mIconPathMap["wifi"] = ":/graphics/systemstatus/wifi.svg";
mIconPathMap["cellular"] = ":/graphics/systemstatus/cellular.svg";
mIconPathMap["battery_charging"] = ":/graphics/systemstatus/battery_charging.svg";
mIconPathMap["battery_low"] = ":/graphics/systemstatus/battery_low.svg";
mIconPathMap["battery_medium"] = ":/graphics/systemstatus/battery_medium.svg";
mIconPathMap["battery_high"] = ":/graphics/systemstatus/battery_high.svg";
mIconPathMap["battery_full"] = ":/graphics/systemstatus/battery_full.svg";
GuiComponent::applyTheme(theme, view, element, properties);
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "systemstatus")};
float textRelativeScale {0.9f};
if (!elem) {
mSize = glm::round(mSize);
mFont = {Font::get(mSize.y * textRelativeScale, FONT_PATH_LIGHT)};
return;
}
mSize.x = 0.0f;
mSize.y = glm::clamp(mSize.y, 0.01f * scale.y, 0.5f * scale.y);
mSize = glm::round(mSize);
if (elem->has("textRelativeScale"))
textRelativeScale = glm::clamp(elem->get<float>("textRelativeScale"), 0.5f, 1.0f);
if (elem->has("fontPath"))
mFont = {Font::get(mSize.y * textRelativeScale, elem->get<std::string>("fontPath"))};
else
mFont = {Font::get(mSize.y * textRelativeScale, FONT_PATH_LIGHT)};
if (elem->has("color"))
mColorShift = elem->get<unsigned int>("color");
if (elem->has("colorEnd"))
mColorShiftEnd = elem->get<unsigned int>("colorEnd");
else
mColorShiftEnd = mColorShift;
if (elem->has("backgroundColor")) {
mBackgroundColor = elem->get<unsigned int>("backgroundColor");
if (elem->has("backgroundColorEnd"))
mBackgroundColorEnd = elem->get<unsigned int>("backgroundColorEnd");
else
mBackgroundColorEnd = mBackgroundColor;
if (elem->has("backgroundGradientType")) {
const std::string& backgroundGradientType {
elem->get<std::string>("backgroundGradientType")};
if (backgroundGradientType == "horizontal") {
mColorGradientHorizontal = true;
}
else if (backgroundGradientType == "vertical") {
mColorGradientHorizontal = false;
}
else {
mColorGradientHorizontal = true;
LOG(LogWarning) << "SystemStatusComponent: Invalid theme configuration, property "
"\"backgroundGradientType\" for element \""
<< element.substr(13) << "\" defined as \""
<< backgroundGradientType << "\"";
}
}
if (elem->has("backgroundPadding")) {
const glm::vec2 backgroundPadding {
glm::clamp(elem->get<glm::vec2>("backgroundPadding"), 0.0f, 0.2f)};
mBackgroundPadding.x = backgroundPadding.x * mRenderer->getScreenWidth();
mBackgroundPadding.y = backgroundPadding.y * mRenderer->getScreenHeight();
}
if (elem->has("backgroundCornerRadius")) {
mBackgroundCornerRadius =
glm::clamp(elem->get<float>("backgroundCornerRadius"), 0.0f, 0.5f) *
mRenderer->getScreenWidth();
}
}
if (elem->has("entries")) {
// Replace possible whitespace separators with commas.
std::string entriesTag {Utils::String::toLower(elem->get<std::string>("entries"))};
for (auto& character : entriesTag) {
if (std::isspace(character))
character = ',';
}
entriesTag = Utils::String::replace(entriesTag, ",,", ",");
std::vector<std::string> entries {Utils::String::delimitedStringToVector(entriesTag, ",")};
// If the "all" value has been set then leave mEntries fully populated.
if (std::find(entries.begin(), entries.end(), "all") == entries.end()) {
mEntries.clear();
for (auto& allowedEntry : sAllowedEntries) {
if (std::find(entries.cbegin(), entries.cend(), allowedEntry) != entries.cend())
mEntries.emplace_back(allowedEntry);
}
}
}
if (elem->has("entrySpacing")) {
mEntrySpacing = std::round(glm::clamp(elem->get<float>("entrySpacing"), 0.0f, 0.04f) *
mRenderer->getScreenWidth());
}
// Custom entry icons.
// The names may look a bit strange when combined with the PREFIX string "icon_" but it's
// because ThemeData adds this prefix to avoid name collisions when using XML attributes.
if (elem->has(PREFIX "icon_wifi"))
mIconPathMap["wifi"] = elem->get<std::string>(PREFIX "icon_wifi");
if (elem->has(PREFIX "icon_bluetooth"))
mIconPathMap["bluetooth"] = elem->get<std::string>(PREFIX "icon_bluetooth");
if (elem->has(PREFIX "icon_cellular"))
mIconPathMap["cellular"] = elem->get<std::string>(PREFIX "icon_cellular");
if (elem->has(PREFIX "icon_battery_charging"))
mIconPathMap["battery_charging"] = elem->get<std::string>(PREFIX "icon_battery_charging");
if (elem->has(PREFIX "icon_battery_low"))
mIconPathMap["icon_battery_low"] = elem->get<std::string>(PREFIX "icon_battery_low");
if (elem->has(PREFIX "icon_battery_medium"))
mIconPathMap["battery_medium"] = elem->get<std::string>(PREFIX "icon_battery_medium");
if (elem->has(PREFIX "icon_battery_high"))
mIconPathMap["battery_high"] = elem->get<std::string>(PREFIX "icon_battery_high");
if (elem->has(PREFIX "icon_battery_full"))
mIconPathMap["battery_full"] = elem->get<std::string>(PREFIX "icon_battery_full");
}
void SystemStatusComponent::update(int deltaTime)
{
if (mEntries.empty())
return;
mAccumulator += deltaTime;
mAccumulatorAndroid += deltaTime;
if (mAccumulator >= SystemStatus::updateTime) {
#if defined(__ANDROID__)
// For Android we poll on the main thread instead of in a separate thread.
SystemStatus::Status status;
if (mAccumulatorAndroid >= SystemStatus::pollingTime ||
SystemStatus::getInstance().getPollImmediately()) {
status = SystemStatus::getInstance().getStatus(true);
mAccumulatorAndroid = 0;
}
else {
status = SystemStatus::getInstance().getStatus(false);
}
#else
SystemStatus::Status status {SystemStatus::getInstance().getStatus()};
#endif
mAccumulator = 0;
bool statusChanged {false};
bool batteryStatusChanged {false};
if (mHasBluetooth != status.hasBluetooth) {
mHasBluetooth = status.hasBluetooth;
statusChanged = true;
}
if (mHasWifi != status.hasWifi) {
mHasWifi = status.hasWifi;
statusChanged = true;
}
if (mHasCellular != status.hasCellular) {
mHasCellular = status.hasCellular;
statusChanged = true;
}
if (mHasBattery != status.hasBattery) {
mHasBattery = status.hasBattery;
statusChanged = true;
batteryStatusChanged = true;
}
if (mHasBattery && mBatteryCharging != status.batteryCharging) {
mBatteryCharging = status.batteryCharging;
batteryStatusChanged = true;
}
if (mHasBattery && mBatteryCapacity != status.batteryCapacity) {
mBatteryCapacity = status.batteryCapacity;
batteryStatusChanged = true;
}
if (statusChanged)
updateGrid();
if (mHasBattery && batteryStatusChanged) {
if (mBatteryPercentage != nullptr)
mBatteryPercentage->setValue(std::to_string(mBatteryCapacity) + "%");
if (mBatteryCharging)
mBattery->setImage(mIconPathMap["battery_charging"]);
else if (mBatteryCapacity >= 0 && mBatteryCapacity <= 25)
mBattery->setImage(mIconPathMap["battery_low"]);
else if (mBatteryCapacity >= 26 && mBatteryCapacity <= 60)
mBattery->setImage(mIconPathMap["battery_medium"]);
else if (mBatteryCapacity >= 61 && mBatteryCapacity <= 90)
mBattery->setImage(mIconPathMap["battery_high"]);
else if (mBatteryCapacity > 90)
mBattery->setImage(mIconPathMap["battery_full"]);
}
}
}
void SystemStatusComponent::render(const glm::mat4& parentTrans)
{
if (mDisplayEntries.empty())
return;
if (mBackgroundColor != 0x00000000) {
const glm::vec3 positionTemp {mPosition};
mPosition.x -= mBackgroundPadding.x / 2.0f;
mPosition.y -= mBackgroundPadding.y / 2.0f;
const glm::mat4 trans {parentTrans * getTransform()};
mRenderer->setMatrix(trans);
mRenderer->drawRect(0.0f, 0.0f, mSize.x + mBackgroundPadding.x,
mSize.y + mBackgroundPadding.y, mBackgroundColor, mBackgroundColorEnd,
mColorGradientHorizontal, mThemeOpacity, 1.0f,
Renderer::BlendFactor::SRC_ALPHA,
Renderer::BlendFactor::ONE_MINUS_SRC_ALPHA, mBackgroundCornerRadius);
mPosition = positionTemp;
}
if (mGrid) {
mGrid->setPosition(mPosition);
mGrid->setRotationOrigin(mRotationOrigin);
mGrid->setRotation(mRotation);
if (Settings::getInstance()->getBool("DebugImage")) {
const glm::mat4 trans {parentTrans * getTransform()};
mRenderer->setMatrix(trans);
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033);
}
mGrid->render(parentTrans);
}
}

View file

@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
//
// ES-DE Frontend
// SystemStatusComponent.h
//
// Displays system status information (Bluetooth, Wi-Fi, cellular and battery).
//
#ifndef ES_CORE_COMPONENTS_SYSTEM_STATUS_COMPONENT_H
#define ES_CORE_COMPONENTS_SYSTEM_STATUS_COMPONENT_H
#include "GuiComponent.h"
#include "components/ComponentGrid.h"
#include "components/ImageComponent.h"
#include "components/TextComponent.h"
#include "renderers/Renderer.h"
#include "resources/Font.h"
class SystemStatusComponent : public GuiComponent
{
public:
SystemStatusComponent();
void updateGrid();
void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties) override;
void update(int deltaTime) override;
void render(const glm::mat4& parent) override;
private:
Renderer* mRenderer;
std::shared_ptr<ComponentGrid> mGrid;
std::shared_ptr<Font> mFont;
std::shared_ptr<ImageComponent> mBattery;
std::shared_ptr<TextComponent> mBatteryPercentage;
bool mHasBluetooth;
bool mHasWifi;
bool mHasCellular;
bool mHasBattery;
bool mBatteryCharging;
int mBatteryCapacity;
std::vector<std::string> mEntries;
std::vector<std::string> mDisplayEntries;
std::map<std::string, int> mEntryMap;
std::map<std::string, std::string> mIconPathMap;
static inline std::vector<std::string> sAllowedEntries {"bluetooth", "wifi", "cellular",
"battery"};
unsigned int mColorShift;
unsigned int mBackgroundColor;
unsigned int mBackgroundColorEnd;
int mAccumulator;
int mAccumulatorAndroid;
glm::vec2 mBackgroundPadding;
float mBackgroundCornerRadius;
bool mColorGradientHorizontal;
float mEntrySpacing;
};
#endif // ES_CORE_COMPONENTS_SYSTEM_STATUS_COMPONENT_H