Initial commit

This commit is contained in:
Alexander Olofsson 2025-04-13 10:38:33 +02:00
commit e36facce47
Signed by: ace
GPG key ID: D439C9470CB04C73
20 changed files with 1090 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.cache/
build/
build_pi/

56
CMakeLists.txt Normal file
View file

@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.24...3.30)
project(BarStatusScreen)
include(FetchContent)
# Generate compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Dependencies
set(RAYLIB_VERSION 5.0)
FetchContent_Declare(
raylib
DOWNLOAD_EXTRACT_TIMESTAMP OFF
URL https://github.com/raysan5/raylib/archive/refs/tags/${RAYLIB_VERSION}.tar.gz
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(raylib)
# Our Project
add_executable(${PROJECT_NAME})
add_subdirectory(src)
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME})
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY $<TARGET_FILE_DIR:${PROJECT_NAME}>)
if ("${PLATFORM}" STREQUAL "Web")
add_custom_command(
TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/resources $<TARGET_FILE_DIR:${PROJECT_NAME}>/../resources
)
#DEPENDS ${PROJECT_NAME}
else()
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/resources $<TARGET_FILE_DIR:${PROJECT_NAME}>/resources
)
#DEPENDS ${PROJECT_NAME}
endif()
#set(raylib_VERBOSE 1)
target_link_libraries(${PROJECT_NAME} raylib)
# Web Configurations
if ("${PLATFORM}" STREQUAL "Web")
# Tell Emscripten to build an example.html file.
set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html")
target_link_options(${PROJECT_NAME} PUBLIC -sUSE_GLFW=3 PUBLIC --preload-file resources)
endif()

55
build_pi.sh Executable file
View file

@ -0,0 +1,55 @@
#!/bin/bash
if [ "$(uname -m)" == "aarch64" ]; then
apk add -U cmake make g++ mesa-dev
mkdir -p /build/build_pi
cd /build/build_pi
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DPLATFORM=DRM \
-DCUSTOMIZE_BUILD=ON \
-DSUPPORT_CAMERA_SYSTEM=ON \
-DSUPPORT_CLIPBOARD_IMAGE=OFF \
-DSUPPORT_COMPRESSION_API=OFF \
-DSUPPORT_DEFAULT_FONT=ON \
-DSUPPORT_FILEFORMAT_DDS=OFF \
-DSUPPORT_FILEFORMAT_FNT=OFF \
-DSUPPORT_FILEFORMAT_GIF=OFF \
-DSUPPORT_FILEFORMAT_GLTF=OFF \
-DSUPPORT_FILEFORMAT_HDR=OFF \
-DSUPPORT_FILEFORMAT_IQM=OFF \
-DSUPPORT_FILEFORMAT_M3D=OFF \
-DSUPPORT_FILEFORMAT_MOD=OFF \
-DSUPPORT_FILEFORMAT_MP3=OFF \
-DSUPPORT_FILEFORMAT_MTL=OFF \
-DSUPPORT_FILEFORMAT_OBJ=OFF \
-DSUPPORT_FILEFORMAT_OGG=OFF \
-DSUPPORT_FILEFORMAT_PNG=ON \
-DSUPPORT_FILEFORMAT_QOA=OFF \
-DSUPPORT_FILEFORMAT_QOI=ON \
-DSUPPORT_FILEFORMAT_TTF=ON \
-DSUPPORT_FILEFORMAT_VOX=OFF \
-DSUPPORT_FILEFORMAT_WAV=OFF \
-DSUPPORT_FILEFORMAT_XM=OFF \
-DSUPPORT_GESTURES_SYSTEM=OFF \
-DSUPPORT_GIF_RECORDING=OFF \
-DSUPPORT_IMAGE_EXPORT=OFF \
-DSUPPORT_IMAGE_GENERATION=OFF \
-DSUPPORT_IMAGE_MANIPULATION=OFF \
-DSUPPORT_MESH_GENERATION=ON \
-DSUPPORT_MODULE_RAUDIO=OFF \
-DSUPPORT_MODULE_RMODELS=ON \
-DSUPPORT_MODULE_RSHAPES=ON \
-DSUPPORT_MODULE_RTEXT=ON \
-DSUPPORT_MODULE_RTEXTURES=ON \
-DSUPPORT_MOUSE_GESTURES=OFF \
-DSUPPORT_QUADS_DRAW_MODE=ON \
-DSUPPORT_SCREEN_CAPTURE=OFF \
-DSUPPORT_SSH_KEYBOARD_RPI=OFF \
-DSUPPORT_STANDARD_FILEIO=ON \
-DSUPPORT_TEXT_MANIPULATION=ON \
-DSUPPORT_WINMM_HIGHRES_TIMER=ON
make -j12
else
podman run --rm --arch=aarch64 --pull always -v `pwd`:/build ghcr.io/linuxcontainers/alpine /bin/sh /build/build_pi.sh
fi

