HiDPI support -- fractional scaling is a bit broken, sometimes crashes, is a bit blurry
This commit is contained in:
@@ -22,6 +22,10 @@ EM_JS(int, js_isFullscreen, (), {
|
||||
return document.fullscreenElement ? 1 : 0;
|
||||
});
|
||||
|
||||
EM_JS(float, js_devicePixelRatio, (), {
|
||||
return window.devicePixelRatio || 1.0;
|
||||
});
|
||||
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
#endif
|
||||
@@ -34,6 +38,34 @@ namespace baudmine {
|
||||
|
||||
Application::Application() = default;
|
||||
|
||||
void Application::applyUIScale(float scale) {
|
||||
scale = std::clamp(scale, 0.5f, 4.0f);
|
||||
if (std::abs(scale - appliedScale_) < 0.01f) return;
|
||||
appliedScale_ = scale;
|
||||
|
||||
// Snapshot the 1x base style once.
|
||||
static ImGuiStyle baseStyle = [] {
|
||||
ImGuiStyle s;
|
||||
ImGui::StyleColorsDark(&s);
|
||||
s.WindowRounding = 4.0f;
|
||||
s.FrameRounding = 2.0f;
|
||||
s.GrabRounding = 2.0f;
|
||||
return s;
|
||||
}();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.Fonts->Clear();
|
||||
ImFontConfig fc;
|
||||
fc.SizePixels = 13.0f * scale;
|
||||
io.Fonts->AddFontDefault(&fc);
|
||||
io.Fonts->Build();
|
||||
ImGui_ImplOpenGL3_DestroyFontsTexture();
|
||||
|
||||
// Restore base style, then scale from 1x.
|
||||
ImGui::GetStyle() = baseStyle;
|
||||
ImGui::GetStyle().ScaleAllSizes(scale);
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
shutdown();
|
||||
}
|
||||
@@ -112,6 +144,20 @@ bool Application::init(int argc, char** argv) {
|
||||
// Load saved config (overwrites defaults for FFT size, overlap, window, etc.)
|
||||
loadConfig();
|
||||
|
||||
// Apply DPI-aware UI scaling
|
||||
{
|
||||
float dpiScale = 1.0f;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
dpiScale = js_devicePixelRatio();
|
||||
#else
|
||||
float ddpi = 0;
|
||||
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
|
||||
dpiScale = ddpi / 96.0f;
|
||||
#endif
|
||||
float scale = (uiScale_ > 0.0f) ? uiScale_ : dpiScale;
|
||||
applyUIScale(scale);
|
||||
}
|
||||
|
||||
// Apply loaded settings
|
||||
settings_.fftSize = kFFTSizes[fftSizeIdx_];
|
||||
settings_.overlap = overlapPct_ / 100.0f;
|
||||
@@ -370,6 +416,34 @@ void Application::render() {
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("UI scale")) {
|
||||
static constexpr int kScales[] = {100, 150, 175, 200, 225, 250, 300};
|
||||
int curPct = static_cast<int>(appliedScale_ * 100.0f + 0.5f);
|
||||
if (ImGui::MenuItem("Auto", nullptr, uiScale_ == 0.0f)) {
|
||||
uiScale_ = 0.0f;
|
||||
float dpiScale = 1.0f;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
dpiScale = js_devicePixelRatio();
|
||||
#else
|
||||
float ddpi = 0;
|
||||
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
|
||||
dpiScale = ddpi / 96.0f;
|
||||
#endif
|
||||
applyUIScale(dpiScale);
|
||||
saveConfig();
|
||||
}
|
||||
for (int s : kScales) {
|
||||
char label[16];
|
||||
std::snprintf(label, sizeof(label), "%d%%", s);
|
||||
if (ImGui::MenuItem(label, nullptr, uiScale_ > 0.0f && std::abs(curPct - s) <= 2)) {
|
||||
uiScale_ = s / 100.0f;
|
||||
applyUIScale(uiScale_);
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
@@ -409,7 +483,7 @@ void Application::render() {
|
||||
// Layout
|
||||
float totalW = ImGui::GetContentRegionAvail().x;
|
||||
float contentH = ImGui::GetContentRegionAvail().y;
|
||||
float controlW = showSidebar_ ? 270.0f : 0.0f;
|
||||
float controlW = showSidebar_ ? 270.0f * appliedScale_ : 0.0f;
|
||||
float contentW = totalW - (showSidebar_ ? controlW + 8 : 0);
|
||||
|
||||
// Control panel (sidebar)
|
||||
@@ -686,6 +760,7 @@ void Application::renderControlPanel() {
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Reset to 2x zoom");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ── Channels ──
|
||||
@@ -695,7 +770,7 @@ void Application::renderControlPanel() {
|
||||
bool isMulti = waterfallMultiCh_ && nCh > 1;
|
||||
|
||||
// Header with inline Single/Multi toggle
|
||||
float widgetW = (nCh > 1) ? 60.0f : 0.0f;
|
||||
float widgetW = (nCh > 1) ? ImGui::CalcTextSize(" Multi ").x + ImGui::GetStyle().FramePadding.x * 2 : 0.0f;
|
||||
float gap = ImGui::GetStyle().ItemSpacing.x * 0.25f;
|
||||
ImVec2 hdrMin = ImGui::GetCursorScreenPos();
|
||||
float winLeft = ImGui::GetWindowPos().x;
|
||||
@@ -1536,6 +1611,7 @@ void Application::loadConfig() {
|
||||
int fs = config_.getInt("freq_scale", static_cast<int>(freqScale_));
|
||||
freqScale_ = static_cast<FreqScale>(fs);
|
||||
vsync_ = config_.getBool("vsync", vsync_);
|
||||
uiScale_ = config_.getFloat("ui_scale", uiScale_);
|
||||
spectrumFrac_ = config_.getFloat("spectrum_frac", spectrumFrac_);
|
||||
showSidebar_ = config_.getBool("show_sidebar", showSidebar_);
|
||||
specDisplay_.peakHoldEnable = config_.getBool("peak_hold", specDisplay_.peakHoldEnable);
|
||||
@@ -1579,6 +1655,7 @@ void Application::saveConfig() const {
|
||||
cfg.setFloat("max_db", maxDB_);
|
||||
cfg.setInt("freq_scale", static_cast<int>(freqScale_));
|
||||
cfg.setBool("vsync", vsync_);
|
||||
cfg.setFloat("ui_scale", uiScale_);
|
||||
cfg.setFloat("spectrum_frac", spectrumFrac_);
|
||||
cfg.setBool("show_sidebar", showSidebar_);
|
||||
cfg.setBool("peak_hold", specDisplay_.peakHoldEnable);
|
||||
|
||||
@@ -121,6 +121,9 @@ private:
|
||||
FreqScale freqScale_ = FreqScale::Linear;
|
||||
bool paused_ = false;
|
||||
bool vsync_ = true;
|
||||
float uiScale_ = 0.0f; // 0 = auto (use DPI), >0 = manual override
|
||||
float appliedScale_ = 0.0f; // currently applied scale (0 = not yet applied)
|
||||
void applyUIScale(float scale);
|
||||
// (waterfallW_ removed — texture width tracks bin count automatically)
|
||||
// (waterfallH_ removed — fixed history depth of 1024 rows)
|
||||
|
||||
|
||||
@@ -123,7 +123,9 @@ void Cursors::draw(const SpectrumDisplay& specDisplay,
|
||||
fmtFreqDB(deltaBuf, sizeof(deltaBuf), "D", dFreq, dDB);
|
||||
|
||||
ImVec2 dSz = ImGui::CalcTextSize(deltaBuf);
|
||||
float tx = posX + sizeX - dSz.x - 168;
|
||||
// Reserve space for hover label to the right.
|
||||
float reserveW = ImGui::CalcTextSize(" 00.000 kHz 000.0 dB").x;
|
||||
float tx = posX + sizeX - dSz.x - reserveW;
|
||||
float lineH = ImGui::GetTextLineHeight();
|
||||
float ty = posY + 4;
|
||||
ImU32 col = IM_COL32(255, 200, 100, 255);
|
||||
|
||||
@@ -119,7 +119,9 @@ void Measurements::draw(const SpectrumDisplay& specDisplay,
|
||||
char pkBuf[128];
|
||||
fmtFreqDB(pkBuf, sizeof(pkBuf), "Peak", globalPeak_.freq, globalPeak_.dB);
|
||||
ImVec2 pkSz = ImGui::CalcTextSize(pkBuf);
|
||||
float pkX = posX + sizeX - pkSz.x - 8 - 350;
|
||||
// Reserve space for delta + hover labels to the right.
|
||||
float reserveW = ImGui::CalcTextSize("D: 00.000 kHz 000.0 dB 00.000 kHz 000.0 dB").x;
|
||||
float pkX = posX + sizeX - pkSz.x - 8 - reserveW;
|
||||
float pkY = posY + 4;
|
||||
ImGui::GetWindowDrawList()->AddText({pkX, pkY}, IM_COL32(180, 180, 200, 200), pkBuf);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ void SpectrumDisplay::draw(const std::vector<std::vector<float>>& spectra,
|
||||
dl->AddLine({posX, y}, {posX + sizeX, y}, gridCol);
|
||||
char label[16];
|
||||
std::snprintf(label, sizeof(label), "%.0f", db);
|
||||
dl->AddText({posX + 2, y - 12}, textCol, label);
|
||||
dl->AddText({posX + 2, y - ImGui::GetTextLineHeight()}, textCol, label);
|
||||
}
|
||||
|
||||
// ── Vertical (frequency) grid — adapt count to available width ──
|
||||
@@ -148,7 +148,7 @@ void SpectrumDisplay::draw(const std::vector<std::vector<float>>& spectra,
|
||||
std::snprintf(label, sizeof(label), "%.1fk", freq / 1e3);
|
||||
else
|
||||
std::snprintf(label, sizeof(label), "%.0f", freq);
|
||||
dl->AddText({x + 2, posY + sizeY - 14}, textCol, label);
|
||||
dl->AddText({x + 2, posY + sizeY - ImGui::GetTextLineHeight()}, textCol, label);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,12 +48,13 @@
|
||||
setStatus: function(text) {
|
||||
document.getElementById('status').textContent = text;
|
||||
},
|
||||
// Auto-resize canvas to fill the window.
|
||||
// Auto-resize canvas at full device resolution.
|
||||
preRun: [function() {
|
||||
function resize() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = Math.round(window.innerWidth * dpr);
|
||||
canvas.height = Math.round(window.innerHeight * dpr);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
Reference in New Issue
Block a user