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;
|
return document.fullscreenElement ? 1 : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
EM_JS(float, js_devicePixelRatio, (), {
|
||||||
|
return window.devicePixelRatio || 1.0;
|
||||||
|
});
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#include <GL/gl.h>
|
#include <GL/gl.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -34,6 +38,34 @@ namespace baudmine {
|
|||||||
|
|
||||||
Application::Application() = default;
|
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() {
|
Application::~Application() {
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
@@ -112,6 +144,20 @@ bool Application::init(int argc, char** argv) {
|
|||||||
// Load saved config (overwrites defaults for FFT size, overlap, window, etc.)
|
// Load saved config (overwrites defaults for FFT size, overlap, window, etc.)
|
||||||
loadConfig();
|
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
|
// Apply loaded settings
|
||||||
settings_.fftSize = kFFTSizes[fftSizeIdx_];
|
settings_.fftSize = kFFTSizes[fftSizeIdx_];
|
||||||
settings_.overlap = overlapPct_ / 100.0f;
|
settings_.overlap = overlapPct_ / 100.0f;
|
||||||
@@ -370,6 +416,34 @@ void Application::render() {
|
|||||||
saveConfig();
|
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();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +483,7 @@ void Application::render() {
|
|||||||
// Layout
|
// Layout
|
||||||
float totalW = ImGui::GetContentRegionAvail().x;
|
float totalW = ImGui::GetContentRegionAvail().x;
|
||||||
float contentH = ImGui::GetContentRegionAvail().y;
|
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);
|
float contentW = totalW - (showSidebar_ ? controlW + 8 : 0);
|
||||||
|
|
||||||
// Control panel (sidebar)
|
// Control panel (sidebar)
|
||||||
@@ -686,6 +760,7 @@ void Application::renderControlPanel() {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Reset to 2x zoom");
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Reset to 2x zoom");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Channels ──
|
// ── Channels ──
|
||||||
@@ -695,7 +770,7 @@ void Application::renderControlPanel() {
|
|||||||
bool isMulti = waterfallMultiCh_ && nCh > 1;
|
bool isMulti = waterfallMultiCh_ && nCh > 1;
|
||||||
|
|
||||||
// Header with inline Single/Multi toggle
|
// 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;
|
float gap = ImGui::GetStyle().ItemSpacing.x * 0.25f;
|
||||||
ImVec2 hdrMin = ImGui::GetCursorScreenPos();
|
ImVec2 hdrMin = ImGui::GetCursorScreenPos();
|
||||||
float winLeft = ImGui::GetWindowPos().x;
|
float winLeft = ImGui::GetWindowPos().x;
|
||||||
@@ -1536,6 +1611,7 @@ void Application::loadConfig() {
|
|||||||
int fs = config_.getInt("freq_scale", static_cast<int>(freqScale_));
|
int fs = config_.getInt("freq_scale", static_cast<int>(freqScale_));
|
||||||
freqScale_ = static_cast<FreqScale>(fs);
|
freqScale_ = static_cast<FreqScale>(fs);
|
||||||
vsync_ = config_.getBool("vsync", vsync_);
|
vsync_ = config_.getBool("vsync", vsync_);
|
||||||
|
uiScale_ = config_.getFloat("ui_scale", uiScale_);
|
||||||
spectrumFrac_ = config_.getFloat("spectrum_frac", spectrumFrac_);
|
spectrumFrac_ = config_.getFloat("spectrum_frac", spectrumFrac_);
|
||||||
showSidebar_ = config_.getBool("show_sidebar", showSidebar_);
|
showSidebar_ = config_.getBool("show_sidebar", showSidebar_);
|
||||||
specDisplay_.peakHoldEnable = config_.getBool("peak_hold", specDisplay_.peakHoldEnable);
|
specDisplay_.peakHoldEnable = config_.getBool("peak_hold", specDisplay_.peakHoldEnable);
|
||||||
@@ -1579,6 +1655,7 @@ void Application::saveConfig() const {
|
|||||||
cfg.setFloat("max_db", maxDB_);
|
cfg.setFloat("max_db", maxDB_);
|
||||||
cfg.setInt("freq_scale", static_cast<int>(freqScale_));
|
cfg.setInt("freq_scale", static_cast<int>(freqScale_));
|
||||||
cfg.setBool("vsync", vsync_);
|
cfg.setBool("vsync", vsync_);
|
||||||
|
cfg.setFloat("ui_scale", uiScale_);
|
||||||
cfg.setFloat("spectrum_frac", spectrumFrac_);
|
cfg.setFloat("spectrum_frac", spectrumFrac_);
|
||||||
cfg.setBool("show_sidebar", showSidebar_);
|
cfg.setBool("show_sidebar", showSidebar_);
|
||||||
cfg.setBool("peak_hold", specDisplay_.peakHoldEnable);
|
cfg.setBool("peak_hold", specDisplay_.peakHoldEnable);
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ private:
|
|||||||
FreqScale freqScale_ = FreqScale::Linear;
|
FreqScale freqScale_ = FreqScale::Linear;
|
||||||
bool paused_ = false;
|
bool paused_ = false;
|
||||||
bool vsync_ = true;
|
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)
|
// (waterfallW_ removed — texture width tracks bin count automatically)
|
||||||
// (waterfallH_ removed — fixed history depth of 1024 rows)
|
// (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);
|
fmtFreqDB(deltaBuf, sizeof(deltaBuf), "D", dFreq, dDB);
|
||||||
|
|
||||||
ImVec2 dSz = ImGui::CalcTextSize(deltaBuf);
|
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 lineH = ImGui::GetTextLineHeight();
|
||||||
float ty = posY + 4;
|
float ty = posY + 4;
|
||||||
ImU32 col = IM_COL32(255, 200, 100, 255);
|
ImU32 col = IM_COL32(255, 200, 100, 255);
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ void Measurements::draw(const SpectrumDisplay& specDisplay,
|
|||||||
char pkBuf[128];
|
char pkBuf[128];
|
||||||
fmtFreqDB(pkBuf, sizeof(pkBuf), "Peak", globalPeak_.freq, globalPeak_.dB);
|
fmtFreqDB(pkBuf, sizeof(pkBuf), "Peak", globalPeak_.freq, globalPeak_.dB);
|
||||||
ImVec2 pkSz = ImGui::CalcTextSize(pkBuf);
|
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;
|
float pkY = posY + 4;
|
||||||
ImGui::GetWindowDrawList()->AddText({pkX, pkY}, IM_COL32(180, 180, 200, 200), pkBuf);
|
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);
|
dl->AddLine({posX, y}, {posX + sizeX, y}, gridCol);
|
||||||
char label[16];
|
char label[16];
|
||||||
std::snprintf(label, sizeof(label), "%.0f", db);
|
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 ──
|
// ── 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);
|
std::snprintf(label, sizeof(label), "%.1fk", freq / 1e3);
|
||||||
else
|
else
|
||||||
std::snprintf(label, sizeof(label), "%.0f", freq);
|
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) {
|
setStatus: function(text) {
|
||||||
document.getElementById('status').textContent = text;
|
document.getElementById('status').textContent = text;
|
||||||
},
|
},
|
||||||
// Auto-resize canvas to fill the window.
|
// Auto-resize canvas at full device resolution.
|
||||||
preRun: [function() {
|
preRun: [function() {
|
||||||
function resize() {
|
function resize() {
|
||||||
var canvas = document.getElementById('canvas');
|
var canvas = document.getElementById('canvas');
|
||||||
canvas.width = window.innerWidth;
|
var dpr = window.devicePixelRatio || 1;
|
||||||
canvas.height = window.innerHeight;
|
canvas.width = Math.round(window.innerWidth * dpr);
|
||||||
|
canvas.height = Math.round(window.innerHeight * dpr);
|
||||||
}
|
}
|
||||||
resize();
|
resize();
|
||||||
window.addEventListener('resize', resize);
|
window.addEventListener('resize', resize);
|
||||||
|
|||||||
Reference in New Issue
Block a user