diff --git a/docs/overlay.md b/docs/overlay.md index ef99a2f..484e668 100644 --- a/docs/overlay.md +++ b/docs/overlay.md @@ -76,6 +76,12 @@ not every frame. Settings toggles 12/24-hour time and cycles date Off → MM/DD/YYYY → DD/MM/YYYY → YYYY-MM-DD → Off. These only affect display; mount choices remain in Settings. +Settings → **About** replaces the settings area with static information: the +build version (plus git commit when built from an untagged or modified tree), +source URL, MIT license, the selected model's name and license from its manifest, +the bundled font's license, and a pointer to `docs/third-party.md`. It performs +no checks and no network access; Settings returns. Recording controls stay live. + The complete transcript preview is paginated by glyph width and six-line height; Previous and Next navigate it without changing the source transcript. Status fits on the single status line; the old bottom detail label is gone. diff --git a/src/overlay.cpp b/src/overlay.cpp index 4c710a2..2b79784 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -189,6 +189,8 @@ struct Overlay::Impl { surface.set_layout_locked(config.lock_layout); surface.set_clock_24h(config.clock_24h); surface.set_date_format(config.date_format); + surface.set_version(std::string(FRAMEYAP_VERSION) + + (std::string(FRAMEYAP_GIT_INFO).empty() ? "" : " " FRAMEYAP_GIT_INFO)); overlay_check(overlay->SetOverlayFlag(handle, vr::VROverlayFlags_VisibleInDashboard, true), overlay, "VisibleInDashboard"); vr::HmdVector2_t mouse_scale{{float(W), float(H)}}; overlay_check(overlay->SetOverlayMouseScale(handle, &mouse_scale), overlay, "SetOverlayMouseScale"); diff --git a/src/panel_surface.cpp b/src/panel_surface.cpp index 9fa4b56..f6d78ae 100644 --- a/src/panel_surface.cpp +++ b/src/panel_surface.cpp @@ -31,10 +31,10 @@ struct Rect { enum class Control { Review, Settings, Bindings, Prev, Next, Record, Cancel, Insert, Enter, Quit, World, Left, Right, Head, Recenter, LasersAnytime, AdvancedDebug, AutoInsert, Clock24h, Date, LockLayout, CloseMicWhenIdle, Models, ModelRow, - ModelInstall, ModelPrev, ModelNext }; -enum class Tab { Review, Settings, Models }; + ModelInstall, ModelPrev, ModelNext, About }; +enum class Tab { Review, Settings, Models, About }; struct Button { Rect r; Control id; const char* label; }; -constexpr std::array buttons{{ +constexpr std::array buttons{{ {{32, 138, 180, 46}, Control::Review, "Review"}, {{226, 138, 180, 46}, Control::Settings, "Settings"}, {{420, 138, 180, 46}, Control::Bindings, "Bindings"}, @@ -67,6 +67,7 @@ constexpr std::array buttons{{ {{514, 530, 454, 36}, Control::ModelInstall, "Install"}, {{32, 530, 214, 36}, Control::ModelPrev, "Previous"}, {{260, 530, 214, 36}, Control::ModelNext, "Next"}, + {{346, 378, 154, 44}, Control::About, "About"}, }}; std::optional action(Control c) { switch (c) { @@ -126,7 +127,7 @@ struct PanelSurface::Impl { DateFormat date_format = DateFormat::MonthDayYear; std::time_t clock_time = std::time(nullptr); ClockLabel displayed_clock; - std::string placement_note, binding_note; + std::string placement_note, binding_note, version; StatusIndicators indicators; std::array pressed{{-1, -1}}; std::array press_time{}; @@ -354,7 +355,7 @@ struct PanelSurface::Impl { } bool visible(Control c) const { if (panel.quick_open && c != Control::Cancel && c != Control::Enter && c != Control::Quit) return false; - if (c == Control::Models) return tab == Tab::Settings; + if (c == Control::Models || c == Control::About) return tab == Tab::Settings; if (c == Control::ModelRow) return tab == Tab::Models && !install_confirm; if (c == Control::ModelPrev || c == Control::ModelNext) return tab == Tab::Models; if (c == Control::ModelInstall) return tab == Tab::Models; @@ -395,6 +396,27 @@ struct PanelSurface::Impl { pressed.fill(-1); drag_cursor = -1; } + // Static facts only: nothing here is a live check of the model or device. + void about() { + rounded({32, 204, 936, 334}, 16, mix(card, muted, .08f), mix(card, cyan, .25f), .18f); + text("FrameYap", 48, 244, 30, ink, 952); + text("Version " + (version.empty() ? std::string("unknown") : version), 48, 276, 22, cyan, 952); + text("On-device voice typing for Steam Frame. Nothing is sent to the cloud.", 48, 306, 19, muted, 952); + const auto it = std::find_if(panel.models.begin(), panel.models.end(), [&](const auto& m) { return m.id == panel.selected_backend; }); + const std::array, 5> rows{{ + {"Source", "github.com/baketnk/frame-yap"}, + {"License", "MIT (FrameYap contributors)"}, + {"Model", it == panel.models.end() ? "none selected" : + it->license.empty() ? it->name : it->name + " (" + it->license + ")"}, + {"Font", "Inconsolata (SIL OFL 1.1)"}, + {"Notices", "docs/third-party.md in the source"}}}; + for (size_t i = 0; i < rows.size(); ++i) { + const int y = 352 + int(i) * 36; + text(rows[i].first, 48, y, 22, cyan, 190); + text(rows[i].second, 200, y, 22, ink, 952); + } + text("Press Settings to go back.", 48, 524, 17, muted, 952); + } bool render(const Panel& p, Clock::time_point now, bool animate) { if (!animation_start) animation_start = now; if (gradient.enabled && animate && now - last_animation_frame >= std::chrono::milliseconds(100)) @@ -502,6 +524,8 @@ struct PanelSurface::Impl { text("Select Install to review full source, size and license before download.", 32, 225, 18, cyan, 968); } } else text("No model selected.", 32, 225, 18, pink, 968); + } else if (tab == Tab::About) { + about(); } else { text("Hold Quit: hold 0.9s then release. Lasers anytime: system-wide; may affect games.", 32, 204, 18, pink, 968); @@ -647,6 +671,7 @@ SurfaceEvent PanelSurface::pointer_up(unsigned cursor, float x, float y, Clock:: impl_->tab = Tab::Models; impl_->model_page = 0; impl_->install_confirm = false; impl_->consent_snapshot.reset(); impl_->reset(); impl_->dirty = true; } + else if (c == Control::About) { impl_->tab = Tab::About; impl_->reset(); impl_->dirty = true; } else if (c == Control::ModelRow) { const auto& model = impl_->panel.models[impl_->model_page * 6 + index - 23]; result.model_action = ModelAction{model.id, false, {}}; @@ -700,6 +725,9 @@ void PanelSurface::set_layout_locked(bool locked) { impl_->layout_locked = locked; impl_->reset(); impl_->dirty = true; } } +void PanelSurface::set_version(std::string version) { + if (impl_->version != version) { impl_->version = std::move(version); impl_->dirty = true; } +} void PanelSurface::set_placement_note(std::string note) { if (impl_->placement_note != note) { impl_->placement_note = std::move(note); impl_->dirty = true; } } diff --git a/src/panel_surface.hpp b/src/panel_surface.hpp index e481253..57f9f6d 100644 --- a/src/panel_surface.hpp +++ b/src/panel_surface.hpp @@ -59,6 +59,8 @@ public: void set_layout_locked(bool locked); void reset_pointers(); void set_placement_note(std::string note); + // Shown on Settings -> About; empty shows "version unknown". + void set_version(std::string version); void set_lasers_anytime(bool enabled); void set_advanced_debug(bool enabled); void set_auto_insert(bool enabled); diff --git a/tests/panel_test.cpp b/tests/panel_test.cpp index 81ba6e7..e8f12cf 100644 --- a/tests/panel_test.cpp +++ b/tests/panel_test.cpp @@ -518,5 +518,24 @@ int main(int argc, char** argv) { model_panel.model_busy = true; chooser.render(model_panel); no_action(chooser.pointer_up(0, 700, 546)); // change invalidates approval + // About: reached only from Settings; display-only, recording stays usable. + PanelSurface about(argv[1], Mount::World, {}, {.enabled = false}); + Panel about_panel{"Ready", "", "", true, false}; + about.render(about_panel); + no_action(click(about, 420, 400)); // hidden on Review + assert(!about.render(about_panel)); + no_action(click(about, 290, 160)); about.render(about_panel); // Settings + const auto settings_pixels = about.pixels(); + no_action(click(about, 420, 400)); // About + assert(about.render(about_panel)); + const auto about_pixels = about.pixels(); + assert(about_pixels != settings_pixels); + no_action(click(about, 100, 250)); // Settings' World-space button is not live under About + no_action(click(about, 700, 400)); // nor Lasers anytime + about.set_version("0.1.202609261200"); + assert(about.render(about_panel) && about.pixels() != about_pixels); + assert(click(about, 100, 610).action == UiAction::BeginRecord); + no_action(click(about, 290, 160)); about.render(about_panel); // Settings returns + assert(about.pixels() == settings_pixels); std::cout << "panel checks passed (no OpenVR, microphone or input injection)\n"; }