From cf23ea6be182113abc4edc210bfc7e15b29d5a80 Mon Sep 17 00:00:00 2001 From: chreden <4263940+chreden@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:27:12 +0100 Subject: [PATCH] Add toggle for route height labels in route settings Add a checkbox in the route settings to toggle height markers on route. Add a default setting for new routes in the settings window. Closes #1560 --- trview.app.tests/Routing/RouteTests.cpp | 23 +++++++++++ .../Settings/SettingsLoaderTests.cpp | 25 ++++++++++++ trview.app.ui.tests/SettingsWindowTests.cpp | 38 +++++++++++++++++++ trview.app/Mocks/Routing/IRandomizerRoute.h | 2 + trview.app/Mocks/Routing/IRoute.h | 2 + trview.app/Routing/IRoute.h | 2 + trview.app/Routing/RandomizerRoute.cpp | 10 +++++ trview.app/Routing/RandomizerRoute.h | 3 +- trview.app/Routing/Route.cpp | 12 +++++- trview.app/Routing/Route.h | 4 +- trview.app/Settings/SettingsLoader.cpp | 2 + trview.app/Settings/UserSettings.h | 1 + trview.app/UI/SettingsWindow.cpp | 2 + trview.app/UI/SettingsWindow.h | 1 + trview.app/UI/ViewerUI.cpp | 26 +++++++------ trview.app/Windows/RouteWindow.cpp | 7 +++- 16 files changed, 145 insertions(+), 15 deletions(-) diff --git a/trview.app.tests/Routing/RouteTests.cpp b/trview.app.tests/Routing/RouteTests.cpp index 162524801..4a170936a 100644 --- a/trview.app.tests/Routing/RouteTests.cpp +++ b/trview.app.tests/Routing/RouteTests.cpp @@ -531,3 +531,26 @@ TEST(Route, SetShowRouteLine) ASSERT_TRUE(raised); ASSERT_FALSE(route->show_route_line()); } + + +TEST(Route, SetShowHeightLabels) +{ + auto route = register_test_module().build(); + + ASSERT_TRUE(route->show_height_labels()); + route->set_show_height_labels(false); + ASSERT_FALSE(route->show_height_labels()); +} + +TEST(Route, DefaultHeightLines) +{ + auto route = register_test_module() + .with_settings({ .show_route_height_labels = false }) + .build(); + ASSERT_FALSE(route->show_height_labels()); + + auto route2 = register_test_module() + .with_settings({ .show_route_height_labels = true }) + .build(); + ASSERT_TRUE(route2->show_height_labels()); +} diff --git a/trview.app.tests/Settings/SettingsLoaderTests.cpp b/trview.app.tests/Settings/SettingsLoaderTests.cpp index 13213590f..ddaf41b4b 100644 --- a/trview.app.tests/Settings/SettingsLoaderTests.cpp +++ b/trview.app.tests/Settings/SettingsLoaderTests.cpp @@ -802,3 +802,28 @@ TEST(SettingsLoader, PluginsSaved) loader->save_user_settings(settings); EXPECT_THAT(output, HasSubstr("\"plugins\":{\"Default\":{\"enabled\":true}}")); } + +TEST(SettingsLoader, HeightLinesLoaded) +{ + auto loader = setup_setting("{\"show_route_height_labels\":false}"); + auto settings = loader->load_user_settings(); + ASSERT_EQ(settings.show_route_height_labels, false); + + auto loader_true = setup_setting("{\"show_route_height_labels\":true}"); + auto settings_true = loader_true->load_user_settings(); + ASSERT_EQ(settings_true.show_route_height_labels, true); +} + +TEST(SettingsLoader, HeightLinesSaved) +{ + std::string output; + auto loader = setup_save_setting(output); + UserSettings settings; + settings.show_route_height_labels = false; + loader->save_user_settings(settings); + EXPECT_THAT(output, HasSubstr("\"show_route_height_labels\":false")); + + settings.show_route_height_labels = true; + loader->save_user_settings(settings); + EXPECT_THAT(output, HasSubstr("\"show_route_height_labels\":true")); +} diff --git a/trview.app.ui.tests/SettingsWindowTests.cpp b/trview.app.ui.tests/SettingsWindowTests.cpp index 322e389de..c9424c92f 100644 --- a/trview.app.ui.tests/SettingsWindowTests.cpp +++ b/trview.app.ui.tests/SettingsWindowTests.cpp @@ -673,6 +673,44 @@ void register_settings_window_tests(ImGuiTestEngine* engine) IM_CHECK_EQ(ItemText(ctx, ctx->ItemInfo("TabBar/Route/Default Waypoint Colour/##Z")->ID), "B:255"); }); + test>(engine, "Settings Window", "Clicking Show Height Lines Raises Event", + [](ImGuiTestContext* ctx) { render(ctx->GetVars>()); }, + [](ImGuiTestContext* ctx) + { + auto messaging = mock_shared(); + auto& controls = ctx->GetVars>(); + controls.ptr = register_test_module().with_messaging(messaging).build(); + controls.ptr->toggle_visibility(); + + std::optional received_value; + EXPECT_CALL(*messaging, send_message).WillOnce(SaveArg<0>(&received_value)); + + ctx->SetRef("Settings"); + ctx->ItemClick("TabBar/Route"); + IM_CHECK_EQ(ctx->ItemIsChecked("TabBar/Route/Show Height Labels by Default"), true); + ctx->ItemUncheck("TabBar/Route/Show Height Labels by Default"); + IM_CHECK_EQ(ctx->ItemIsChecked("TabBar/Route/Show Height Labels by Default"), false); + IM_CHECK_EQ(received_value.has_value(), true); + IM_CHECK_EQ(get_settings(*received_value).show_route_height_labels, false); + }); + + test>(engine, "Settings Window", "Set Show Height Lines Updates Checkbox", + [](ImGuiTestContext* ctx) { render(ctx->GetVars>()); }, + [](ImGuiTestContext* ctx) + { + auto& controls = ctx->GetVars>(); + controls.ptr = register_test_module().build(); + controls.ptr->toggle_visibility(); + controls.ptr->receive_message(message({ .waypoint_colour = Colour(0.5f, 0.75f, 1.0f) })); + + ctx->SetRef("Settings"); + ctx->ItemClick("TabBar/Route"); + IM_CHECK_EQ(ctx->ItemIsChecked("TabBar/Route/Show Height Labels by Default"), true); + controls.ptr->receive_message(message({ .show_route_height_labels = false })); + ctx->Yield(); + IM_CHECK_EQ(ctx->ItemIsChecked("TabBar/Route/Show Height Labels by Default"), false); + }); + test>(engine, "Settings Window", "Set FOV Updates Slider", [](ImGuiTestContext* ctx) { render(ctx->GetVars>()); }, [](ImGuiTestContext* ctx) diff --git a/trview.app/Mocks/Routing/IRandomizerRoute.h b/trview.app/Mocks/Routing/IRandomizerRoute.h index ee7459804..875c60db5 100644 --- a/trview.app/Mocks/Routing/IRandomizerRoute.h +++ b/trview.app/Mocks/Routing/IRandomizerRoute.h @@ -45,6 +45,8 @@ namespace trview MOCK_METHOD(std::weak_ptr, waypoint, (uint32_t), (const, override)); MOCK_METHOD(uint32_t, waypoints, (), (const, override)); MOCK_METHOD(void, move_level, (const std::string&, const std::string&)); + MOCK_METHOD(void, set_show_height_labels, (bool), (override)); + MOCK_METHOD(bool, show_height_labels, (), (const, override)); }; } } diff --git a/trview.app/Mocks/Routing/IRoute.h b/trview.app/Mocks/Routing/IRoute.h index 2832d534a..d1087b39c 100644 --- a/trview.app/Mocks/Routing/IRoute.h +++ b/trview.app/Mocks/Routing/IRoute.h @@ -35,9 +35,11 @@ namespace trview MOCK_METHOD(void, set_colour, (const Colour&), (override)); MOCK_METHOD(void, set_filename, (const std::string&), (override)); MOCK_METHOD(void, set_level, (const std::weak_ptr&), (override)); + MOCK_METHOD(void, set_show_height_labels, (bool), (override)); MOCK_METHOD(void, set_show_route_line, (bool), (override)); MOCK_METHOD(void, set_unsaved, (bool), (override)); MOCK_METHOD(void, set_waypoint_colour, (const Colour&), (override)); + MOCK_METHOD(bool, show_height_labels, (), (const, override)); MOCK_METHOD(bool, show_route_line, (), (const, override)); MOCK_METHOD(Colour, waypoint_colour, (), (const, override)); MOCK_METHOD(std::weak_ptr, waypoint, (uint32_t), (const, override)); diff --git a/trview.app/Routing/IRoute.h b/trview.app/Routing/IRoute.h index 7960afb65..5be4312df 100644 --- a/trview.app/Routing/IRoute.h +++ b/trview.app/Routing/IRoute.h @@ -141,6 +141,7 @@ namespace trview /// The colour to use. virtual void set_colour(const Colour& colour) = 0; virtual void set_filename(const std::string& filename) = 0; + virtual void set_show_height_labels(bool show) = 0; virtual void set_show_route_line(bool show) = 0; virtual void set_level(const std::weak_ptr& level) = 0; /// @@ -153,6 +154,7 @@ namespace trview /// /// Whether the route has unsaved changes. virtual void set_unsaved(bool value) = 0; + virtual bool show_height_labels() const = 0; virtual bool show_route_line() const = 0; /// /// Get the colour to use for the stick. diff --git a/trview.app/Routing/RandomizerRoute.cpp b/trview.app/Routing/RandomizerRoute.cpp index 35413f1e6..4c24931ca 100644 --- a/trview.app/Routing/RandomizerRoute.cpp +++ b/trview.app/Routing/RandomizerRoute.cpp @@ -447,6 +447,16 @@ namespace trview return _waypoints.back(); } + void RandomizerRoute::set_show_height_labels(bool show) + { + _route->set_show_height_labels(show); + } + + bool RandomizerRoute::show_height_labels() const + { + return _route->show_height_labels(); + } + std::shared_ptr import_randomizer_route(const IRandomizerRoute::Source& route_source, const std::shared_ptr& files, const std::string& route_filename, const RandomizerSettings& randomizer_settings) { try diff --git a/trview.app/Routing/RandomizerRoute.h b/trview.app/Routing/RandomizerRoute.h index ad526d659..abdea5f51 100644 --- a/trview.app/Routing/RandomizerRoute.h +++ b/trview.app/Routing/RandomizerRoute.h @@ -52,8 +52,9 @@ namespace trview std::weak_ptr waypoint(uint32_t index) const override; uint32_t waypoints() const override; void move_level(const std::string& from, const std::string& to) override; - void import(const std::vector& data, const RandomizerSettings& randomizer_settings); + void set_show_height_labels(bool show) override; + bool show_height_labels() const override; private: void update_waypoints(); diff --git a/trview.app/Routing/Route.cpp b/trview.app/Routing/Route.cpp index af553be66..f348c1808 100644 --- a/trview.app/Routing/Route.cpp +++ b/trview.app/Routing/Route.cpp @@ -95,7 +95,7 @@ namespace trview } Route::Route(std::unique_ptr selection_renderer, const IWaypoint::Source& waypoint_source, const UserSettings& settings) - : _selection_renderer(std::move(selection_renderer)), _waypoint_source(waypoint_source), _colour(settings.route_colour), _waypoint_colour(settings.waypoint_colour) + : _selection_renderer(std::move(selection_renderer)), _waypoint_source(waypoint_source), _colour(settings.route_colour), _waypoint_colour(settings.waypoint_colour), _show_height_labels(settings.show_route_height_labels) { } @@ -532,6 +532,16 @@ namespace trview _waypoints = new_waypoints; } + bool Route::show_height_labels() const + { + return _show_height_labels; + } + + void Route::set_show_height_labels(bool show) + { + _show_height_labels = show; + } + std::shared_ptr import_route(const IRoute::Source& route_source, const std::shared_ptr& files, const std::string& route_filename) { try diff --git a/trview.app/Routing/Route.h b/trview.app/Routing/Route.h index 2b0a564b7..a6f08693f 100644 --- a/trview.app/Routing/Route.h +++ b/trview.app/Routing/Route.h @@ -50,7 +50,8 @@ namespace trview virtual Colour waypoint_colour() const override; virtual std::weak_ptr waypoint(uint32_t index) const override; virtual uint32_t waypoints() const override; - + bool show_height_labels() const override; + void set_show_height_labels(bool show) override; void import(const std::vector& data); private: uint32_t next_index() const; @@ -68,5 +69,6 @@ namespace trview std::weak_ptr _level; std::optional _filename; bool _show_route_line{ true }; + bool _show_height_labels{ true }; }; } diff --git a/trview.app/Settings/SettingsLoader.cpp b/trview.app/Settings/SettingsLoader.cpp index 8af6ed7de..2de1ce8b6 100644 --- a/trview.app/Settings/SettingsLoader.cpp +++ b/trview.app/Settings/SettingsLoader.cpp @@ -135,6 +135,7 @@ namespace trview read_attribute(json, settings.linear_filtering, "linear_filtering"); read_attribute(json, settings.version, "version"); read_attribute(json, settings.filter_directory, "filter_directory"); + read_attribute(json, settings.show_route_height_labels, "show_route_height_labels"); settings.recent_files.resize(std::min(settings.recent_files.size(), settings.max_recent_files)); } @@ -221,6 +222,7 @@ namespace trview json["linear_filtering"] = settings.linear_filtering; json["version"] = trview::version(); json["filter_directory"] = settings.filter_directory; + json["show_route_height_labels"] = settings.show_route_height_labels; _files->save_file(file_path, json.dump()); } catch (...) diff --git a/trview.app/Settings/UserSettings.h b/trview.app/Settings/UserSettings.h index 4c62a21ff..85463dcf5 100644 --- a/trview.app/Settings/UserSettings.h +++ b/trview.app/Settings/UserSettings.h @@ -91,6 +91,7 @@ namespace trview bool linear_filtering{ false }; std::string version; std::string filter_directory; + bool show_route_height_labels{ true }; bool operator==(const UserSettings& other) const; }; diff --git a/trview.app/UI/SettingsWindow.cpp b/trview.app/UI/SettingsWindow.cpp index 7b2bd67c2..317d2f9ff 100644 --- a/trview.app/UI/SettingsWindow.cpp +++ b/trview.app/UI/SettingsWindow.cpp @@ -236,6 +236,8 @@ namespace trview messages::send_settings(_message_system, _settings); } + checkbox(Names::show_height_labels, _settings.show_route_height_labels); + ImGui::EndTabItem(); } diff --git a/trview.app/UI/SettingsWindow.h b/trview.app/UI/SettingsWindow.h index 0c5b863d8..df99351f6 100644 --- a/trview.app/UI/SettingsWindow.h +++ b/trview.app/UI/SettingsWindow.h @@ -47,6 +47,7 @@ namespace trview static inline const std::string reset_fov = "Reset##Fov"; static inline const std::string statics_startup = "Open Statics Window at startup"; static inline const std::string linear_filtering = "Linear Filtering"; + static inline const std::string show_height_labels = "Show Height Labels by Default"; }; explicit SettingsWindow(const std::shared_ptr& dialogs, diff --git a/trview.app/UI/ViewerUI.cpp b/trview.app/UI/ViewerUI.cpp index a2047e60a..3bb120a04 100644 --- a/trview.app/UI/ViewerUI.cpp +++ b/trview.app/UI/ViewerUI.cpp @@ -429,21 +429,25 @@ namespace trview const auto pos = waypoint->screen_position(); if (is_on_screen(pos, *vp)) { - ImGui::SetNextWindowPos(vp->Pos + ImVec2(pos.x, pos.y)); - if (ImGui::Begin(std::format("##waypoint{}", i).c_str(), nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoFocusOnAppearing)) + const bool need_label = !notes.empty() || (route->show_height_labels() && diff != 0); + if (need_label) { - if (diff != 0) - { - const std::string sign = diff <= 0 ? "+" : "-"; - const int clicks = static_cast(round(diff / trlevel::Click)); - ImGui::Text(std::format("{}{} ({}{} clicks)", sign, abs(diff), sign, abs(clicks)).c_str()); - } - if (!notes.empty()) + ImGui::SetNextWindowPos(vp->Pos + ImVec2(pos.x, pos.y)); + if (ImGui::Begin(std::format("##waypoint{}", i).c_str(), nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoFocusOnAppearing)) { - ImGui::Text(notes.c_str()); + if (diff != 0) + { + const std::string sign = diff <= 0 ? "+" : "-"; + const int clicks = static_cast(round(diff / trlevel::Click)); + ImGui::Text(std::format("{}{} ({}{} clicks)", sign, abs(diff), sign, abs(clicks)).c_str()); + } + if (!notes.empty()) + { + ImGui::Text(notes.c_str()); + } } + ImGui::End(); } - ImGui::End(); } } } diff --git a/trview.app/Windows/RouteWindow.cpp b/trview.app/Windows/RouteWindow.cpp index d1e3908ed..29307cb9a 100644 --- a/trview.app/Windows/RouteWindow.cpp +++ b/trview.app/Windows/RouteWindow.cpp @@ -656,11 +656,16 @@ namespace trview { route->set_waypoint_colour(waypoint_colour); } - bool route_line = route->show_route_line();; + bool route_line = route->show_route_line(); if (ImGui::Checkbox("Route Line", &route_line)) { route->set_show_route_line(route_line); } + bool height_labels = route->show_height_labels(); + if (ImGui::Checkbox("Height Labels", &height_labels)) + { + route->set_show_height_labels(height_labels); + } ImGui::EndPopup(); } else