mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-10-04 12:47:56 +03:00
fix line endings
This commit is contained in:
@@ -1,265 +1,265 @@
|
||||
#include "notifications.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
#include "external/imgui/imgui_internal.h"
|
||||
#include "external/fmt/include/fmt/format.h"
|
||||
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/time.h"
|
||||
|
||||
namespace overlay::notifications {
|
||||
|
||||
bool ENABLED = true;
|
||||
Position POSITION = Position::BottomRight;
|
||||
|
||||
struct Notification {
|
||||
uint64_t id;
|
||||
std::string text;
|
||||
Severity severity;
|
||||
double created_ms;
|
||||
float duration_s;
|
||||
};
|
||||
|
||||
static std::mutex g_mutex;
|
||||
static std::deque<Notification> g_items;
|
||||
static std::atomic<uint64_t> g_next_id { 1 };
|
||||
static std::atomic<size_t> g_count { 0 };
|
||||
|
||||
// duration in seconds each notification stays visible
|
||||
static constexpr float DURATION_S = 3.0f;
|
||||
|
||||
// maximum number of notifications kept in the queue (oldest dropped beyond this)
|
||||
static constexpr size_t MAX_NOTIFICATIONS = 6;
|
||||
|
||||
// time (ms) over which a toast fades out at the end of its lifetime
|
||||
static constexpr float FADE_OUT_MS = 400.0f;
|
||||
|
||||
// fixed width of each toast window, in unscaled pixels
|
||||
static constexpr float TOAST_WIDTH = 320.0f;
|
||||
|
||||
// gap between the toast stack and the screen edges (right + bottom)
|
||||
static constexpr float TOAST_MARGIN = 20.0f;
|
||||
|
||||
// vertical gap between stacked toasts
|
||||
static constexpr float TOAST_SPACING = 8.0f;
|
||||
|
||||
// inner padding inside a toast window (horizontal / vertical)
|
||||
static constexpr float TOAST_PAD_X = 10.0f;
|
||||
static constexpr float TOAST_PAD_Y = 8.0f;
|
||||
|
||||
// width of the colored severity accent bar drawn on the left edge
|
||||
static constexpr float TOAST_ACCENT_W = 6.0f;
|
||||
|
||||
// base opacity of the toast background (0..1), multiplied by the fade alpha
|
||||
static constexpr float TOAST_BG_ALPHA = 0.85f;
|
||||
|
||||
static constexpr ImGuiWindowFlags TOAST_FLAGS =
|
||||
ImGuiWindowFlags_NoDecoration
|
||||
| ImGuiWindowFlags_NoInputs
|
||||
| ImGuiWindowFlags_NoNav
|
||||
| ImGuiWindowFlags_NoMove
|
||||
| ImGuiWindowFlags_NoSavedSettings
|
||||
| ImGuiWindowFlags_NoFocusOnAppearing
|
||||
| ImGuiWindowFlags_NoBringToFrontOnFocus
|
||||
| ImGuiWindowFlags_AlwaysAutoResize;
|
||||
|
||||
static ImU32 severity_accent(Severity sev) {
|
||||
switch (sev) {
|
||||
case Severity::Success: return IM_COL32(80, 200, 120, 255);
|
||||
case Severity::Warning: return IM_COL32(230, 180, 60, 255);
|
||||
case Severity::Error: return IM_COL32(220, 60, 60, 255);
|
||||
case Severity::Info:
|
||||
default: return IM_COL32(90, 160, 230, 255);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_expired(const Notification &n, double now_ms) {
|
||||
return (now_ms - n.created_ms) >= (n.duration_s * 1000.0);
|
||||
}
|
||||
|
||||
// returns 0.0 .. 1.0 fade alpha based on time remaining
|
||||
static float compute_alpha(const Notification &n, double now_ms) {
|
||||
const double remaining_ms = (n.duration_s * 1000.0) - (now_ms - n.created_ms);
|
||||
if (remaining_ms >= FADE_OUT_MS) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (remaining_ms <= 0.0) {
|
||||
return 0.0f;
|
||||
}
|
||||
return static_cast<float>(remaining_ms / FADE_OUT_MS);
|
||||
}
|
||||
|
||||
// drop expired items and copy the rest under a single lock acquisition
|
||||
static std::vector<Notification> snapshot_and_prune(double now_ms) {
|
||||
std::vector<Notification> snapshot;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
for (auto it = g_items.begin(); it != g_items.end();) {
|
||||
if (is_expired(*it, now_ms)) {
|
||||
it = g_items.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
g_count.store(g_items.size(), std::memory_order_release);
|
||||
snapshot.assign(g_items.begin(), g_items.end());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
// is the configured anchor on the right edge of the screen?
|
||||
static bool position_is_right(Position p) {
|
||||
return p == Position::BottomRight || p == Position::TopRight;
|
||||
}
|
||||
|
||||
// is the configured anchor on the bottom edge of the screen?
|
||||
static bool position_is_bottom(Position p) {
|
||||
return p == Position::BottomRight || p == Position::BottomLeft;
|
||||
}
|
||||
|
||||
// draw a single toast anchored to the configured corner; `cursor_y` is the
|
||||
// y-coordinate of the toast edge nearest the anchor (top edge for Top* anchors,
|
||||
// bottom edge for Bottom* anchors). returns its height in pixels.
|
||||
static float draw_toast(const Notification &n, float cursor_y, float alpha) {
|
||||
const float toast_width = apply_scaling(TOAST_WIDTH);
|
||||
const float margin = apply_scaling(TOAST_MARGIN);
|
||||
const ImVec2 &display = ImGui::GetIO().DisplaySize;
|
||||
const Position pos = POSITION;
|
||||
|
||||
const auto window_id = fmt::format("##spice_notif_{}", n.id);
|
||||
|
||||
// anchor x/pivot.x select the screen edge; pivot.y matches cursor_y semantics
|
||||
const float anchor_x = position_is_right(pos) ? (display.x - margin) : margin;
|
||||
const float pivot_x = position_is_right(pos) ? 1.0f : 0.0f;
|
||||
const float pivot_y = position_is_bottom(pos) ? 1.0f : 0.0f;
|
||||
ImGui::SetNextWindowPos(ImVec2(anchor_x, cursor_y),
|
||||
ImGuiCond_Always, ImVec2(pivot_x, pivot_y));
|
||||
ImGui::SetNextWindowSize(ImVec2(toast_width, 0.f), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowBgAlpha(TOAST_BG_ALPHA * alpha);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
|
||||
ImVec2(apply_scaling(TOAST_PAD_X), apply_scaling(TOAST_PAD_Y)));
|
||||
|
||||
float height = 0.f;
|
||||
if (ImGui::Begin(window_id.c_str(), nullptr, TOAST_FLAGS)) {
|
||||
// keep toasts above other overlay windows (e.g. the persistent FPS
|
||||
// window, which may be toggled on after a toast already exists), but
|
||||
// tuck them behind a blocking modal so they get dimmed/occluded by the
|
||||
// modal backdrop instead of floating on top of it.
|
||||
ImGuiWindow *toast_window = ImGui::GetCurrentWindow();
|
||||
if (ImGuiWindow *modal = ImGui::GetTopMostPopupModal()) {
|
||||
ImGui::BringWindowToDisplayBehind(toast_window, modal);
|
||||
} else {
|
||||
ImGui::BringWindowToDisplayFront(toast_window);
|
||||
}
|
||||
|
||||
const ImVec2 win_pos = ImGui::GetWindowPos();
|
||||
const ImVec2 win_size = ImGui::GetWindowSize();
|
||||
|
||||
// accent bar on the left edge of the window
|
||||
const ImU32 accent = severity_accent(n.severity);
|
||||
const ImU32 accent_faded =
|
||||
(accent & 0x00FFFFFFu) | (static_cast<ImU32>(alpha * 255.0f) << 24);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(
|
||||
win_pos,
|
||||
ImVec2(win_pos.x + apply_scaling(TOAST_ACCENT_W), win_pos.y + win_size.y),
|
||||
accent_faded);
|
||||
|
||||
// small gutter past the accent bar, then wrapped text
|
||||
ImGui::Dummy(ImVec2(apply_scaling(2.0f), 0.f));
|
||||
ImGui::SameLine();
|
||||
ImGui::PushTextWrapPos(win_pos.x + win_size.x - apply_scaling(TOAST_PAD_X));
|
||||
ImGui::TextUnformatted(n.text.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
height = ImGui::GetWindowSize().y;
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
return height;
|
||||
}
|
||||
|
||||
uint64_t add(Severity severity, std::string text) {
|
||||
if (!ENABLED || !overlay::ENABLED || overlay::OVERLAY == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Notification n {
|
||||
.id = g_next_id.fetch_add(1, std::memory_order_relaxed),
|
||||
.text = std::move(text),
|
||||
.severity = severity,
|
||||
.created_ms = get_performance_milliseconds(),
|
||||
.duration_s = DURATION_S,
|
||||
};
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
g_items.push_back(std::move(n));
|
||||
while (g_items.size() > MAX_NOTIFICATIONS) {
|
||||
g_items.pop_front();
|
||||
}
|
||||
g_count.store(g_items.size(), std::memory_order_release);
|
||||
}
|
||||
return n.id;
|
||||
}
|
||||
|
||||
uint64_t add_throttled(Severity severity, const std::string &key,
|
||||
double cooldown_seconds, std::string text) {
|
||||
if (!ENABLED || !overlay::ENABLED || overlay::OVERLAY == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
// per-key last-emit timestamps live behind their own lock so we don't
|
||||
// hold g_mutex across the map lookup.
|
||||
static std::mutex throttle_mutex;
|
||||
static std::unordered_map<std::string, double> last_emit_ms;
|
||||
const double now_ms = get_performance_milliseconds();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(throttle_mutex);
|
||||
auto it = last_emit_ms.find(key);
|
||||
if (it != last_emit_ms.end()
|
||||
&& (now_ms - it->second) < (cooldown_seconds * 1000.0)) {
|
||||
return 0;
|
||||
}
|
||||
last_emit_ms[key] = now_ms;
|
||||
}
|
||||
return add(severity, std::move(text));
|
||||
}
|
||||
|
||||
bool has_pending() {
|
||||
return g_count.load(std::memory_order_acquire) > 0;
|
||||
}
|
||||
|
||||
void draw() {
|
||||
const double now_ms = get_performance_milliseconds();
|
||||
const auto snapshot = snapshot_and_prune(now_ms);
|
||||
if (snapshot.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// stack from the anchored edge with newest toast at the anchor.
|
||||
// Bottom* anchors stack upward; Top* anchors stack downward.
|
||||
const float spacing = apply_scaling(TOAST_SPACING);
|
||||
const float margin = apply_scaling(TOAST_MARGIN);
|
||||
const bool bottom = position_is_bottom(POSITION);
|
||||
float cursor_y = bottom
|
||||
? (ImGui::GetIO().DisplaySize.y - margin)
|
||||
: margin;
|
||||
for (auto it = snapshot.rbegin(); it != snapshot.rend(); ++it) {
|
||||
const float alpha = compute_alpha(*it, now_ms);
|
||||
const float height = draw_toast(*it, cursor_y, alpha);
|
||||
cursor_y += bottom ? -(height + spacing) : (height + spacing);
|
||||
}
|
||||
}
|
||||
|
||||
void apply_game_default_position(const std::string &game_name) {
|
||||
if (game_name == "Reflec Beat") {
|
||||
POSITION = Position::TopRight;
|
||||
}
|
||||
// others keep the default (BottomRight)
|
||||
}
|
||||
}
|
||||
#include "notifications.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
#include "external/imgui/imgui_internal.h"
|
||||
#include "external/fmt/include/fmt/format.h"
|
||||
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/time.h"
|
||||
|
||||
namespace overlay::notifications {
|
||||
|
||||
bool ENABLED = true;
|
||||
Position POSITION = Position::BottomRight;
|
||||
|
||||
struct Notification {
|
||||
uint64_t id;
|
||||
std::string text;
|
||||
Severity severity;
|
||||
double created_ms;
|
||||
float duration_s;
|
||||
};
|
||||
|
||||
static std::mutex g_mutex;
|
||||
static std::deque<Notification> g_items;
|
||||
static std::atomic<uint64_t> g_next_id { 1 };
|
||||
static std::atomic<size_t> g_count { 0 };
|
||||
|
||||
// duration in seconds each notification stays visible
|
||||
static constexpr float DURATION_S = 3.0f;
|
||||
|
||||
// maximum number of notifications kept in the queue (oldest dropped beyond this)
|
||||
static constexpr size_t MAX_NOTIFICATIONS = 6;
|
||||
|
||||
// time (ms) over which a toast fades out at the end of its lifetime
|
||||
static constexpr float FADE_OUT_MS = 400.0f;
|
||||
|
||||
// fixed width of each toast window, in unscaled pixels
|
||||
static constexpr float TOAST_WIDTH = 320.0f;
|
||||
|
||||
// gap between the toast stack and the screen edges (right + bottom)
|
||||
static constexpr float TOAST_MARGIN = 20.0f;
|
||||
|
||||
// vertical gap between stacked toasts
|
||||
static constexpr float TOAST_SPACING = 8.0f;
|
||||
|
||||
// inner padding inside a toast window (horizontal / vertical)
|
||||
static constexpr float TOAST_PAD_X = 10.0f;
|
||||
static constexpr float TOAST_PAD_Y = 8.0f;
|
||||
|
||||
// width of the colored severity accent bar drawn on the left edge
|
||||
static constexpr float TOAST_ACCENT_W = 6.0f;
|
||||
|
||||
// base opacity of the toast background (0..1), multiplied by the fade alpha
|
||||
static constexpr float TOAST_BG_ALPHA = 0.85f;
|
||||
|
||||
static constexpr ImGuiWindowFlags TOAST_FLAGS =
|
||||
ImGuiWindowFlags_NoDecoration
|
||||
| ImGuiWindowFlags_NoInputs
|
||||
| ImGuiWindowFlags_NoNav
|
||||
| ImGuiWindowFlags_NoMove
|
||||
| ImGuiWindowFlags_NoSavedSettings
|
||||
| ImGuiWindowFlags_NoFocusOnAppearing
|
||||
| ImGuiWindowFlags_NoBringToFrontOnFocus
|
||||
| ImGuiWindowFlags_AlwaysAutoResize;
|
||||
|
||||
static ImU32 severity_accent(Severity sev) {
|
||||
switch (sev) {
|
||||
case Severity::Success: return IM_COL32(80, 200, 120, 255);
|
||||
case Severity::Warning: return IM_COL32(230, 180, 60, 255);
|
||||
case Severity::Error: return IM_COL32(220, 60, 60, 255);
|
||||
case Severity::Info:
|
||||
default: return IM_COL32(90, 160, 230, 255);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_expired(const Notification &n, double now_ms) {
|
||||
return (now_ms - n.created_ms) >= (n.duration_s * 1000.0);
|
||||
}
|
||||
|
||||
// returns 0.0 .. 1.0 fade alpha based on time remaining
|
||||
static float compute_alpha(const Notification &n, double now_ms) {
|
||||
const double remaining_ms = (n.duration_s * 1000.0) - (now_ms - n.created_ms);
|
||||
if (remaining_ms >= FADE_OUT_MS) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (remaining_ms <= 0.0) {
|
||||
return 0.0f;
|
||||
}
|
||||
return static_cast<float>(remaining_ms / FADE_OUT_MS);
|
||||
}
|
||||
|
||||
// drop expired items and copy the rest under a single lock acquisition
|
||||
static std::vector<Notification> snapshot_and_prune(double now_ms) {
|
||||
std::vector<Notification> snapshot;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
for (auto it = g_items.begin(); it != g_items.end();) {
|
||||
if (is_expired(*it, now_ms)) {
|
||||
it = g_items.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
g_count.store(g_items.size(), std::memory_order_release);
|
||||
snapshot.assign(g_items.begin(), g_items.end());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
// is the configured anchor on the right edge of the screen?
|
||||
static bool position_is_right(Position p) {
|
||||
return p == Position::BottomRight || p == Position::TopRight;
|
||||
}
|
||||
|
||||
// is the configured anchor on the bottom edge of the screen?
|
||||
static bool position_is_bottom(Position p) {
|
||||
return p == Position::BottomRight || p == Position::BottomLeft;
|
||||
}
|
||||
|
||||
// draw a single toast anchored to the configured corner; `cursor_y` is the
|
||||
// y-coordinate of the toast edge nearest the anchor (top edge for Top* anchors,
|
||||
// bottom edge for Bottom* anchors). returns its height in pixels.
|
||||
static float draw_toast(const Notification &n, float cursor_y, float alpha) {
|
||||
const float toast_width = apply_scaling(TOAST_WIDTH);
|
||||
const float margin = apply_scaling(TOAST_MARGIN);
|
||||
const ImVec2 &display = ImGui::GetIO().DisplaySize;
|
||||
const Position pos = POSITION;
|
||||
|
||||
const auto window_id = fmt::format("##spice_notif_{}", n.id);
|
||||
|
||||
// anchor x/pivot.x select the screen edge; pivot.y matches cursor_y semantics
|
||||
const float anchor_x = position_is_right(pos) ? (display.x - margin) : margin;
|
||||
const float pivot_x = position_is_right(pos) ? 1.0f : 0.0f;
|
||||
const float pivot_y = position_is_bottom(pos) ? 1.0f : 0.0f;
|
||||
ImGui::SetNextWindowPos(ImVec2(anchor_x, cursor_y),
|
||||
ImGuiCond_Always, ImVec2(pivot_x, pivot_y));
|
||||
ImGui::SetNextWindowSize(ImVec2(toast_width, 0.f), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowBgAlpha(TOAST_BG_ALPHA * alpha);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
|
||||
ImVec2(apply_scaling(TOAST_PAD_X), apply_scaling(TOAST_PAD_Y)));
|
||||
|
||||
float height = 0.f;
|
||||
if (ImGui::Begin(window_id.c_str(), nullptr, TOAST_FLAGS)) {
|
||||
// keep toasts above other overlay windows (e.g. the persistent FPS
|
||||
// window, which may be toggled on after a toast already exists), but
|
||||
// tuck them behind a blocking modal so they get dimmed/occluded by the
|
||||
// modal backdrop instead of floating on top of it.
|
||||
ImGuiWindow *toast_window = ImGui::GetCurrentWindow();
|
||||
if (ImGuiWindow *modal = ImGui::GetTopMostPopupModal()) {
|
||||
ImGui::BringWindowToDisplayBehind(toast_window, modal);
|
||||
} else {
|
||||
ImGui::BringWindowToDisplayFront(toast_window);
|
||||
}
|
||||
|
||||
const ImVec2 win_pos = ImGui::GetWindowPos();
|
||||
const ImVec2 win_size = ImGui::GetWindowSize();
|
||||
|
||||
// accent bar on the left edge of the window
|
||||
const ImU32 accent = severity_accent(n.severity);
|
||||
const ImU32 accent_faded =
|
||||
(accent & 0x00FFFFFFu) | (static_cast<ImU32>(alpha * 255.0f) << 24);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(
|
||||
win_pos,
|
||||
ImVec2(win_pos.x + apply_scaling(TOAST_ACCENT_W), win_pos.y + win_size.y),
|
||||
accent_faded);
|
||||
|
||||
// small gutter past the accent bar, then wrapped text
|
||||
ImGui::Dummy(ImVec2(apply_scaling(2.0f), 0.f));
|
||||
ImGui::SameLine();
|
||||
ImGui::PushTextWrapPos(win_pos.x + win_size.x - apply_scaling(TOAST_PAD_X));
|
||||
ImGui::TextUnformatted(n.text.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
height = ImGui::GetWindowSize().y;
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
return height;
|
||||
}
|
||||
|
||||
uint64_t add(Severity severity, std::string text) {
|
||||
if (!ENABLED || !overlay::ENABLED || overlay::OVERLAY == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Notification n {
|
||||
.id = g_next_id.fetch_add(1, std::memory_order_relaxed),
|
||||
.text = std::move(text),
|
||||
.severity = severity,
|
||||
.created_ms = get_performance_milliseconds(),
|
||||
.duration_s = DURATION_S,
|
||||
};
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
g_items.push_back(std::move(n));
|
||||
while (g_items.size() > MAX_NOTIFICATIONS) {
|
||||
g_items.pop_front();
|
||||
}
|
||||
g_count.store(g_items.size(), std::memory_order_release);
|
||||
}
|
||||
return n.id;
|
||||
}
|
||||
|
||||
uint64_t add_throttled(Severity severity, const std::string &key,
|
||||
double cooldown_seconds, std::string text) {
|
||||
if (!ENABLED || !overlay::ENABLED || overlay::OVERLAY == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
// per-key last-emit timestamps live behind their own lock so we don't
|
||||
// hold g_mutex across the map lookup.
|
||||
static std::mutex throttle_mutex;
|
||||
static std::unordered_map<std::string, double> last_emit_ms;
|
||||
const double now_ms = get_performance_milliseconds();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(throttle_mutex);
|
||||
auto it = last_emit_ms.find(key);
|
||||
if (it != last_emit_ms.end()
|
||||
&& (now_ms - it->second) < (cooldown_seconds * 1000.0)) {
|
||||
return 0;
|
||||
}
|
||||
last_emit_ms[key] = now_ms;
|
||||
}
|
||||
return add(severity, std::move(text));
|
||||
}
|
||||
|
||||
bool has_pending() {
|
||||
return g_count.load(std::memory_order_acquire) > 0;
|
||||
}
|
||||
|
||||
void draw() {
|
||||
const double now_ms = get_performance_milliseconds();
|
||||
const auto snapshot = snapshot_and_prune(now_ms);
|
||||
if (snapshot.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// stack from the anchored edge with newest toast at the anchor.
|
||||
// Bottom* anchors stack upward; Top* anchors stack downward.
|
||||
const float spacing = apply_scaling(TOAST_SPACING);
|
||||
const float margin = apply_scaling(TOAST_MARGIN);
|
||||
const bool bottom = position_is_bottom(POSITION);
|
||||
float cursor_y = bottom
|
||||
? (ImGui::GetIO().DisplaySize.y - margin)
|
||||
: margin;
|
||||
for (auto it = snapshot.rbegin(); it != snapshot.rend(); ++it) {
|
||||
const float alpha = compute_alpha(*it, now_ms);
|
||||
const float height = draw_toast(*it, cursor_y, alpha);
|
||||
cursor_y += bottom ? -(height + spacing) : (height + spacing);
|
||||
}
|
||||
}
|
||||
|
||||
void apply_game_default_position(const std::string &game_name) {
|
||||
if (game_name == "Reflec Beat") {
|
||||
POSITION = Position::TopRight;
|
||||
}
|
||||
// others keep the default (BottomRight)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user