Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43cd93bbd7 | ||
|
|
92ba9f39fb |
+176
-272
@@ -2,27 +2,26 @@
|
|||||||
|
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "audio/audio_downloader.h"
|
#include "audio/audio_downloader.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include "proclamation_options_page.h"
|
#include "proclamation_options_page.h"
|
||||||
#include "scramble/fibonacci_scrambler.h"
|
#include "scramble/fibonacci_scrambler.h"
|
||||||
#include "utf8_utils.h"
|
#include "utf8_utils.h"
|
||||||
#include "logical_size.h"
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_ttf/SDL_ttf.h>
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdint>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using radiantwave::FibonacciLinePattern;
|
using radiantwave::FibonacciLinePattern;
|
||||||
using radiantwave::scroller::ProclamationStream;
|
using radiantwave::scroller::ProclamationStream;
|
||||||
|
|
||||||
namespace
|
namespace {
|
||||||
{
|
|
||||||
|
|
||||||
constexpr float kFadeSeconds = 5.0f;
|
constexpr float kFadeSeconds = 5.0f;
|
||||||
constexpr float kVelocityMin = -3500.0f;
|
constexpr float kVelocityMin = -3500.0f;
|
||||||
@@ -30,8 +29,7 @@ namespace
|
|||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
void ScrollerPage::onPush(AppState &state)
|
void ScrollerPage::onPush(AppState &state) {
|
||||||
{
|
|
||||||
state_ = &state;
|
state_ = &state;
|
||||||
|
|
||||||
// Save the current logical presentation (LETTERBOX at design resolution)
|
// Save the current logical presentation (LETTERBOX at design resolution)
|
||||||
@@ -42,19 +40,20 @@ void ScrollerPage::onPush(AppState &state)
|
|||||||
SDL_GetRenderLogicalPresentation(state.renderer, &savedLogicalW_,
|
SDL_GetRenderLogicalPresentation(state.renderer, &savedLogicalW_,
|
||||||
&savedLogicalH_, &savedMode_);
|
&savedLogicalH_, &savedMode_);
|
||||||
|
|
||||||
SDL_Window *window = SDL_GetRenderWindow(state.renderer);
|
// Use the renderer's actual output size in pixels, not the window size in
|
||||||
|
// points. SDL_GetWindowSize() returns screen coordinates, which can be
|
||||||
|
// smaller than the real drawable if the display has a content scale
|
||||||
|
// applied (common on some monitors/compositors) — that mismatch caused
|
||||||
|
// this page to lay out and center within a sub-region of the screen
|
||||||
|
// instead of the full physical canvas. SDL_GetRenderOutputSize() gives us
|
||||||
|
// the true pixel dimensions of whatever we're actually rendering into.
|
||||||
int windowW = 0;
|
int windowW = 0;
|
||||||
int windowH = 0;
|
int windowH = 0;
|
||||||
if (window)
|
SDL_GetRenderOutputSize(state.renderer, &windowW, &windowH);
|
||||||
{
|
if (windowW <= 0) {
|
||||||
SDL_GetWindowSize(window, &windowW, &windowH);
|
|
||||||
}
|
|
||||||
if (windowW <= 0)
|
|
||||||
{
|
|
||||||
windowW = radiantwave::kDesignWidth;
|
windowW = radiantwave::kDesignWidth;
|
||||||
}
|
}
|
||||||
if (windowH <= 0)
|
if (windowH <= 0) {
|
||||||
{
|
|
||||||
windowH = radiantwave::kDesignHeight;
|
windowH = radiantwave::kDesignHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,49 +81,44 @@ void ScrollerPage::onPush(AppState &state)
|
|||||||
// plays. A frequency file that is not on disk yet falls back to the manual
|
// plays. A frequency file that is not on disk yet falls back to the manual
|
||||||
// track rather than playing nothing.
|
// track rather than playing nothing.
|
||||||
std::string track = state.config.get().audio.current_track;
|
std::string track = state.config.get().audio.current_track;
|
||||||
if (state.config.get().audio.dynamic_audio && !streams_.empty())
|
if (state.config.get().audio.dynamic_audio && !streams_.empty()) {
|
||||||
{
|
|
||||||
const ProclamationStream &stream = streams_[streamIndex_];
|
const ProclamationStream &stream = streams_[streamIndex_];
|
||||||
if (stream.solfeggio_frequency_id != 0 && !stream.audioFilename.empty() &&
|
if (stream.solfeggio_frequency_id != 0 &&
|
||||||
std::filesystem::exists(audioFilePath(stream.audioFilename)))
|
!stream.audioFilename.empty() &&
|
||||||
{
|
std::filesystem::exists(audioFilePath(stream.audioFilename))) {
|
||||||
track = stream.audioFilename;
|
track = stream.audioFilename;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (std::filesystem::exists(audioFilePath(track)))
|
if (std::filesystem::exists(audioFilePath(track))) {
|
||||||
{
|
|
||||||
state.audio.playTrackFadeIn(track, kFadeSeconds);
|
state.audio.playTrackFadeIn(track, kFadeSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.keybinds.bind(SDLK_ESCAPE, "scroller_exit", [this]()
|
state.keybinds.bind(SDLK_ESCAPE, "scroller_exit", [this]() {
|
||||||
{
|
|
||||||
if (state_) {
|
if (state_) {
|
||||||
state_->pages.pop(*state_);
|
state_->pages.pop(*state_);
|
||||||
} });
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::onResume(AppState &state)
|
void ScrollerPage::onResume(AppState &state) {
|
||||||
{
|
|
||||||
state_ = &state;
|
state_ = &state;
|
||||||
createFont(state);
|
createFont(state);
|
||||||
createPatterns();
|
createPatterns();
|
||||||
rebuildWordTextures();
|
rebuildWordTextures();
|
||||||
|
|
||||||
state.keybinds.bind(SDLK_ESCAPE, "scroller_exit", [this]()
|
state.keybinds.bind(SDLK_ESCAPE, "scroller_exit", [this]() {
|
||||||
{
|
|
||||||
if (state_) {
|
if (state_) {
|
||||||
state_->pages.pop(*state_);
|
state_->pages.pop(*state_);
|
||||||
} });
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::onPause(AppState &state)
|
void ScrollerPage::onPause(AppState &state) {
|
||||||
{
|
|
||||||
state.keybinds.unbindAll();
|
state.keybinds.unbindAll();
|
||||||
destroyResources();
|
destroyResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::onPop(AppState &state)
|
void ScrollerPage::onPop(AppState &state) {
|
||||||
{
|
|
||||||
// Restore the original logical presentation so other pages (Home,
|
// Restore the original logical presentation so other pages (Home,
|
||||||
// Settings, etc.) render in the design-resolution LETTERBOX space.
|
// Settings, etc.) render in the design-resolution LETTERBOX space.
|
||||||
SDL_SetRenderLogicalPresentation(state.renderer, savedLogicalW_,
|
SDL_SetRenderLogicalPresentation(state.renderer, savedLogicalW_,
|
||||||
@@ -136,14 +130,10 @@ void ScrollerPage::onPop(AppState &state)
|
|||||||
TTF_Quit();
|
TTF_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::handleEvent(const SDL_Event &)
|
void ScrollerPage::handleEvent(const SDL_Event &) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScrollerPage::update(float dt)
|
void ScrollerPage::update(float dt) {
|
||||||
{
|
if (state_) {
|
||||||
if (state_)
|
|
||||||
{
|
|
||||||
state_->audio.update(dt);
|
state_->audio.update(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,69 +141,55 @@ void ScrollerPage::update(float dt)
|
|||||||
|
|
||||||
scrollOffset_ += velocity_ * dt;
|
scrollOffset_ += velocity_ * dt;
|
||||||
float move = std::floor(scrollOffset_);
|
float move = std::floor(scrollOffset_);
|
||||||
if (move != 0.0f)
|
if (move != 0.0f) {
|
||||||
{
|
|
||||||
scrollOffset_ -= move;
|
scrollOffset_ -= move;
|
||||||
for (auto &line : lines_)
|
for (auto &line : lines_) {
|
||||||
{
|
for (auto &w : line.words) {
|
||||||
for (auto &w : line.words)
|
|
||||||
{
|
|
||||||
w.y += move;
|
w.y += move;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lines_.empty())
|
if (lines_.empty()) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (velocity_ > 0.0f)
|
if (velocity_ > 0.0f) {
|
||||||
{
|
|
||||||
// Lines move downward. Recycle every line that scrolled fully off the
|
// Lines move downward. Recycle every line that scrolled fully off the
|
||||||
// bottom this frame (movement is continuous, so one frame can cover
|
// bottom this frame (movement is continuous, so one frame can cover
|
||||||
// several line heights) and add a replacement just above the top.
|
// several line heights) and add a replacement just above the top.
|
||||||
while (!lines_.empty() && lines_.back().words[0].y > screenH_)
|
while (!lines_.empty() && lines_.back().words[0].y > screenH_) {
|
||||||
{
|
|
||||||
float baseY = lines_.front().words[0].y - lineHeight_;
|
float baseY = lines_.front().words[0].y - lineHeight_;
|
||||||
lines_.pop_back();
|
lines_.pop_back();
|
||||||
if (!addLineAt(baseY))
|
if (!addLineAt(baseY)) {
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (velocity_ < 0.0f) {
|
||||||
else if (velocity_ < 0.0f)
|
|
||||||
{
|
|
||||||
// Lines move upward. Recycle every line that scrolled fully off the top
|
// Lines move upward. Recycle every line that scrolled fully off the top
|
||||||
// this frame and add a replacement just below the bottom.
|
// this frame and add a replacement just below the bottom.
|
||||||
while (!lines_.empty() &&
|
while (!lines_.empty() &&
|
||||||
lines_.front().words[0].y + lines_.front().words[0].height < 0.0f)
|
lines_.front().words[0].y + lines_.front().words[0].height <
|
||||||
{
|
0.0f) {
|
||||||
float baseY = lines_.back().words[0].y + lineHeight_;
|
float baseY = lines_.back().words[0].y + lineHeight_;
|
||||||
lines_.pop_front();
|
lines_.pop_front();
|
||||||
if (!addLineAt(baseY))
|
if (!addLineAt(baseY)) {
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::render(SDL_Renderer *renderer)
|
void ScrollerPage::render(SDL_Renderer *renderer) {
|
||||||
{
|
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
for (const auto &line : lines_)
|
for (const auto &line : lines_) {
|
||||||
{
|
if (line.words.empty()) {
|
||||||
if (line.words.empty())
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float startX = std::floor((screenW_ - line.width) / 2.0f);
|
float startX = std::floor((screenW_ - line.width) / 2.0f);
|
||||||
for (const auto &w : line.words)
|
for (const auto &w : line.words) {
|
||||||
{
|
|
||||||
SDL_SetTextureColorMod(w.texture, line.r, line.g, line.b);
|
SDL_SetTextureColorMod(w.texture, line.r, line.g, line.b);
|
||||||
SDL_FRect dst{startX + w.x, w.y, w.width, w.height};
|
SDL_FRect dst{startX + w.x, w.y, w.width, w.height};
|
||||||
SDL_RenderTexture(renderer, w.texture, nullptr, &dst);
|
SDL_RenderTexture(renderer, w.texture, nullptr, &dst);
|
||||||
@@ -221,53 +197,46 @@ void ScrollerPage::render(SDL_Renderer *renderer)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::buildStreams(AppState &state)
|
void ScrollerPage::buildStreams(AppState &state) {
|
||||||
{
|
|
||||||
streams_.clear();
|
streams_.clear();
|
||||||
fallbackPalette_.clear();
|
fallbackPalette_.clear();
|
||||||
lines_.clear();
|
lines_.clear();
|
||||||
streamIndex_ = 0;
|
streamIndex_ = 0;
|
||||||
|
|
||||||
auto proclamations = state.db.getActiveProclamations();
|
auto proclamations = state.db.getActiveProclamations();
|
||||||
if (proclamations.empty())
|
if (proclamations.empty()) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &c : state.db.getPaletteForFrequencyId(5))
|
for (const auto &c :
|
||||||
{ // 528 Hz palette
|
state.db.getPaletteForFrequencyId(5)) { // 528 Hz palette
|
||||||
fallbackPalette_.push_back({c.r, c.g, c.b, c.a});
|
fallbackPalette_.push_back({c.r, c.g, c.b, c.a});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<int, std::vector<radiantwave::scroller::StreamColor>> palettesByFreqId;
|
std::unordered_map<int, std::vector<radiantwave::scroller::StreamColor>>
|
||||||
for (const auto &rec : proclamations)
|
palettesByFreqId;
|
||||||
{
|
for (const auto &rec : proclamations) {
|
||||||
int freqId = rec.solfeggio_frequency_id;
|
int freqId = rec.solfeggio_frequency_id;
|
||||||
if (palettesByFreqId.count(freqId) != 0)
|
if (palettesByFreqId.count(freqId) != 0) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
std::vector<radiantwave::scroller::StreamColor> colors;
|
std::vector<radiantwave::scroller::StreamColor> colors;
|
||||||
for (const auto &c : state.db.getPaletteForFrequencyId(freqId))
|
for (const auto &c : state.db.getPaletteForFrequencyId(freqId)) {
|
||||||
{
|
|
||||||
colors.push_back({c.r, c.g, c.b, c.a});
|
colors.push_back({c.r, c.g, c.b, c.a});
|
||||||
}
|
}
|
||||||
palettesByFreqId[freqId] = std::move(colors);
|
palettesByFreqId[freqId] = std::move(colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<int, radiantwave::db::AudioFileRecord> audioByFreqId;
|
std::unordered_map<int, radiantwave::db::AudioFileRecord> audioByFreqId;
|
||||||
for (const auto &af : state.db.getAudioFiles())
|
for (const auto &af : state.db.getAudioFiles()) {
|
||||||
{
|
|
||||||
int freqId = af.solfeggio_frequency_id;
|
int freqId = af.solfeggio_frequency_id;
|
||||||
// Self-heal for databases seeded from a legacy manifest that left the
|
// Self-heal for databases seeded from a legacy manifest that left the
|
||||||
// frequency id at 0 for every row: recover the id from the filename.
|
// frequency id at 0 for every row: recover the id from the filename.
|
||||||
if (freqId == 0)
|
if (freqId == 0) {
|
||||||
{
|
|
||||||
freqId = radiantwave::audio::frequencyIdForHz(
|
freqId = radiantwave::audio::frequencyIdForHz(
|
||||||
radiantwave::audio::hzFromFilename(af.filename));
|
radiantwave::audio::hzFromFilename(af.filename));
|
||||||
}
|
}
|
||||||
if (freqId != 0)
|
if (freqId != 0) {
|
||||||
{
|
|
||||||
audioByFreqId[freqId] = af;
|
audioByFreqId[freqId] = af;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,10 +246,8 @@ void ScrollerPage::buildStreams(AppState &state)
|
|||||||
DEFAULT_PROPLEAMATION_ID);
|
DEFAULT_PROPLEAMATION_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::createFont(AppState &state)
|
void ScrollerPage::createFont(AppState &state) {
|
||||||
{
|
if (font_) {
|
||||||
if (font_)
|
|
||||||
{
|
|
||||||
TTF_CloseFont(font_);
|
TTF_CloseFont(font_);
|
||||||
font_ = nullptr;
|
font_ = nullptr;
|
||||||
}
|
}
|
||||||
@@ -291,46 +258,41 @@ void ScrollerPage::createFont(AppState &state)
|
|||||||
clearCharTextures();
|
clearCharTextures();
|
||||||
|
|
||||||
fontPxSize_ = state.config.get().proclamation.font_size;
|
fontPxSize_ = state.config.get().proclamation.font_size;
|
||||||
if (fontPxSize_ <= 0)
|
if (fontPxSize_ <= 0) {
|
||||||
{
|
|
||||||
fontPxSize_ = 32;
|
fontPxSize_ = 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
font_ = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", fontPxSize_);
|
font_ = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", fontPxSize_);
|
||||||
if (!font_)
|
if (!font_) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int w = 0;
|
int w = 0;
|
||||||
int h = 0;
|
int h = 0;
|
||||||
TTF_GetStringSize(font_, "Mg", 0, &w, &h);
|
TTF_GetStringSize(font_, "Mg", 0, &w, &h);
|
||||||
lineHeight_ = h > 0 ? static_cast<float>(h) : static_cast<float>(fontPxSize_);
|
lineHeight_ =
|
||||||
|
h > 0 ? static_cast<float>(h) : static_cast<float>(fontPxSize_);
|
||||||
|
|
||||||
spaceWidth_ = measureWidth(" ");
|
spaceWidth_ = measureWidth(" ");
|
||||||
if (spaceWidth_ <= 0.0f)
|
if (spaceWidth_ <= 0.0f) {
|
||||||
{
|
|
||||||
spaceWidth_ = static_cast<float>(fontPxSize_) * 0.5f;
|
spaceWidth_ = static_cast<float>(fontPxSize_) * 0.5f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::createPatterns()
|
void ScrollerPage::createPatterns() {
|
||||||
{
|
|
||||||
patterns_.clear();
|
patterns_.clear();
|
||||||
patterns_.reserve(streams_.size());
|
patterns_.reserve(streams_.size());
|
||||||
for (size_t i = 0; i < streams_.size(); ++i)
|
for (size_t i = 0; i < streams_.size(); ++i) {
|
||||||
{
|
|
||||||
int peak = computePeakWordCount(streams_[i].words);
|
int peak = computePeakWordCount(streams_[i].words);
|
||||||
patterns_.emplace_back(peak, maxLineWidth_, spaceWidth_,
|
patterns_.emplace_back(
|
||||||
[this](const std::string &word)
|
peak, maxLineWidth_, spaceWidth_,
|
||||||
{ return measureWidth(word); });
|
[this](const std::string &word) { return measureWidth(word); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScrollerPage::computePeakWordCount(const std::vector<std::string> &words) const
|
int ScrollerPage::computePeakWordCount(
|
||||||
{
|
const std::vector<std::string> &words) const {
|
||||||
if (words.empty() || screenW_ <= 0.0f)
|
if (words.empty() || screenW_ <= 0.0f) {
|
||||||
{
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,14 +300,12 @@ int ScrollerPage::computePeakWordCount(const std::vector<std::string> &words) co
|
|||||||
// spacing. The peak is the number of whole words that fit across the
|
// spacing. The peak is the number of whole words that fit across the
|
||||||
// screen, so the widest Fibonacci lines fill the width at natural spacing.
|
// screen, so the widest Fibonacci lines fill the width at natural spacing.
|
||||||
double total = 0.0;
|
double total = 0.0;
|
||||||
for (const auto &w : words)
|
for (const auto &w : words) {
|
||||||
{
|
|
||||||
total += measureWidth(w);
|
total += measureWidth(w);
|
||||||
}
|
}
|
||||||
double n = static_cast<double>(words.size());
|
double n = static_cast<double>(words.size());
|
||||||
double perWord = (total + (n - 1.0) * spaceWidth_) / n;
|
double perWord = (total + (n - 1.0) * spaceWidth_) / n;
|
||||||
if (perWord <= 0.0)
|
if (perWord <= 0.0) {
|
||||||
{
|
|
||||||
perWord = 1.0;
|
perWord = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,45 +313,36 @@ int ScrollerPage::computePeakWordCount(const std::vector<std::string> &words) co
|
|||||||
return fit < 1 ? 1 : fit;
|
return fit < 1 ? 1 : fit;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::destroyResources()
|
void ScrollerPage::destroyResources() {
|
||||||
{
|
|
||||||
clearCharTextures();
|
clearCharTextures();
|
||||||
|
|
||||||
for (auto &entry : wordTextures_)
|
for (auto &entry : wordTextures_) {
|
||||||
{
|
if (entry.second.texture) {
|
||||||
if (entry.second.texture)
|
|
||||||
{
|
|
||||||
SDL_DestroyTexture(entry.second.texture);
|
SDL_DestroyTexture(entry.second.texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wordTextures_.clear();
|
wordTextures_.clear();
|
||||||
|
|
||||||
if (font_)
|
if (font_) {
|
||||||
{
|
|
||||||
TTF_CloseFont(font_);
|
TTF_CloseFont(font_);
|
||||||
font_ = nullptr;
|
font_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::rebuildWordTextures()
|
void ScrollerPage::rebuildWordTextures() {
|
||||||
{
|
|
||||||
wordTextures_.clear();
|
wordTextures_.clear();
|
||||||
for (auto &line : lines_)
|
for (auto &line : lines_) {
|
||||||
{
|
|
||||||
float width = 0.0f;
|
float width = 0.0f;
|
||||||
for (auto &item : line.words)
|
for (auto &item : line.words) {
|
||||||
{
|
|
||||||
ensureWordTexture(item.text);
|
ensureWordTexture(item.text);
|
||||||
const CachedWord &cached = wordTextures_[item.text];
|
const CachedWord &cached = wordTextures_[item.text];
|
||||||
if (!cached.texture)
|
if (!cached.texture) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
item.texture = cached.texture;
|
item.texture = cached.texture;
|
||||||
item.width = cached.width;
|
item.width = cached.width;
|
||||||
item.height = cached.height;
|
item.height = cached.height;
|
||||||
if (width > 0.0f)
|
if (width > 0.0f) {
|
||||||
{
|
|
||||||
width += spaceWidth_;
|
width += spaceWidth_;
|
||||||
}
|
}
|
||||||
item.x = width;
|
item.x = width;
|
||||||
@@ -401,31 +352,25 @@ void ScrollerPage::rebuildWordTextures()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::prefillLines()
|
void ScrollerPage::prefillLines() {
|
||||||
{
|
if (streams_.empty() || lineHeight_ <= 0.0f) {
|
||||||
if (streams_.empty() || lineHeight_ <= 0.0f)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int n = static_cast<int>(std::ceil(screenH_ / lineHeight_)) + 2;
|
int n = static_cast<int>(std::ceil(screenH_ / lineHeight_)) + 2;
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i) {
|
||||||
{
|
|
||||||
float baseY = -lineHeight_ + static_cast<float>(i) * lineHeight_;
|
float baseY = -lineHeight_ + static_cast<float>(i) * lineHeight_;
|
||||||
addLineAt(baseY);
|
addLineAt(baseY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScrollerPage::addLineAt(float baseY)
|
bool ScrollerPage::addLineAt(float baseY) {
|
||||||
{
|
if (streams_.empty() || !font_) {
|
||||||
if (streams_.empty() || !font_)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProclamationStream &stream = streams_[streamIndex_];
|
ProclamationStream &stream = streams_[streamIndex_];
|
||||||
if (stream.words.empty())
|
if (stream.words.empty()) {
|
||||||
{
|
|
||||||
advanceStream();
|
advanceStream();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -433,7 +378,8 @@ bool ScrollerPage::addLineAt(float baseY)
|
|||||||
FibonacciLinePattern &pattern = patterns_[streamIndex_];
|
FibonacciLinePattern &pattern = patterns_[streamIndex_];
|
||||||
|
|
||||||
// Capture palette state before any stream advance.
|
// Capture palette state before any stream advance.
|
||||||
std::vector<radiantwave::scroller::StreamColor> paletteCopy = stream.palette;
|
std::vector<radiantwave::scroller::StreamColor> paletteCopy =
|
||||||
|
stream.palette;
|
||||||
int lineColorIndex = stream.colorIndex;
|
int lineColorIndex = stream.colorIndex;
|
||||||
stream.colorIndex++;
|
stream.colorIndex++;
|
||||||
|
|
||||||
@@ -443,17 +389,15 @@ bool ScrollerPage::addLineAt(float baseY)
|
|||||||
stream.wordsPlaced += placed;
|
stream.wordsPlaced += placed;
|
||||||
// First word of a fresh pass is the "start" of the proclamation; print to
|
// First word of a fresh pass is the "start" of the proclamation; print to
|
||||||
// stdout once so the console shows the current title while it plays.
|
// stdout once so the console shows the current title while it plays.
|
||||||
if (stream.wordsPlaced == placed)
|
if (stream.wordsPlaced == placed) {
|
||||||
{
|
|
||||||
std::printf("Playing %s\n", stream.name.c_str());
|
std::printf("Playing %s\n", stream.name.c_str());
|
||||||
}
|
}
|
||||||
bool advanced = false;
|
bool advanced = false;
|
||||||
while (!advanced && !stream.words.empty() && stream.wordsPlaced >= static_cast<int>(stream.words.size()))
|
while (!advanced && !stream.words.empty() &&
|
||||||
{
|
stream.wordsPlaced >= static_cast<int>(stream.words.size())) {
|
||||||
stream.wordsPlaced -= static_cast<int>(stream.words.size());
|
stream.wordsPlaced -= static_cast<int>(stream.words.size());
|
||||||
stream.completed++;
|
stream.completed++;
|
||||||
if (stream.completed >= stream.repeat)
|
if (stream.completed >= stream.repeat) {
|
||||||
{
|
|
||||||
stream.completed = 0;
|
stream.completed = 0;
|
||||||
advanceStream();
|
advanceStream();
|
||||||
advanced = true;
|
advanced = true;
|
||||||
@@ -462,12 +406,10 @@ bool ScrollerPage::addLineAt(float baseY)
|
|||||||
|
|
||||||
ScrollerLine line;
|
ScrollerLine line;
|
||||||
float width = 0.0f;
|
float width = 0.0f;
|
||||||
for (const auto &text : wordStrs)
|
for (const auto &text : wordStrs) {
|
||||||
{
|
|
||||||
ensureWordTexture(text);
|
ensureWordTexture(text);
|
||||||
const CachedWord &cached = wordTextures_[text];
|
const CachedWord &cached = wordTextures_[text];
|
||||||
if (!cached.texture)
|
if (!cached.texture) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
WordItem item;
|
WordItem item;
|
||||||
@@ -476,8 +418,7 @@ bool ScrollerPage::addLineAt(float baseY)
|
|||||||
item.width = cached.width;
|
item.width = cached.width;
|
||||||
item.height = cached.height;
|
item.height = cached.height;
|
||||||
item.y = baseY;
|
item.y = baseY;
|
||||||
if (width > 0.0f)
|
if (width > 0.0f) {
|
||||||
{
|
|
||||||
width += spaceWidth_;
|
width += spaceWidth_;
|
||||||
}
|
}
|
||||||
item.x = width;
|
item.x = width;
|
||||||
@@ -485,77 +426,63 @@ bool ScrollerPage::addLineAt(float baseY)
|
|||||||
line.words.push_back(item);
|
line.words.push_back(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.words.empty())
|
if (line.words.empty()) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
line.width = width;
|
line.width = width;
|
||||||
|
|
||||||
if (!paletteCopy.empty())
|
if (!paletteCopy.empty()) {
|
||||||
{
|
const auto &c =
|
||||||
const auto &c = paletteCopy[lineColorIndex % static_cast<int>(paletteCopy.size())];
|
paletteCopy[lineColorIndex % static_cast<int>(paletteCopy.size())];
|
||||||
line.r = c.r;
|
line.r = c.r;
|
||||||
line.g = c.g;
|
line.g = c.g;
|
||||||
line.b = c.b;
|
line.b = c.b;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lines_.empty() && baseY < lines_.front().words[0].y)
|
if (!lines_.empty() && baseY < lines_.front().words[0].y) {
|
||||||
{
|
|
||||||
lines_.push_front(std::move(line));
|
lines_.push_front(std::move(line));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
lines_.push_back(std::move(line));
|
lines_.push_back(std::move(line));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::advanceStream()
|
void ScrollerPage::advanceStream() {
|
||||||
{
|
if (streams_.empty()) {
|
||||||
if (streams_.empty())
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
streamIndex_ = (streamIndex_ + 1) % static_cast<int>(streams_.size());
|
streamIndex_ = (streamIndex_ + 1) % static_cast<int>(streams_.size());
|
||||||
// Diagnostic: report the palette the incoming stream will render so the
|
// Diagnostic: report the palette the incoming stream will render so the
|
||||||
// console shows whether colors should change when this stream starts.
|
// console shows whether colors should change when this stream starts.
|
||||||
const ProclamationStream &stream = streams_[streamIndex_];
|
const ProclamationStream &stream = streams_[streamIndex_];
|
||||||
if (!stream.palette.empty())
|
if (!stream.palette.empty()) {
|
||||||
{
|
|
||||||
const auto &c = stream.palette[0];
|
const auto &c = stream.palette[0];
|
||||||
std::printf("[Scroller] -> %s freqId=%d palette[0]=#%02X%02X%02X\n",
|
std::printf("[Scroller] -> %s freqId=%d palette[0]=#%02X%02X%02X\n",
|
||||||
stream.name.c_str(), stream.solfeggio_frequency_id,
|
stream.name.c_str(), stream.solfeggio_frequency_id, c.r,
|
||||||
c.r, c.g, c.b);
|
c.g, c.b);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
std::printf("[Scroller] -> %s freqId=%d palette=EMPTY\n",
|
std::printf("[Scroller] -> %s freqId=%d palette=EMPTY\n",
|
||||||
stream.name.c_str(), stream.solfeggio_frequency_id);
|
stream.name.c_str(), stream.solfeggio_frequency_id);
|
||||||
}
|
}
|
||||||
switchAudioForCurrentStream();
|
switchAudioForCurrentStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::switchAudioForCurrentStream()
|
void ScrollerPage::switchAudioForCurrentStream() {
|
||||||
{
|
if (prefilling_ || streams_.empty() || !state_) {
|
||||||
if (prefilling_ || streams_.empty() || !state_)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic audio only: with a fixed manual track selected the audio must
|
// Dynamic audio only: with a fixed manual track selected the audio must
|
||||||
// never switch as proclamations change.
|
// never switch as proclamations change.
|
||||||
if (!state_->config.get().audio.dynamic_audio)
|
if (!state_->config.get().audio.dynamic_audio) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProclamationStream &stream = streams_[streamIndex_];
|
const ProclamationStream &stream = streams_[streamIndex_];
|
||||||
if (stream.solfeggio_frequency_id == 0 || stream.audioFilename.empty())
|
if (stream.solfeggio_frequency_id == 0 || stream.audioFilename.empty()) {
|
||||||
{
|
|
||||||
return; // default stream: never touch audio
|
return; // default stream: never touch audio
|
||||||
}
|
}
|
||||||
if (!std::filesystem::exists(audioFilePath(stream.audioFilename)))
|
if (!std::filesystem::exists(audioFilePath(stream.audioFilename))) {
|
||||||
{
|
|
||||||
return; // keep current track; retried on the next activation
|
return; // keep current track; retried on the next activation
|
||||||
}
|
}
|
||||||
// Crossfades over kFadeSeconds; a no-op when the requested track is the
|
// Crossfades over kFadeSeconds; a no-op when the requested track is the
|
||||||
@@ -563,14 +490,11 @@ void ScrollerPage::switchAudioForCurrentStream()
|
|||||||
state_->audio.playTrackCrossfade(stream.audioFilename, kFadeSeconds);
|
state_->audio.playTrackCrossfade(stream.audioFilename, kFadeSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::updateAutoVelocity(float dt)
|
void ScrollerPage::updateAutoVelocity(float dt) {
|
||||||
{
|
switch (velocityState_) {
|
||||||
switch (velocityState_)
|
|
||||||
{
|
|
||||||
case 0: // ramping to the negative extreme
|
case 0: // ramping to the negative extreme
|
||||||
velocity_ -= velocityStep_ * dt;
|
velocity_ -= velocityStep_ * dt;
|
||||||
if (velocity_ <= kVelocityMin)
|
if (velocity_ <= kVelocityMin) {
|
||||||
{
|
|
||||||
velocity_ = kVelocityMin;
|
velocity_ = kVelocityMin;
|
||||||
velocityTimer_ = velocityDelay_;
|
velocityTimer_ = velocityDelay_;
|
||||||
velocityState_ = 1;
|
velocityState_ = 1;
|
||||||
@@ -578,15 +502,13 @@ void ScrollerPage::updateAutoVelocity(float dt)
|
|||||||
break;
|
break;
|
||||||
case 1: // holding at the negative extreme
|
case 1: // holding at the negative extreme
|
||||||
velocityTimer_ -= dt;
|
velocityTimer_ -= dt;
|
||||||
if (velocityTimer_ <= 0.0f)
|
if (velocityTimer_ <= 0.0f) {
|
||||||
{
|
|
||||||
velocityState_ = 2;
|
velocityState_ = 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2: // ramping to the positive extreme
|
case 2: // ramping to the positive extreme
|
||||||
velocity_ += velocityStep_ * dt;
|
velocity_ += velocityStep_ * dt;
|
||||||
if (velocity_ >= kVelocityMax)
|
if (velocity_ >= kVelocityMax) {
|
||||||
{
|
|
||||||
velocity_ = kVelocityMax;
|
velocity_ = kVelocityMax;
|
||||||
velocityTimer_ = velocityDelay_;
|
velocityTimer_ = velocityDelay_;
|
||||||
velocityState_ = 3;
|
velocityState_ = 3;
|
||||||
@@ -594,8 +516,7 @@ void ScrollerPage::updateAutoVelocity(float dt)
|
|||||||
break;
|
break;
|
||||||
case 3: // holding at the positive extreme
|
case 3: // holding at the positive extreme
|
||||||
velocityTimer_ -= dt;
|
velocityTimer_ -= dt;
|
||||||
if (velocityTimer_ <= 0.0f)
|
if (velocityTimer_ <= 0.0f) {
|
||||||
{
|
|
||||||
velocityState_ = 0;
|
velocityState_ = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -605,10 +526,8 @@ void ScrollerPage::updateAutoVelocity(float dt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::ensureWordTexture(const std::string &word)
|
void ScrollerPage::ensureWordTexture(const std::string &word) {
|
||||||
{
|
if (wordTextures_.count(word) != 0) {
|
||||||
if (wordTextures_.count(word) != 0)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,69 +550,64 @@ void ScrollerPage::ensureWordTexture(const std::string &word)
|
|||||||
// breaking, centering, and the Fibonacci line pattern are unchanged; only
|
// breaking, centering, and the Fibonacci line pattern are unchanged; only
|
||||||
// the glyph pixels move.
|
// the glyph pixels move.
|
||||||
CachedWord cached;
|
CachedWord cached;
|
||||||
if (font_ && state_)
|
if (font_ && state_) {
|
||||||
{
|
|
||||||
std::vector<uint32_t> codePoints;
|
std::vector<uint32_t> codePoints;
|
||||||
if (radiantwave::splitUtf8(word, codePoints) && !codePoints.empty())
|
if (radiantwave::splitUtf8(word, codePoints) && !codePoints.empty()) {
|
||||||
{
|
|
||||||
std::vector<CharEntry *> glyphs;
|
std::vector<CharEntry *> glyphs;
|
||||||
float totalWidth = 0.0f;
|
float totalWidth = 0.0f;
|
||||||
float glyphHeight = 0.0f;
|
float glyphHeight = 0.0f;
|
||||||
for (uint32_t cp : codePoints)
|
for (uint32_t cp : codePoints) {
|
||||||
{
|
|
||||||
const std::string glyphText = radiantwave::encodeUtf8(cp);
|
const std::string glyphText = radiantwave::encodeUtf8(cp);
|
||||||
if (glyphText.empty())
|
if (glyphText.empty()) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ensureCharTexture(glyphText);
|
ensureCharTexture(glyphText);
|
||||||
CharEntry &entry = charTextures_[glyphText];
|
CharEntry &entry = charTextures_[glyphText];
|
||||||
if (!entry.surface)
|
if (!entry.surface) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
glyphs.push_back(&entry);
|
glyphs.push_back(&entry);
|
||||||
totalWidth += entry.advance;
|
totalWidth += entry.advance;
|
||||||
if (entry.glyphH > glyphHeight)
|
if (entry.glyphH > glyphHeight) {
|
||||||
{
|
|
||||||
glyphHeight = entry.glyphH;
|
glyphHeight = entry.glyphH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!glyphs.empty())
|
if (!glyphs.empty()) {
|
||||||
{
|
const int wordW =
|
||||||
const int wordW = std::max(1, static_cast<int>(std::lround(totalWidth)));
|
std::max(1, static_cast<int>(std::lround(totalWidth)));
|
||||||
const int wordH = std::max(1, static_cast<int>(std::lround(glyphHeight)));
|
const int wordH =
|
||||||
SDL_Surface *wordSurface = SDL_CreateSurface(wordW, wordH, SDL_PIXELFORMAT_RGBA8888);
|
std::max(1, static_cast<int>(std::lround(glyphHeight)));
|
||||||
if (wordSurface)
|
SDL_Surface *wordSurface =
|
||||||
{
|
SDL_CreateSurface(wordW, wordH, SDL_PIXELFORMAT_RGBA8888);
|
||||||
|
if (wordSurface) {
|
||||||
// Every glyph surface carries the font's full line height
|
// Every glyph surface carries the font's full line height
|
||||||
// (probe-verified), so y = 0 keeps every crop on the shared
|
// (probe-verified), so y = 0 keeps every crop on the shared
|
||||||
// baseline and per-word textures stay line-height tall.
|
// baseline and per-word textures stay line-height tall.
|
||||||
float x = 0.0f;
|
float x = 0.0f;
|
||||||
for (const CharEntry *entry : glyphs)
|
for (const CharEntry *entry : glyphs) {
|
||||||
{
|
|
||||||
SDL_Rect srcRect{0, 0, static_cast<int>(entry->glyphW),
|
SDL_Rect srcRect{0, 0, static_cast<int>(entry->glyphW),
|
||||||
static_cast<int>(entry->glyphH)};
|
static_cast<int>(entry->glyphH)};
|
||||||
SDL_Rect dstRect{static_cast<int>(std::lround(x)), 0,
|
SDL_Rect dstRect{static_cast<int>(std::lround(x)), 0,
|
||||||
static_cast<int>(entry->glyphW),
|
static_cast<int>(entry->glyphW),
|
||||||
static_cast<int>(entry->glyphH)};
|
static_cast<int>(entry->glyphH)};
|
||||||
SDL_BlitSurface(entry->surface, &srcRect, wordSurface, &dstRect);
|
SDL_BlitSurface(entry->surface, &srcRect, wordSurface,
|
||||||
|
&dstRect);
|
||||||
x += entry->advance;
|
x += entry->advance;
|
||||||
}
|
}
|
||||||
cached.texture = SDL_CreateTextureFromSurface(state_->renderer, wordSurface);
|
cached.texture = SDL_CreateTextureFromSurface(
|
||||||
|
state_->renderer, wordSurface);
|
||||||
// The scroller steps glyphs in whole pixels, so bilinear
|
// The scroller steps glyphs in whole pixels, so bilinear
|
||||||
// filtering can only smear them: nearest-neighbor keeps
|
// filtering can only smear them: nearest-neighbor keeps
|
||||||
// every frame pixel-sharp.
|
// every frame pixel-sharp.
|
||||||
if (cached.texture)
|
if (cached.texture) {
|
||||||
{
|
SDL_SetTextureScaleMode(cached.texture,
|
||||||
SDL_SetTextureScaleMode(cached.texture, SDL_SCALEMODE_NEAREST);
|
SDL_SCALEMODE_NEAREST);
|
||||||
}
|
}
|
||||||
cached.width = static_cast<float>(wordSurface->w);
|
cached.width = static_cast<float>(wordSurface->w);
|
||||||
cached.height = static_cast<float>(wordSurface->h);
|
cached.height = static_cast<float>(wordSurface->h);
|
||||||
SDL_DestroySurface(wordSurface);
|
SDL_DestroySurface(wordSurface);
|
||||||
if (!cached.texture)
|
if (!cached.texture) {
|
||||||
{
|
|
||||||
cached.width = 0.0f;
|
cached.width = 0.0f;
|
||||||
cached.height = 0.0f;
|
cached.height = 0.0f;
|
||||||
}
|
}
|
||||||
@@ -704,10 +618,8 @@ void ScrollerPage::ensureWordTexture(const std::string &word)
|
|||||||
wordTextures_[word] = cached;
|
wordTextures_[word] = cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::ensureCharTexture(const std::string &glyph)
|
void ScrollerPage::ensureCharTexture(const std::string &glyph) {
|
||||||
{
|
if (charTextures_.count(glyph) != 0) {
|
||||||
if (charTextures_.count(glyph) != 0)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,25 +628,23 @@ void ScrollerPage::ensureCharTexture(const std::string &glyph)
|
|||||||
// font size; createFont() clears the atlas whenever the font or its size
|
// font size; createFont() clears the atlas whenever the font or its size
|
||||||
// changes, and pause/pop clear it as part of destroyResources().
|
// changes, and pause/pop clear it as part of destroyResources().
|
||||||
CharEntry entry;
|
CharEntry entry;
|
||||||
if (font_ && state_)
|
if (font_ && state_) {
|
||||||
{
|
|
||||||
SDL_Surface *surface = TTF_RenderText_Blended(font_, glyph.c_str(), 0,
|
SDL_Surface *surface = TTF_RenderText_Blended(font_, glyph.c_str(), 0,
|
||||||
{255, 255, 255, 255});
|
{255, 255, 255, 255});
|
||||||
if (surface)
|
if (surface) {
|
||||||
{
|
|
||||||
const int gW = surface->w;
|
const int gW = surface->w;
|
||||||
const int gH = surface->h;
|
const int gH = surface->h;
|
||||||
const int n = radiantwave::scramble::canvasSize(gW, gH);
|
const int n = radiantwave::scramble::canvasSize(gW, gH);
|
||||||
if (n > 0)
|
if (n > 0) {
|
||||||
{
|
|
||||||
// The scrambler needs tightly packed RGBA8888; a TTF surface's
|
// The scrambler needs tightly packed RGBA8888; a TTF surface's
|
||||||
// pitch can exceed gW * 4, so copy row by row.
|
// pitch can exceed gW * 4, so copy row by row.
|
||||||
std::vector<uint8_t> source(static_cast<size_t>(gW) * gH * 4);
|
std::vector<uint8_t> source(static_cast<size_t>(gW) * gH * 4);
|
||||||
const uint8_t *pixels = static_cast<const uint8_t *>(surface->pixels);
|
const uint8_t *pixels =
|
||||||
for (int y = 0; y < gH; ++y)
|
static_cast<const uint8_t *>(surface->pixels);
|
||||||
{
|
for (int y = 0; y < gH; ++y) {
|
||||||
std::memcpy(source.data() + static_cast<size_t>(y) * gW * 4,
|
std::memcpy(source.data() + static_cast<size_t>(y) * gW * 4,
|
||||||
pixels + static_cast<size_t>(y) * surface->pitch,
|
pixels +
|
||||||
|
static_cast<size_t>(y) * surface->pitch,
|
||||||
static_cast<size_t>(gW) * 4);
|
static_cast<size_t>(gW) * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -743,25 +653,24 @@ void ScrollerPage::ensureCharTexture(const std::string &glyph)
|
|||||||
// The fixed seed makes every device render the same glyph as
|
// The fixed seed makes every device render the same glyph as
|
||||||
// the same scrambled pattern, with no key to persist or ship.
|
// the same scrambled pattern, with no key to persist or ship.
|
||||||
radiantwave::scramble::ScrambleConfig config;
|
radiantwave::scramble::ScrambleConfig config;
|
||||||
if (radiantwave::scramble::scramble(source.data(), scrambled.data(),
|
if (radiantwave::scramble::scramble(
|
||||||
gW, gH, config))
|
source.data(), scrambled.data(), gW, gH, config)) {
|
||||||
{
|
SDL_Surface *tile =
|
||||||
SDL_Surface *tile = SDL_CreateSurface(n, n, SDL_PIXELFORMAT_RGBA8888);
|
SDL_CreateSurface(n, n, SDL_PIXELFORMAT_RGBA8888);
|
||||||
if (tile)
|
if (tile) {
|
||||||
{
|
|
||||||
uint8_t *dst = static_cast<uint8_t *>(tile->pixels);
|
uint8_t *dst = static_cast<uint8_t *>(tile->pixels);
|
||||||
for (int y = 0; y < n; ++y)
|
for (int y = 0; y < n; ++y) {
|
||||||
{
|
std::memcpy(dst + static_cast<size_t>(y) *
|
||||||
std::memcpy(dst + static_cast<size_t>(y) * tile->pitch,
|
tile->pitch,
|
||||||
scrambled.data() + static_cast<size_t>(y) * n * 4,
|
scrambled.data() +
|
||||||
|
static_cast<size_t>(y) * n * 4,
|
||||||
static_cast<size_t>(n) * 4);
|
static_cast<size_t>(n) * 4);
|
||||||
}
|
}
|
||||||
entry.surface = tile;
|
entry.surface = tile;
|
||||||
entry.glyphW = static_cast<float>(gW);
|
entry.glyphW = static_cast<float>(gW);
|
||||||
entry.glyphH = static_cast<float>(gH);
|
entry.glyphH = static_cast<float>(gH);
|
||||||
entry.advance = measureWidth(glyph);
|
entry.advance = measureWidth(glyph);
|
||||||
if (entry.advance <= 0.0f)
|
if (entry.advance <= 0.0f) {
|
||||||
{
|
|
||||||
entry.advance = entry.glyphW;
|
entry.advance = entry.glyphW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -773,22 +682,17 @@ void ScrollerPage::ensureCharTexture(const std::string &glyph)
|
|||||||
charTextures_[glyph] = entry;
|
charTextures_[glyph] = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrollerPage::clearCharTextures()
|
void ScrollerPage::clearCharTextures() {
|
||||||
{
|
for (auto &entry : charTextures_) {
|
||||||
for (auto &entry : charTextures_)
|
if (entry.second.surface) {
|
||||||
{
|
|
||||||
if (entry.second.surface)
|
|
||||||
{
|
|
||||||
SDL_DestroySurface(entry.second.surface);
|
SDL_DestroySurface(entry.second.surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
charTextures_.clear();
|
charTextures_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
float ScrollerPage::measureWidth(const std::string &text) const
|
float ScrollerPage::measureWidth(const std::string &text) const {
|
||||||
{
|
if (!font_) {
|
||||||
if (!font_)
|
|
||||||
{
|
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
int w = 0;
|
int w = 0;
|
||||||
@@ -797,9 +701,9 @@ float ScrollerPage::measureWidth(const std::string &text) const
|
|||||||
return static_cast<float>(w);
|
return static_cast<float>(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScrollerPage::audioFilePath(const std::string &filename) const
|
std::string ScrollerPage::audioFilePath(const std::string &filename) const {
|
||||||
{
|
std::string path1 =
|
||||||
std::string path1 = std::string(std::getenv("HOME") ? std::getenv("HOME") : "/tmp") +
|
std::string(std::getenv("HOME") ? std::getenv("HOME") : "/tmp") +
|
||||||
"/.local/radiantwave/audio/" + filename;
|
"/.local/radiantwave/audio/" + filename;
|
||||||
std::string path2 = "assets/audio/" + filename;
|
std::string path2 = "assets/audio/" + filename;
|
||||||
return std::filesystem::exists(path1) ? path1 : path2;
|
return std::filesystem::exists(path1) ? path1 : path2;
|
||||||
|
|||||||
Reference in New Issue
Block a user