205
src/Application.cpp Normal file
View file

@ -0,0 +1,205 @@
#include "Application.hpp"
#include "Widget.hpp"
#include "widgets/DummyWidget.hpp"
#include "raylib.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <memory>
using namespace BSS;
Application::Application()
: m_shuffleInterval(30)
, m_activeWidgetCounter{}
, m_halfWidth{}
, m_halfHeight{}
, m_widgetShuffleTime{}
, m_rand(std::random_device()())
{
std::cout << "Starting up Bar Status Screen..." << std::endl;
for (auto& factory : BSS::Widget::WidgetFactories)
{
auto widget = factory();
if (!widget->IsAvailable())
{
std::cout << " " << (widget->IsVanity() ? "(Vanity) " : "") << "Widget " << widget->GetName() << " is not available, skipping." << std::endl;
continue;
}
std::cout << " Registering " << widget->GetName() << (widget->IsVanity() ? " vanity" : "") << " widget" << std::endl;
m_widgets.emplace_back(std::move(widget));
}
BSS::Widget::WidgetFactories.clear();
for (int i = 0; i < 4; ++i)
m_activeWidgets[i] = std::make_shared<Widgets::DummyWidget>();
#if PLATFORM_DRM
int screenWidth = 1280;
int screenHeight = 720;
#else
int screenWidth = GetMonitorWidth(0);
int screenHeight = GetMonitorHeight(0);
#endif
// SetTargetFPS(GetMonitorRefreshRate(0));
SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE);
InitWindow(screenWidth, screenHeight, "Bar Status Screen");
SetWindowState(FLAG_WINDOW_MAXIMIZED | FLAG_FULLSCREEN_MODE);
SetTargetFPS(30);
m_halfWidth = GetScreenWidth() / 2.f;
m_halfHeight = GetScreenHeight() / 2.f;
std::cout << " Adding Logo widget." << std::endl;
AddWidget("Logo");
}
Application::~Application()
{
}
void Application::Run()
{
auto lastFrame = GetTime();
while (!WindowShouldClose())
{
const auto cur = GetTime();
const float dt = cur - lastFrame;
lastFrame = cur;
Update(dt);
Draw();
}
CloseWindow();
}
void Application::AddWidget(const std::string& name)
{
std::cout << "Adding named widget " << name << std::endl;
auto it = std::find_if(std::begin(m_widgets), std::end(m_widgets), [&](auto& widget) { return widget->GetName() == name; });
if (it == std::end(m_widgets))
std::cout << " Unable to find widged named " << name << ", ignoring." << std::endl;
else
AddWidget(*it);
}
void Application::AddDummyWidget()
{
AddWidget(std::make_shared<Widgets::DummyWidget>());
}
void Application::Update(float dt)
{
m_halfWidth = GetScreenWidth() / 2.f;
m_halfHeight = GetScreenHeight() / 2.f;
for (auto& widget : m_widgets)
if (widget->IsActive())
widget->Update(dt);
m_widgetShuffleTime += dt;
if (m_widgetShuffleTime > m_shuffleInterval || IsKeyPressed(KEY_ENTER))
{
AddRandomWidget();
m_widgetShuffleTime = 0;
}
}
void Application::Draw()
{
BeginDrawing();
ClearBackground(BLACK);
int i = 0;
for (auto& widget : m_activeWidgets)
{
uint8_t x = i % 2;
uint8_t y = i++ / 2;
Rectangle rect {
x * m_halfWidth, y * m_halfHeight,
m_halfWidth, m_halfHeight
};
bool scissor = widget->ShouldScissor();
if (scissor)
BeginScissorMode(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
widget->Draw(rect.x, rect.y, rect.width, rect.height);
if (scissor)
EndScissorMode();
}
DrawFPS(0, 0);
EndDrawing();
}
void Application::AddRandomWidget()
{
std::shuffle(m_widgets.begin(), m_widgets.end(), m_rand);
bool allowVanity = std::uniform_real_distribution<float>()(m_rand) < 0.25;
auto it = std::end(m_widgets);
std::cout << "Adding random widget" << (allowVanity ? " (vanity allowed)" : "") << std::endl;
do
{
// Find first valid widget in list of loaded widgets
it = std::find_if(std::begin(m_widgets), std::end(m_widgets), [&](auto& potential) {
if (potential->IsVanity() && !allowVanity)
return false;
// Skip if widget is already displayed
if (std::find_if(std::begin(m_activeWidgets), std::end(m_activeWidgets), [&](auto& loaded) { return loaded == potential; }) != std::end(m_activeWidgets))
return false;
return true;
});
if (it == std::end(m_widgets) && !allowVanity)
{
std::cout << " Failed to find regular widget, retrying with vanity allowed." << std::endl;
allowVanity = true;
}
else
break;
} while(true);
if (it != std::end(m_widgets))
AddWidget(*it);
else
std::cout << " No valid widgets to add, ignoring." << std::endl;
}
void Application::AddWidget(const std::shared_ptr<Widget>& widget)
{
std::cout << "Adding " << widget->GetName() << " widget" << std::endl;
if (!widget->IsActive())
widget->Activate(m_halfWidth, m_halfHeight);
auto active = m_activeWidgets[m_activeWidgetCounter];
m_activeWidgets[m_activeWidgetCounter] = widget;
m_activeWidgetCounter = (m_activeWidgetCounter + 1) % m_activeWidgets.size();
std::cout << " Replacing " << active->GetName() << " widget" << std::endl;
if (std::find(std::begin(m_activeWidgets), std::end(m_activeWidgets), active) == std::end(m_activeWidgets))
{
std::cout << " Deactivating " << active->GetName() << " widget" << std::endl;
active->Deactivate();
}
}

43
src/Application.hpp Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include "Widget.hpp"
#include <memory>
#include <random>
#include <vector>
namespace BSS
{
class Application
{
public:
Application();
Application(const Application&) = delete;
~Application();
void SetShuffleInterval(float interval) { m_shuffleInterval = interval; }
void Run();
void AddWidget(const std::string& name);
void AddRandomWidget();
void AddDummyWidget();
private:
void Update(float dt);
void Draw();
void AddWidget(const std::shared_ptr<Widget>& widget);
std::vector<std::shared_ptr<Widget>> m_widgets;
std::array<std::shared_ptr<Widget>, 4> m_activeWidgets;
size_t m_activeWidgetCounter;
float m_shuffleInterval;
std::mt19937 m_rand;
float m_halfWidth, m_halfHeight, m_widgetShuffleTime;
};
}

4
src/CMakeLists.txt Normal file
View file

@ -0,0 +1,4 @@
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
file(GLOB_RECURSE HEADER_FILES CONFIGURE_DEPENDS *.hpp)
target_sources(${PROJECT_NAME} PRIVATE ${SOURCE_FILES} ${HEADER_FILES})

View file

@ -0,0 +1,16 @@
#pragma once
#include "raylib.h"
#include "rlgl.h"
namespace BSS::Util
{
class RLMatrix
{
public:
RLMatrix() { rlPushMatrix(); }
~RLMatrix() { rlPopMatrix(); }
};
}

89
src/Util/Text2D.hpp Normal file
View file

@ -0,0 +1,89 @@
#pragma once
#include "raylib.h"
#include <algorithm>
#include <string>
namespace BSS::Util
{
inline void DrawTextCenter(Font font, const std::string& text, Vector2 position, float fontSize, float fontSpacing, Color textColor)
{
if (text.find('\n') != std::string::npos)
{
const int splits = std::count(std::begin(text), std::end(text), '\n');
const float total = splits * fontSize;
int at = 0;
int part = 0;
do {
const int next = text.find('\n', at);
const float perc = part++ / (float)splits;
DrawTextCenter(font, text.substr(at, next), { position.x, position.y - total * 0.5f + total * perc }, fontSize, fontSpacing, textColor);
at = next;
if (at != std::string::npos)
at++;
} while (at != std::string::npos);
return;
}
auto size = MeasureTextEx(font, text.c_str(), fontSize, fontSpacing);
DrawTextEx(font, text.c_str(), { position.x - size.x / 2.f, position.y - size.y / 2.f }, fontSize, fontSpacing, textColor);
}
inline void DrawTextCenterOutline(Font font, const std::string& text, Vector2 position, float fontSize, float fontSpacing, float outline, Color textColor, Color outlineColor)
{
if (text.find('\n') != std::string::npos)
{
const int splits = std::count(std::begin(text), std::end(text), '\n');
const float total = splits * fontSize;
int at = 0;
int part = 0;
do {
const int next = text.find('\n', at);
const float perc = part++ / (float)splits;
DrawTextCenterOutline(font, text.substr(at, next), { position.x, position.y - total * 0.5f + total * perc }, fontSize, fontSpacing, outline, textColor, outlineColor);
at = next;
if (at != std::string::npos)
at++;
} while (at != std::string::npos);
return;
}
const float scale = fontSize / font.baseSize;
const char* str = text.c_str();
const auto size = MeasureTextEx(font, str, fontSize, fontSpacing);
Vector2 pos = { position.x - size.x * 0.5f, position.y - size.y * 0.5f };
for (size_t i = 0; i < text.length();)
{
int codepointByteCount = 0;
const int codepoint = GetCodepoint(str + i, &codepointByteCount);
const int glyphIdx = GetGlyphIndex(font, codepoint);
if (codepoint == 0x3f)
codepointByteCount = 1;
if (codepoint != ' ' && codepoint != '\t' && codepoint != '\n')
{
DrawTextCodepoint(font, codepoint, { pos.x - outline, pos.y - outline }, fontSize, outlineColor);
DrawTextCodepoint(font, codepoint, { pos.x - outline, pos.y + outline }, fontSize, outlineColor);
DrawTextCodepoint(font, codepoint, { pos.x + outline, pos.y - outline }, fontSize, outlineColor);
DrawTextCodepoint(font, codepoint, { pos.x + outline, pos.y + outline }, fontSize, outlineColor);
DrawTextCodepoint(font, codepoint, pos, fontSize, textColor);
}
if (font.glyphs[glyphIdx].advanceX == 0.f)
pos.x += (font.recs[glyphIdx].width + fontSpacing) * scale;
else
pos.x += (font.glyphs[glyphIdx].advanceX + fontSpacing) * scale;
i += codepointByteCount;
}
}
}

161
src/Util/Text3D.cpp Normal file
View file

@ -0,0 +1,161 @@
#include "Text3D.hpp"
#include "rlgl.h"
void BSS::Util::DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint)
{
// Character index position in sprite font
// NOTE: In case a codepoint is not available in the font, index returned points to '?'
int index = GetGlyphIndex(font, codepoint);
float scale = fontSize/(float)font.baseSize;
// Character destination rectangle on screen
// NOTE: We consider charsPadding on drawing
position.x += (float)(font.glyphs[index].offsetX - font.glyphPadding)/(float)font.baseSize*scale;
position.z += (float)(font.glyphs[index].offsetY - font.glyphPadding)/(float)font.baseSize*scale;
// Character source rectangle from font texture atlas
// NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects
Rectangle srcRec = { font.recs[index].x - (float)font.glyphPadding, font.recs[index].y - (float)font.glyphPadding,
font.recs[index].width + 2.0f*font.glyphPadding, font.recs[index].height + 2.0f*font.glyphPadding };
float width = (float)(font.recs[index].width + 2.0f*font.glyphPadding)/(float)font.baseSize*scale;
float height = (float)(font.recs[index].height + 2.0f*font.glyphPadding)/(float)font.baseSize*scale;
if (font.texture.id > 0)
{
const float x = 0.0f;
const float y = 0.0f;
const float z = 0.0f;
// normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f)
const float tx = srcRec.x/font.texture.width;
const float ty = srcRec.y/font.texture.height;
const float tw = (srcRec.x+srcRec.width)/font.texture.width;
const float th = (srcRec.y+srcRec.height)/font.texture.height;
rlCheckRenderBatchLimit(4 + 4*backface);
rlSetTexture(font.texture.id);
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
rlBegin(RL_QUADS);
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
// Front Face
rlNormal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up
rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Left Of The Texture and Quad
rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Left Of The Texture and Quad
rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Right Of The Texture and Quad
rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Right Of The Texture and Quad
if (backface)
{
// Back Face
rlNormal3f(0.0f, -1.0f, 0.0f); // Normal Pointing Down
rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Right Of The Texture and Quad
rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Left Of The Texture and Quad
rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Left Of The Texture and Quad
rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Right Of The Texture and Quad
}
rlEnd();
rlPopMatrix();
rlSetTexture(0);
}
}
void BSS::Util::DrawText3D(Font font, const std::string& text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint)
{
float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
float textOffsetX = 0.0f; // Offset X to next character to draw
float scale = fontSize/(float)font.baseSize;
for (int i = 0; i < text.size();)
{
// Get next codepoint from byte string and glyph index in font
int codepointByteCount = 0;
int codepoint = GetCodepoint(&text[i], &codepointByteCount);
int index = GetGlyphIndex(font, codepoint);
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all of the bad bytes using the '?' symbol moving one byte
if (codepoint == 0x3f) codepointByteCount = 1;
if (codepoint == '\n')
{
// NOTE: Fixed line spacing of 1.5 line-height
// TODO: Support custom line spacing defined by user
textOffsetY += scale + lineSpacing/(float)font.baseSize*scale;
textOffsetX = 0.0f;
}
else
{
if ((codepoint != ' ') && (codepoint != '\t'))
{
DrawTextCodepoint3D(font, codepoint, (Vector3){ position.x + textOffsetX, position.y, position.z + textOffsetY }, fontSize, backface, tint);
}
if (font.glyphs[index].advanceX == 0) textOffsetX += (float)(font.recs[index].width + fontSpacing)/(float)font.baseSize*scale;
else textOffsetX += (float)(font.glyphs[index].advanceX + fontSpacing)/(float)font.baseSize*scale;
}
i += codepointByteCount; // Move text bytes counter to next codepoint
}
}
Vector3 BSS::Util::MeasureText3D(Font font, const std::string& text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint)
{
int tempLen = 0; // Used to count longer text line num chars
int lenCounter = 0;
float tempTextWidth = 0.0f; // Used to count longer text line width
float scale = fontSize/(float)font.baseSize;
float textHeight = scale;
float textWidth = 0.0f;
int letter = 0; // Current character
int index = 0; // Index position in sprite font
for (int i = 0; i < text.size(); i++)
{
lenCounter++;
int next = 0;
letter = GetCodepoint(&text[i], &next);
index = GetGlyphIndex(font, letter);
// NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1
if (letter == 0x3f)
next = 1;
i += next - 1;
if (letter != '\n')
{
if (font.glyphs[index].advanceX != 0) textWidth += (font.glyphs[index].advanceX+fontSpacing)/(float)font.baseSize*scale;
else textWidth += (font.recs[index].width + font.glyphs[index].offsetX)/(float)font.baseSize*scale;
}
else
{
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
lenCounter = 0;
textWidth = 0.0f;
textHeight += scale + lineSpacing/(float)font.baseSize*scale;
}
if (tempLen < lenCounter) tempLen = lenCounter;
}
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
Vector3 vec = { 0 };
vec.x = tempTextWidth + (float)((tempLen - 1)*fontSpacing/(float)font.baseSize*scale); // Adds chars spacing to measure
vec.y = 0.25f;
vec.z = textHeight;
return vec;
}

