diff --git a/PRIVACY.md b/PRIVACY.md index d4d012e..22aaba0 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -95,7 +95,7 @@ The whole tool works by reading the clipboard, so this is worth being precise ab | path | what | |---|---| -| `/config.json` | your settings: league, hotkeys, panel geometry, listing status, result count, filter ranges, client and interface language, panel opacity, whether to update automatically - **and your QuickPaste entries, in full**, since they are text you typed for this tool to hold | +| `/config.json` | your settings: league, hotkeys, panel geometry, listing status, result count, filter ranges, client and interface language, panel opacity, whether the status indicator is shown, whether to update automatically - **and your QuickPaste entries, in full**, since they are text you typed for this tool to hold | | `/cookies.txt` | the cookie jar above | | `/data//` | the downloaded game-data bundle, plus a `current` pointer | | `/update/` | a downloaded release of the application, waiting for the restart that applies it. One file, consumed as it is applied; absent whenever no update is pending | diff --git a/docs/architecture.md b/docs/architecture.md index b7e1184..62a7dd5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -231,13 +231,20 @@ while a click on our own card would; and `place_overlay` logs the geometry it ch thing to read when the panel lands somewhere unexpected. The **idle status marker** replaces the old "● PPC" spike: two lines — `PoPC v` and the data -bundle's version — in outlined yellow at half opacity over the mana globe, which is where the game +bundle's version — in yellow inside a light-grey halo at half opacity over the mana globe, which is where the game itself has nothing to say. `Config::status_right`/`status_bottom` place its centre, as offsets from the game window's bottom-right corner ÷ its height (the same reasoning as the frame edges), and they are config-file-only. Horizontally that centre is the globe's; vertically it sits below it, in the globe's lower half, so that the third line — the one an available update adds — still lands on the glass instead of on the frame. `place_overlay` sizes the window to the text for that screen, so the idle -overlay is a 200×48 rectangle rather than a dialog-sized one nothing is drawn into. +overlay is a 200×48 rectangle rather than a dialog-sized one nothing is drawn into. The outline +stamped around the glyphs is light grey, not black: the globe behind it is dark whenever the mana +is spent or fully reserved, and that is exactly where a black outline vanished. Its per-stamp +alpha is not the line's — eight stamps of one string composite rather than add, so a visible halo +pixel lands at `1-(1-s)^n` for the `n≈3` a straight edge gets, and that is what the constant is +solved back through. +`Config::status_marker` (Settings → General → Appearance, on by default) turns the marker off +altogether; with it off the idle overlay is never mapped, and nothing else about a check changes. **Settings is four tabs** — General, Price check, QuickPaste, Application — between a fixed header (the title and the close disc) and a fixed footer (Save). `kTabs` in `settings_screen.cpp` pairs each name with diff --git a/src/app.cpp b/src/app.cpp index bd8b3ba..11c8fc9 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -163,17 +164,26 @@ std::string update_status_line(const update::Updater::Status& st) { } } -/// One centred line in yellow with a black outline, drawn straight into the draw list: the +/// One centred line in yellow inside a light-grey halo, drawn straight into the draw list: the /// overlay has no background of its own here, so the text is over whatever the game is showing -/// and needs to carry its own contrast. The outline is the same string stamped at eight offsets -/// around the glyphs, which is cheap at two lines and needs no shader. +/// and needs to carry its own contrast. The halo is the same string stamped at eight offsets +/// around the glyphs, which is cheap at three lines and needs no shader. Grey and not black, +/// because the globe behind it is dark whenever the mana is spent or fully reserved and a black +/// outline vanished there; grey and not white, because the text still has to hold its shape +/// against a full globe. void draw_outlined_line(const char* text, float centre_x, float y, float alpha) { ImDrawList* dl = ImGui::GetWindowDrawList(); ImFont* font = ImGui::GetFont(); const float size = ImGui::GetFontSize(); const ImVec2 extent = ImGui::CalcTextSize(text); const ImVec2 at(centre_x - extent.x * 0.5f, y); - const ImU32 outline = IM_COL32(0, 0, 0, static_cast(alpha * 255)); + // The stamps composite over one another rather than adding, so a pixel the halo covers n + // times lands at 1-(1-s)^n. Outside the glyphs — the only part of the halo ever seen — n is + // three along a straight edge, since only the offsets pointing into the glyph reach it, and + // that is what `alpha` is solved back through here. Dividing it by eight instead assumes + // additive blending and a coverage no visible halo pixel has. + const float stamp = 1.0f - std::pow(1.0f - alpha, 1.0f / 3.0f); + const ImU32 outline = IM_COL32(150, 150, 150, static_cast(stamp * 255)); for (const ImVec2 d : {ImVec2(-1, -1), ImVec2(0, -1), ImVec2(1, -1), ImVec2(-1, 0), ImVec2(1, 0), ImVec2(-1, 1), ImVec2(0, 1), ImVec2(1, 1)}) dl->AddText(font, size, ImVec2(at.x + d.x, at.y + d.y), outline, text); @@ -201,10 +211,10 @@ void draw_status_marker(App& app) { const float top = (io.DisplaySize.y - line_h * lines) * 0.5f; draw_outlined_line(version.c_str(), io.DisplaySize.x * 0.5f, top, kStatusAlpha); draw_outlined_line(data.c_str(), io.DisplaySize.x * 0.5f, top + line_h, kStatusAlpha); - // Fully opaque where the other two are half: this one is the only line here that is asking - // for something rather than reporting state. + // Drawn like the other two: what marks this line out is that it is only there at all when + // something is waiting. if (!news.empty()) - draw_outlined_line(news.c_str(), io.DisplaySize.x * 0.5f, top + line_h * 2, 1.0f); + draw_outlined_line(news.c_str(), io.DisplaySize.x * 0.5f, top + line_h * 2, kStatusAlpha); ImGui::PopFont(); ImGui::End(); } @@ -1087,7 +1097,11 @@ void App::update_overlay_placement() { } bool moved = g.x != game_x_ || g.y != game_y_ || g.w != game_w_ || g.h != game_h_; - if (game_present_ && overlay_.visible() && !moved) { + // With the marker off there is nothing to draw while idle, so the window stays unmapped + // instead of mapped over an empty rectangle. Geometry is still tracked below, so the first + // panel to open is placed against the game as it is now. + const bool want_visible = screen_ != Screen::Hidden || config_.status_marker; + if (game_present_ && overlay_.visible() == want_visible && !moved) { // Back from another application without the window having moved: still owed the // keyboard, since it was lost to whatever was in front and no window manager will hand // it to a window it does not manage. @@ -1101,9 +1115,9 @@ void App::update_overlay_placement() { game_h_ = g.h; place_overlay(); - if (!overlay_.visible()) { - overlay_.set_visible(true); - overlay_set_click_through(overlay_.window(), screen_ == Screen::Hidden); + if (overlay_.visible() != want_visible) { + overlay_.set_visible(want_visible); + if (want_visible) overlay_set_click_through(overlay_.window(), screen_ == Screen::Hidden); } if (screen_ != Screen::Hidden) { SDL_RaiseWindow(overlay_.window()); @@ -1369,6 +1383,9 @@ void App::set_screen(Screen s) { if (!active) copy_pending_ = false; // nothing left to fill in; stop watching the clipboard place_overlay(); // each screen has its own geometry; apply before showing if (active && !overlay_.visible()) overlay_.set_visible(true); + // Closing with the marker turned off leaves nothing to show, so unmap rather than sit + // mapped and empty over the game. + if (!active && !config_.status_marker && overlay_.visible()) overlay_.set_visible(false); // The window stays mapped; interactivity is what changes. Idle == click-through // so input passes to the game; active == catch input and raise to the front. overlay_set_click_through(overlay_.window(), !active); diff --git a/src/config.cpp b/src/config.cpp index 3b647e6..5477871 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -74,6 +74,7 @@ void read_into(Config& c, const json& j) { c.inventory_edge = j.value("inventory_edge", c.inventory_edge); c.status_right = j.value("status_right", c.status_right); c.status_bottom = j.value("status_bottom", c.status_bottom); + c.status_marker = j.value("status_marker", c.status_marker); c.reduce_transparency = j.value("reduce_transparency", c.reduce_transparency); c.auto_update = j.value("auto_update", c.auto_update); c.debug_log = j.value("debug_log", c.debug_log); @@ -125,6 +126,7 @@ bool Config::save() const { j["inventory_edge"] = inventory_edge; j["status_right"] = status_right; j["status_bottom"] = status_bottom; + j["status_marker"] = status_marker; j["reduce_transparency"] = reduce_transparency; j["auto_update"] = auto_update; j["debug_log"] = debug_log; diff --git a/src/config.hpp b/src/config.hpp index 097643e..b32b645 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -93,6 +93,11 @@ struct Config { float status_right = 0.110f; ///< marker centre's distance from the right edge, ÷ game height float status_bottom = 0.070f; ///< marker centre's distance from the bottom edge, ÷ game height + /// Draw that marker at all. On by default — it is the only sign the overlay is running — + /// but it is text over the game's HUD, so it is a knob. Off leaves the idle overlay + /// unmapped entirely; nothing else about a price check changes. + bool status_marker = true; + /// Draw every panel's background solid instead of letting the game through it. An /// accessibility setting: text over a moving background is the hard case this answers. /// Opacity and not a blur — see ui/theme.hpp for why a blur is not available to us. diff --git a/src/screens/settings_screen.cpp b/src/screens/settings_screen.cpp index cd21bfa..48f8589 100644 --- a/src/screens/settings_screen.cpp +++ b/src/screens/settings_screen.cpp @@ -428,6 +428,12 @@ void general_tab(App& app, Config& c) { ImGui::PushTextWrapPos(0.0f); ImGui::TextDisabled("%s", ui::text(ui::Msg::ReduceTransparencyHelp)); ImGui::PopTextWrapPos(); + + ImGui::Checkbox(row(ui::text(ui::Msg::StatusMarker)), &c.status_marker); + row_gutter(); + ImGui::PushTextWrapPos(0.0f); + ImGui::TextDisabled("%s", ui::text(ui::Msg::StatusMarkerHelp)); + ImGui::PopTextWrapPos(); } void price_check_tab(App& app, Config& c) { diff --git a/src/ui/strings.cpp b/src/ui/strings.cpp index 91fec31..2cee190 100644 --- a/src/ui/strings.cpp +++ b/src/ui/strings.cpp @@ -76,6 +76,9 @@ constexpr const char* kEnglish[]{ "Reduce transparency", "Draws the panels solid instead of letting the game show through them.", + "Show the status indicator", + "The version and data lines over the mana globe. Turning them off hides nothing else \xe2\x80\x94 " + "price checks still open as usual.", "Docks beside whichever game panel the cursor was over.", "Width", diff --git a/src/ui/strings.hpp b/src/ui/strings.hpp index 682e923..d3ef955 100644 --- a/src/ui/strings.hpp +++ b/src/ui/strings.hpp @@ -82,6 +82,8 @@ enum class Msg : uint16_t { ReduceTransparency, ReduceTransparencyHelp, + StatusMarker, + StatusMarkerHelp, PanelHelp, PanelWidth,