Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
400c6a782b | ||
|
|
9b0bd517c6 | ||
|
|
babb74c7ad | ||
|
|
47785cd4b9 |
@@ -41,10 +41,17 @@ jobs:
|
|||||||
# checks is keyed per-system (checks.<system>.default); nix build's
|
# checks is keyed per-system (checks.<system>.default); nix build's
|
||||||
# #attr shorthand can't resolve it, so resolve the current system inline.
|
# #attr shorthand can't resolve it, so resolve the current system inline.
|
||||||
# --impure lets the flake read APP_VERSION via builtins.getEnv
|
# --impure lets the flake read APP_VERSION via builtins.getEnv
|
||||||
# (pure evals always get the package defaults). The offline test suite
|
# (pure evals always get the package defaults).
|
||||||
# runs in the package's checkPhase.
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
nix build -L ".#packages.$(nix eval --impure --raw --expr 'builtins.currentSystem').default" \
|
||||||
|
--impure --out-link result-pkg
|
||||||
|
# The offline test suite runs in the check derivation's checkPhase.
|
||||||
|
# This is a separate build/output from the package itself.
|
||||||
- name: Build + offline test suite
|
- name: Build + offline test suite
|
||||||
run: nix build -L ".#checks.$(nix eval --impure --raw --expr 'builtins.currentSystem').default" --impure
|
run: |
|
||||||
|
nix build -L ".#checks.$(nix eval --impure --raw --expr 'builtins.currentSystem').default" \
|
||||||
|
--impure --out-link result-checks
|
||||||
|
|
||||||
- name: Live portal tests
|
- name: Live portal tests
|
||||||
env:
|
env:
|
||||||
@@ -59,10 +66,11 @@ jobs:
|
|||||||
./build/test_license_service
|
./build/test_license_service
|
||||||
'
|
'
|
||||||
|
|
||||||
# Release closure lands in the release cache.
|
# Release closure (the actual package, not the check output) lands in the
|
||||||
|
# release cache.
|
||||||
- name: Push to Attic release cache
|
- name: Push to Attic release cache
|
||||||
env:
|
env:
|
||||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN_PROD }}
|
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN_PROD }}
|
||||||
run: |
|
run: |
|
||||||
attic login radiantwave https://attic.radiantwavetech.com "$ATTIC_TOKEN"
|
attic login radiantwave https://attic.radiantwavetech.com "$ATTIC_TOKEN"
|
||||||
attic push radiantwave:radiantwave ./result
|
attic push radiantwave:radiantwave ./result-pkg
|
||||||
|
|||||||
+2
-2
@@ -281,10 +281,10 @@ void Home::render(SDL_Renderer *renderer)
|
|||||||
fourSpaces.destroy();
|
fourSpaces.destroy();
|
||||||
|
|
||||||
const float margin = 20.0f;
|
const float margin = 20.0f;
|
||||||
float rectRight = fw - margin;
|
float rectRight = fw - margin - 50.0f;
|
||||||
float rectLeft = rectRight - (maxNameW + spaceW4 + maxValueW);
|
float rectLeft = rectRight - (maxNameW + spaceW4 + maxValueW);
|
||||||
|
|
||||||
float lineY = fh - margin;
|
float lineY = fh - margin - 50.0f;
|
||||||
const float lineGap = 6.0f;
|
const float lineGap = 6.0f;
|
||||||
for (size_t i = footerNames_.size(); i-- > 0;)
|
for (size_t i = footerNames_.size(); i-- > 0;)
|
||||||
{
|
{
|
||||||
|
|||||||
+10
-5
@@ -7,6 +7,7 @@
|
|||||||
#include "logical_size.h"
|
#include "logical_size.h"
|
||||||
#include "logger/logger.h"
|
#include "logger/logger.h"
|
||||||
#include "provisioning.h"
|
#include "provisioning.h"
|
||||||
|
#include "scroller_page.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// How long audio fades out before the app tears down on quit. Only applied
|
// How long audio fades out before the app tears down on quit. Only applied
|
||||||
@@ -201,11 +202,15 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
|
|||||||
std::to_string(event->window.data1) + "x" +
|
std::to_string(event->window.data1) + "x" +
|
||||||
std::to_string(event->window.data2));
|
std::to_string(event->window.data2));
|
||||||
// Logical presentation persists across resizes; re-apply defensively
|
// Logical presentation persists across resizes; re-apply defensively
|
||||||
// in case backend state was reset underneath us.
|
// in case backend state was reset underneath us. Skip while the
|
||||||
SDL_SetRenderLogicalPresentation(state.renderer,
|
// scroller is active — it owns its own presentation (NONE, full
|
||||||
radiantwave::kDesignWidth,
|
// window) and restores LETTERBOX in onPop.
|
||||||
radiantwave::kDesignHeight,
|
if (!dynamic_cast<ScrollerPage *>(state.pages.current())) {
|
||||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
SDL_SetRenderLogicalPresentation(state.renderer,
|
||||||
|
radiantwave::kDesignWidth,
|
||||||
|
radiantwave::kDesignHeight,
|
||||||
|
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.keybinds.handleEvent(*event)) {
|
if (!state.keybinds.handleEvent(*event)) {
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ void ProclamationOptionsPage::rebuildItems()
|
|||||||
FlatItem::Type::SubCategory,
|
FlatItem::Type::SubCategory,
|
||||||
std::to_string(sub.id),
|
std::to_string(sub.id),
|
||||||
sub.name,
|
sub.name,
|
||||||
1, false, false, false,
|
1, true, false, false,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const auto& p : proclamations_) {
|
for (const auto& p : proclamations_) {
|
||||||
@@ -268,7 +268,7 @@ void ProclamationOptionsPage::rebuildVisible()
|
|||||||
parentVisible = true; // the always-on top-level proclamation
|
parentVisible = true; // the always-on top-level proclamation
|
||||||
} else if (currentRoot >= 0 && items_[currentRoot].expanded) {
|
} else if (currentRoot >= 0 && items_[currentRoot].expanded) {
|
||||||
parentVisible = (item.depth == 1) ||
|
parentVisible = (item.depth == 1) ||
|
||||||
(item.depth == 2 && currentSub >= 0 && items_[currentSub].expanded);
|
(item.depth == 2 && currentSub >= 0);
|
||||||
}
|
}
|
||||||
if (parentVisible) visible_.push_back(i);
|
if (parentVisible) visible_.push_back(i);
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ void ProclamationOptionsPage::toggleItem()
|
|||||||
if (visible_.empty()) return;
|
if (visible_.empty()) return;
|
||||||
FlatItem& item = items_[visible_[highlightIndex_]];
|
FlatItem& item = items_[visible_[highlightIndex_]];
|
||||||
|
|
||||||
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) {
|
if (item.type == FlatItem::Type::RootCategory) {
|
||||||
item.expanded = !item.expanded;
|
item.expanded = !item.expanded;
|
||||||
rebuildVisible();
|
rebuildVisible();
|
||||||
return;
|
return;
|
||||||
@@ -388,7 +388,7 @@ void ProclamationOptionsPage::collapseCurrent()
|
|||||||
{
|
{
|
||||||
if (visible_.empty()) return;
|
if (visible_.empty()) return;
|
||||||
FlatItem& item = items_[visible_[highlightIndex_]];
|
FlatItem& item = items_[visible_[highlightIndex_]];
|
||||||
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) {
|
if (item.type == FlatItem::Type::RootCategory) {
|
||||||
if (item.expanded) {
|
if (item.expanded) {
|
||||||
item.expanded = false;
|
item.expanded = false;
|
||||||
rebuildVisible();
|
rebuildVisible();
|
||||||
@@ -400,7 +400,7 @@ void ProclamationOptionsPage::expandCurrent()
|
|||||||
{
|
{
|
||||||
if (visible_.empty()) return;
|
if (visible_.empty()) return;
|
||||||
FlatItem& item = items_[visible_[highlightIndex_]];
|
FlatItem& item = items_[visible_[highlightIndex_]];
|
||||||
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) {
|
if (item.type == FlatItem::Type::RootCategory) {
|
||||||
if (!item.expanded) {
|
if (!item.expanded) {
|
||||||
item.expanded = true;
|
item.expanded = true;
|
||||||
rebuildVisible();
|
rebuildVisible();
|
||||||
@@ -526,8 +526,7 @@ void ProclamationOptionsPage::handleEvent(const SDL_Event& event)
|
|||||||
if (searchActive_) return;
|
if (searchActive_) return;
|
||||||
if (!visible_.empty()) {
|
if (!visible_.empty()) {
|
||||||
const FlatItem& item = items_[visible_[highlightIndex_]];
|
const FlatItem& item = items_[visible_[highlightIndex_]];
|
||||||
if ((item.type == FlatItem::Type::RootCategory ||
|
if (item.type == FlatItem::Type::RootCategory && item.expanded) {
|
||||||
item.type == FlatItem::Type::SubCategory) && item.expanded) {
|
|
||||||
collapseCurrent();
|
collapseCurrent();
|
||||||
} else {
|
} else {
|
||||||
moveToParent();
|
moveToParent();
|
||||||
@@ -655,14 +654,18 @@ void ProclamationOptionsPage::render(SDL_Renderer* renderer)
|
|||||||
SDL_RenderTexture(renderer, emptyHintText_.texture, nullptr, &dst);
|
SDL_RenderTexture(renderer, emptyHintText_.texture, nullptr, &dst);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compute the widest row so the block can be centered.
|
// Stable width: compute from every result so the list doesn't shift.
|
||||||
float maxTotalWidth = 0.0f;
|
float maxTotalWidth = 0.0f;
|
||||||
|
for (const auto& r : searchResults_) {
|
||||||
|
int w = 0, h = 0;
|
||||||
|
TTF_GetStringSize(fontItem_, proclamations_[r].title.c_str(), 0, &w, &h);
|
||||||
|
maxTotalWidth = std::max(maxTotalWidth, static_cast<float>(w));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<float> textWidths(static_cast<size_t>(listSize));
|
std::vector<float> textWidths(static_cast<size_t>(listSize));
|
||||||
for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < listSize; ++i) {
|
for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < listSize; ++i) {
|
||||||
const std::string& label = proclamations_[searchResults_[i]].title;
|
|
||||||
int w = 0, h = 0;
|
int w = 0, h = 0;
|
||||||
TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h);
|
TTF_GetStringSize(fontItem_, proclamations_[searchResults_[i]].title.c_str(), 0, &w, &h);
|
||||||
maxTotalWidth = std::max(maxTotalWidth, static_cast<float>(w));
|
|
||||||
textWidths[i] = static_cast<float>(w);
|
textWidths[i] = static_cast<float>(w);
|
||||||
}
|
}
|
||||||
float sharedX = (fw - maxTotalWidth) / 2.0f;
|
float sharedX = (fw - maxTotalWidth) / 2.0f;
|
||||||
@@ -695,19 +698,29 @@ void ProclamationOptionsPage::render(SDL_Renderer* renderer)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// --- Browse tree ---
|
// --- Browse tree ---
|
||||||
// Compute the widest row so the block can be centered.
|
// Stable width: compute from every item so the list doesn't
|
||||||
|
// shift left/right when groups expand or collapse.
|
||||||
float maxTotalWidth = 0.0f;
|
float maxTotalWidth = 0.0f;
|
||||||
std::vector<float> textWidths(visible_.size());
|
for (const auto& item : items_) {
|
||||||
for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < static_cast<int>(visible_.size()); ++i) {
|
|
||||||
const FlatItem& item = items_[visible_[i]];
|
|
||||||
std::string label = item.label;
|
std::string label = item.label;
|
||||||
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) {
|
if (item.type == FlatItem::Type::RootCategory) {
|
||||||
label = (item.expanded ? "[-] " : "[+] ") + label;
|
label = (item.expanded ? "[-] " : "[+] ") + label;
|
||||||
}
|
}
|
||||||
int w = 0, h = 0;
|
int w = 0, h = 0;
|
||||||
TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h);
|
TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h);
|
||||||
float total = item.depth * indent + static_cast<float>(w);
|
float total = item.depth * indent + static_cast<float>(w);
|
||||||
maxTotalWidth = std::max(maxTotalWidth, total);
|
maxTotalWidth = std::max(maxTotalWidth, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> textWidths(visible_.size());
|
||||||
|
for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < static_cast<int>(visible_.size()); ++i) {
|
||||||
|
const FlatItem& item = items_[visible_[i]];
|
||||||
|
std::string label = item.label;
|
||||||
|
if (item.type == FlatItem::Type::RootCategory) {
|
||||||
|
label = (item.expanded ? "[-] " : "[+] ") + label;
|
||||||
|
}
|
||||||
|
int w = 0, h = 0;
|
||||||
|
TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h);
|
||||||
textWidths[i] = static_cast<float>(w);
|
textWidths[i] = static_cast<float>(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,7 +734,7 @@ void ProclamationOptionsPage::render(SDL_Renderer* renderer)
|
|||||||
float y = listTop + rowIndex * rowPitch;
|
float y = listTop + rowIndex * rowPitch;
|
||||||
|
|
||||||
std::string label = item.label;
|
std::string label = item.label;
|
||||||
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) {
|
if (item.type == FlatItem::Type::RootCategory) {
|
||||||
label = (item.expanded ? "[-] " : "[+] ") + label;
|
label = (item.expanded ? "[-] " : "[+] ") + label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-7
@@ -34,13 +34,37 @@ void ScrollerPage::onPush(AppState &state)
|
|||||||
{
|
{
|
||||||
state_ = &state;
|
state_ = &state;
|
||||||
|
|
||||||
int winW = 0;
|
// Save the current logical presentation (LETTERBOX at design resolution)
|
||||||
int winH = 0;
|
// and switch to NONE so we render into the full physical window. This
|
||||||
radiantwave::getLayoutSize(state.renderer, &winW, &winH);
|
// lets the scroller scroll the entire vertical extent of tall/rotated
|
||||||
screenW_ = static_cast<float>(winW);
|
// displays while keeping text at the same pixel scale. onPop restores
|
||||||
screenH_ = static_cast<float>(winH);
|
// the original presentation for other pages.
|
||||||
// Lines are capped at the full screen width; the widest Fibonacci lines are
|
SDL_GetRenderLogicalPresentation(state.renderer, &savedLogicalW_,
|
||||||
// sized to fill it with whole words (peak word count derived from the
|
&savedLogicalH_, &savedMode_);
|
||||||
|
|
||||||
|
SDL_Window *window = SDL_GetRenderWindow(state.renderer);
|
||||||
|
int windowW = 0;
|
||||||
|
int windowH = 0;
|
||||||
|
if (window)
|
||||||
|
{
|
||||||
|
SDL_GetWindowSize(window, &windowW, &windowH);
|
||||||
|
}
|
||||||
|
if (windowW <= 0)
|
||||||
|
{
|
||||||
|
windowW = radiantwave::kDesignWidth;
|
||||||
|
}
|
||||||
|
if (windowH <= 0)
|
||||||
|
{
|
||||||
|
windowH = radiantwave::kDesignHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetRenderLogicalPresentation(state.renderer, windowW, windowH,
|
||||||
|
SDL_LOGICAL_PRESENTATION_DISABLED);
|
||||||
|
|
||||||
|
screenW_ = static_cast<float>(windowW);
|
||||||
|
screenH_ = static_cast<float>(windowH);
|
||||||
|
// Lines are capped at the full screen width; the widest Fibonacci lines
|
||||||
|
// are sized to fill it with whole words (peak word count derived from the
|
||||||
// screen), independent of resolution.
|
// screen), independent of resolution.
|
||||||
maxLineWidth_ = screenW_;
|
maxLineWidth_ = screenW_;
|
||||||
|
|
||||||
@@ -101,6 +125,11 @@ void ScrollerPage::onPause(AppState &state)
|
|||||||
|
|
||||||
void ScrollerPage::onPop(AppState &state)
|
void ScrollerPage::onPop(AppState &state)
|
||||||
{
|
{
|
||||||
|
// Restore the original logical presentation so other pages (Home,
|
||||||
|
// Settings, etc.) render in the design-resolution LETTERBOX space.
|
||||||
|
SDL_SetRenderLogicalPresentation(state.renderer, savedLogicalW_,
|
||||||
|
savedLogicalH_, savedMode_);
|
||||||
|
|
||||||
state.audio.stopTrackFadeOut(kFadeSeconds);
|
state.audio.stopTrackFadeOut(kFadeSeconds);
|
||||||
state.keybinds.unbindAll();
|
state.keybinds.unbindAll();
|
||||||
destroyResources();
|
destroyResources();
|
||||||
|
|||||||
@@ -115,4 +115,8 @@ private:
|
|||||||
float velocityDelay_ = 2.5f;
|
float velocityDelay_ = 2.5f;
|
||||||
int velocityState_ = 0;
|
int velocityState_ = 0;
|
||||||
float velocityTimer_ = 0.0f;
|
float velocityTimer_ = 0.0f;
|
||||||
|
|
||||||
|
int savedLogicalW_ = 0;
|
||||||
|
int savedLogicalH_ = 0;
|
||||||
|
SDL_RendererLogicalPresentation savedMode_ = SDL_LOGICAL_PRESENTATION_DISABLED;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user