14
src/Util/Text3D.hpp Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include "raylib.h"
#include <string>
namespace BSS::Util
{
extern void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint);
extern void DrawText3D(Font font, const std::string& text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint);
extern Vector3 MeasureText3D(Font font, const std::string& text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint);
}

54
src/Widget.hpp Normal file
View file

@ -0,0 +1,54 @@
#pragma once
#include <cstdio>
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <cstdarg>
namespace BSS
{
class Widget
{
public:
Widget() : m_activated(false) {}
virtual ~Widget() { if (IsActive()) Deactivate(); }
static std::list<std::function<std::shared_ptr<Widget>()>> WidgetFactories;
virtual std::string GetName() const = 0;
virtual void Init() {}
virtual void Deinit() {}
virtual bool IsAvailable() { return true; }
virtual void Activate(float w, float h) { m_activated = true; LogLine("Activated"); }
virtual void Deactivate() { m_activated = false; }
virtual bool IsActive() { return m_activated; }
virtual bool ShouldScissor() const { return true; }
virtual bool IsVanity() const { return false; }
virtual void Update(float dt) = 0;
virtual void Draw(float x, float y, float width, float height) = 0;
protected:
void LogLine(const char* format, ...) {
printf("[%s] ", GetName().c_str());
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
}
private:
bool m_activated;
};
#define IMPLEMENT_WIDGET(WIDGET) namespace { int dummy = (::BSS::Widget::WidgetFactories.push_back([]() { return std::make_shared<WIDGET>(); }), 0); }; \
}

49
src/main.cpp Normal file
View file

@ -0,0 +1,49 @@
#include "Application.hpp"
#include "Widget.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <csignal>
#include <memory>
std::list<std::function<std::shared_ptr<BSS::Widget>()>> BSS::Widget::WidgetFactories{};
std::unique_ptr<BSS::Application> app;
void sighup_handler(int sig)
{
static const std::filesystem::path widgetPath = "/tmp/bss-widget";
std::cout << "Received sighup, adding widget." << std::endl;
if (std::filesystem::exists(widgetPath))
{
auto file = std::ifstream(widgetPath);
std::string widgetName;
file >> widgetName;
std::filesystem::remove(widgetPath);
app->AddWidget(widgetName);
}
else
app->AddRandomWidget();
}
int main(void)
{
app = std::make_unique<BSS::Application>();
std::signal(SIGHUP, sighup_handler);
//for (int i = 0; i < 3; ++i)
// app->AddRandomWidget();
//app.AddWidget("Clock");
//app.AddWidget("Logo");
//app.AddWidget("Matrix");
//app.SetShuffleInterval(5);
app->Run();
return 0;
}

View file

View file

@ -0,0 +1,21 @@
#include "ClockWidget.hpp"
#include "../Util/Text2D.hpp"
#include "raylib.h"
#include <ctime>
using namespace BSS::Widgets;
IMPLEMENT_WIDGET(ClockWidget);
void ClockWidget::Draw(float x, float y, float width, float height)
{
static char timeString[std::size("yyyy-mm-dd hh:mm:ss")];
const std::time_t time = std::time({});
std::strftime(std::data(timeString), std::size(timeString), "%F\n%T", std::gmtime(&time));
Util::DrawTextCenterOutline(GetFontDefault(), timeString, { x + width * 0.5f, y + height * 0.5f }, height / 8.f, 1.f, height / 200.f, BLACK, GRAY);
}

View file

@ -0,0 +1,20 @@
#pragma once
#include "../Widget.hpp"
namespace BSS::Widgets
{
// A widget displaying the time
class ClockWidget : public Widget
{
public:
std::string GetName() const override { return "Clock"; }
void Update(float dt) override {}
void Draw(float x, float y, float width, float height) override;
bool IsVanity() const override { return true; }
};
}

View file

@ -0,0 +1,18 @@
#pragma once
#include "../Widget.hpp"
namespace BSS::Widgets
{
// A "This space left intentionally blank" widget
class DummyWidget : public Widget
{
public:
inline std::string GetName() const override { return "Dummy"; }
inline void Update(float dt) override {}
inline void Draw(float x, float y, float width, float height) override {}
};
}

111
src/widgets/LogoWidget.cpp Normal file
View file

@ -0,0 +1,111 @@
#include "LogoWidget.hpp"
#include "../Util/Text2D.hpp"
#include "raylib.h"
#include "rlgl.h"
#include <cmath>
using namespace BSS::Widgets;
IMPLEMENT_WIDGET(LogoWidget);
// Morris Maroon / PDP11
const Color Color_CtrlC_Blue{ 99, 15, 30, 255 };
// Brite Copenhagen Blue / PDP15
const Color Color_CtrlC_Red{ 39, 128, 196, 255 };
// Pure yellow
const Color Color_Lysator{ 255, 255, 0, 255 };
LogoWidget::LogoWidget()
{
}
void LogoWidget::Activate(float w, float h)
{
Widget::Activate(w, h);
m_font = GetFontDefault();
PreRender(w, h);
}
void LogoWidget::Deactivate()
{
Widget::Deactivate();
UnloadFont(m_font);
UnloadRenderTexture(m_texture);
m_texture.texture.width = -1;
}
void LogoWidget::Draw(float x, float y, float w, float h)
{
if (m_texture.texture.width != (int)w || m_texture.texture.height != (int)h)
{
EndScissorMode();
PreRender(w, h);
BeginScissorMode(x, y, w, h);
}
// Lysator
DrawTextureRec(m_texture.texture, { (float)m_texture.texture.width * 0.25f, 0, (float)m_texture.texture.width, (float)-m_texture.texture.height }, { x, y - h * 0.05f }, WHITE);
// Ctrl-C
{
const auto mid = Vector2{ x + w * 0.75f, y + h * 0.45f };
const float base = h / 5.f;
Util::DrawTextCenterOutline(m_font, "C", mid, base * 2, 1.f, h / 250.f, Color_CtrlC_Red, WHITE);
Util::DrawTextCenterOutline(m_font, "Ctrl", { mid.x - base * 0.25f, mid.y }, base * 0.75f, 1.f, h / 250.f, Color_CtrlC_Blue, WHITE);
}
Util::DrawTextCenterOutline(m_font, "Bar\nMonitoring", { x + w * 0.5f, y + h * 0.5f }, cos(GetTime() * 0.5f) * 5.f + h / 4.f, 1.f, h / 200.f, BLACK, GRAY);
}
void LogoWidget::PreRender(float w, float h)
{
if (m_texture.texture.width == (int)w && m_texture.texture.height == (int)h)
return;
LogLine("Rendering %fx%fpx texture of Lysator logo", w, h);
auto torusMesh = GenMeshTorus(0.15f, 0.5f, 16, 16);
auto torusModel = LoadModelFromMesh(torusMesh);
if (IsRenderTextureReady(m_texture))
UnloadRenderTexture(m_texture);
m_texture = LoadRenderTexture(w, h);
BeginTextureMode(m_texture);
ClearBackground(BLACK);
Camera camera = { 0 };
camera.position = (Vector3){ 0.0f, 0.0f, 12.0f }; // Camera position
camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target)
camera.fovy = 45.0f; // Camera field-of-view Y
camera.projection = CAMERA_PERSPECTIVE; // Camera projection type
BeginMode3D(camera);
// Lysator
{
// X
DrawCube({ 0, 0, 0 }, 8.f, 0.3f, 0.3f, Color_Lysator);
// Y
DrawCube({ 0, 0, 0 }, 0.3f, 8.f, 0.3f, Color_Lysator);
// Z
DrawCube({ 0, 0, 0 }, 0.3f, 0.3f, 8.f, Color_Lysator);
DrawModelEx( torusModel, { 0, 0, 0 }, { 0, -0.75f, 1.f }, -90.f, { 8.f, 8.f, 8.f }, Color_Lysator);
//DrawModelWiresEx(torusModel, { 0, 0, 0 }, { 0, -0.75f, 1.f }, -90.f, { 8.f, 8.f, 8.f }, BLACK);
}
EndMode3D();
EndTextureMode();
UnloadModel(torusModel);
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "../Widget.hpp"
#include "raylib.h"
namespace BSS::Widgets
{
// A widget displaying the status screen logo
class LogoWidget : public Widget
{
public:
LogoWidget();
std::string GetName() const override { return "Logo"; }
void Activate(float w, float h) override;
void Deactivate() override;
void Update(float dt) override {}
void Draw(float x, float y, float width, float height) override;
bool IsVanity() const override { return true; }
// bool ShouldScissor() const override { return false; }
private:
void PreRender(float w, float h);
RenderTexture2D m_texture;
Font m_font;
};
}

View file

@ -0,0 +1,100 @@
#include "MatrixWidget.hpp"
#include "../Util/Text2D.hpp"
#include "raylib.h"
#include <algorithm>
#include <numbers>
#include <random>
#include <string>
using namespace BSS::Widgets;
IMPLEMENT_WIDGET(MatrixWidget);
void MatrixWidget::Activate(float w, float h)
{
Widget::Activate(w, h);
m_rand = std::mt19937(std::random_device()());
float x = 0;
for (size_t i = 0; i < m_strands.size(); ++i)
{
using namespace std::numbers;
x = std::fmod(x + (float)phi, 0.95f) + 0.025f,
m_strands[i] = {
(int)m_rand(),
x,
0,
std::uniform_real_distribution<float>(0, 0.5f)(m_rand),
std::uniform_int_distribution<size_t>(1, 25)(m_rand),
std::uniform_int_distribution<size_t>(3, 15)(m_rand)
};
}
}
void MatrixWidget::Update(float dt)
{
const float step = 0.1f;
m_accum += dt;
while (m_accum > step)
{
m_accum -= step;
for (auto& strand : m_strands)
{
strand.accum += step;
if (strand.accum >= strand.speed)
{
strand.accum = 0;
strand.length++;
}
if (strand.length > 25)
{
using namespace std::numbers;
strand.seed = m_rand();
strand.x = std::fmod(strand.x + (float)phi, 0.95f) + 0.025f;
strand.speed = std::uniform_real_distribution<float>(0, 0.5f)(m_rand);
strand.length = 1;
strand.renderLen = std::uniform_int_distribution<size_t>(3, 15)(m_rand);
}
}
}
}
void MatrixWidget::Draw(float x, float y, float width, float height)
{
const static std::string chars = "ABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789";
const static Font font = GetFontDefault();
std::uniform_int_distribution<size_t> charDist(0, std::size(chars) - 1);
for (const auto& strand : m_strands)
{
std::mt19937 twister(strand.seed);
for (size_t i = 0; i < strand.length; ++i)
{
const auto chr = chars[charDist(twister)];
const size_t dist = strand.length - i;
const auto maxDist = strand.renderLen;
if (dist > maxDist)
continue;
const float visibility = ((maxDist + 1) - dist) / (float)(maxDist + 1);
auto mainCol = DARKGREEN;
auto outCol = GREEN;
mainCol.a = 255 * visibility;
outCol.a = 255 * visibility;
Util::DrawTextCenterOutline(font, std::string(&chr, 1), { x + width * strand.x, y + (height / 10.f) * i }, height / 20.f, 1.f, height / 500.f, mainCol, outCol);
}
}
}

View file

@ -0,0 +1,37 @@
#pragma once
#include "../Widget.hpp"
#include <array>
#include <random>
namespace BSS::Widgets
{
// A widget displaying Matrix-style falling letters
class MatrixWidget : public Widget
{
public:
std::string GetName() const override { return "Matrix"; }
void Activate(float w, float h) override;
void Update(float dt) override;
void Draw(float x, float y, float width, float height) override;
bool IsVanity() const override { return true; }
private:
struct TextStrand
{
int seed;
float x, accum, speed;
size_t length, renderLen;
};
float m_accum;
std::mt19937 m_rand;
std::array<TextStrand, 20> m_strands;
};
}