diff --git a/README.md b/README.md index 4a8d6387a..cab83adef 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,7 @@ a level file using the File menu or drag and drop a level file onto the window. Key|Action ---|------ CTRL + O | Open file -CTRL + G | Show 'go to room' box -CTRL + E | Show 'go to item' box +CTRL + F | Show find window CTRL + R | Open Route window CTRL + T | New Triggers window CTRL + I | New Items window @@ -177,19 +176,12 @@ Orthographic mode can be useful when paired with the compass selector to choose ### Reset Reset the orbit camera to default rotation. -## Go To Room -_Shortcut: Ctrl+G_ +## Find +_Shortcut: Ctrl+F_ -Enter a room number and press enter to go to that room. +Enter the number or name to search through all available items, triggers or rooms. The results will be presented below and selecting them with the arrow keys or clicking them will select them in the viewer. -![Go To Room](doc/go_to_room.png) - -## Go To Item -_Shortcut: Ctrl+E_ - -Enter an item number and press enter to go to that item. - -![Go To Item](doc/go_to_item.png) +![Find](doc/find.png) ## Minimap diff --git a/doc/find.png b/doc/find.png new file mode 100644 index 000000000..bfaea95b8 Binary files /dev/null and b/doc/find.png differ diff --git a/doc/go_to_item.png b/doc/go_to_item.png deleted file mode 100644 index f71107937..000000000 Binary files a/doc/go_to_item.png and /dev/null differ diff --git a/doc/go_to_room.png b/doc/go_to_room.png deleted file mode 100644 index 3fa798f7e..000000000 Binary files a/doc/go_to_room.png and /dev/null differ diff --git a/trview.app.tests/UI/GoToTests.cpp b/trview.app.tests/UI/GoToTests.cpp index cff837ff6..cd4598b96 100644 --- a/trview.app.tests/UI/GoToTests.cpp +++ b/trview.app.tests/UI/GoToTests.cpp @@ -4,47 +4,12 @@ using namespace trview; using namespace trview::tests; -TEST(GoTo, Name) -{ - GoTo window; - window.toggle_visible(); - - ASSERT_EQ(window.name(), ""); - window.set_name("Item"); - ASSERT_EQ(window.name(), "Item"); - - TestImgui imgui([&]() { window.render(); }); - ASSERT_NE(imgui.find_window(imgui.popup_id("Go To Item").name()), nullptr); -} - -TEST(GoTo, OnSelectedNotRaisedWhenMinusPressedAtZero) -{ - GoTo window; - window.toggle_visible(); - window.set_name("Item"); - - std::optional raised; - auto token = window.on_selected += [&](auto value) - { - raised = value; - }; - - TestImgui imgui([&]() { window.render(); }); - imgui.click_element( - imgui.popup_id("Go To Item").push("##gotoentry").id("-"), - false, - imgui.popup_id("Go To Item").id("##gotoentry")); - - ASSERT_FALSE(raised.has_value()); -} - TEST(GoTo, OnSelectedRaisedNumber) { GoTo window; window.toggle_visible(); - window.set_name("Item"); window.set_items({ { .number = 10, .name = "Room Ten" } }); - std::optional raised; + std::optional raised; auto token = window.on_selected += [&](auto value) { raised = value; @@ -59,7 +24,7 @@ TEST(GoTo, OnSelectedRaisedNumber) imgui.render(); ASSERT_TRUE(raised.has_value()); - ASSERT_EQ(raised.value(), 10u); + ASSERT_EQ(raised.value().number, 10u); ASSERT_FALSE(window.visible()); } @@ -67,9 +32,8 @@ TEST(GoTo, OnSelectedRaisedText) { GoTo window; window.toggle_visible(); - window.set_name("Item"); window.set_items({ {.number = 10, .name = "Room Ten" } }); - std::optional raised; + std::optional raised; auto token = window.on_selected += [&](auto value) { raised = value; @@ -84,7 +48,7 @@ TEST(GoTo, OnSelectedRaisedText) imgui.render(); ASSERT_TRUE(raised.has_value()); - ASSERT_EQ(raised.value(), 10u); + ASSERT_EQ(raised.value().number, 10u); ASSERT_FALSE(window.visible()); } @@ -92,9 +56,8 @@ TEST(GoTo, OnSelectedNotRaisedWhenCancelled) { GoTo window; window.toggle_visible(); - window.set_name("Item"); - std::optional raised; + std::optional raised; auto token = window.on_selected += [&](auto value) { raised = value; diff --git a/trview.app.tests/Windows/ViewerTests.cpp b/trview.app.tests/Windows/ViewerTests.cpp index 27649ae23..774f951c9 100644 --- a/trview.app.tests/Windows/ViewerTests.cpp +++ b/trview.app.tests/Windows/ViewerTests.cpp @@ -119,7 +119,6 @@ TEST(Viewer, SelectItemRaisedForValidItem) auto item = mock_shared(); auto level = mock_shared(); - EXPECT_CALL(*level, item(123)).WillRepeatedly(Return(item)); auto viewer = register_test_module().with_ui(std::move(ui_ptr)).build(); viewer->open(level, ILevel::OpenMode::Full); @@ -127,26 +126,12 @@ TEST(Viewer, SelectItemRaisedForValidItem) std::shared_ptr raised_item; auto token = viewer->on_item_selected += [&raised_item](const auto& item) { raised_item = item.lock(); }; - ui.on_select_item(123); + ui.on_select_item(item); ASSERT_TRUE(raised_item); ASSERT_EQ(raised_item, item); } -/// Tests that the on_hide event from the UI is observed but not forwarded when the item is invalid. -TEST(Viewer, SelectItemNotRaisedForInvalidItem) -{ - auto [ui_ptr, ui] = create_mock(); - auto viewer = register_test_module().with_ui(std::move(ui_ptr)).build(); - - std::shared_ptr raised_item; - auto token = viewer->on_item_selected += [&raised_item](const auto& item) { raised_item = item.lock(); }; - - ui.on_select_item(0); - - ASSERT_FALSE(raised_item); -} - /// Tests that the on_hide event from the UI is observed and forwarded when the item is valid. TEST(Viewer, ItemVisibilityRaisedForValidItem) { @@ -198,13 +183,14 @@ TEST(Viewer, SelectRoomRaised) auto [ui_ptr, ui] = create_mock(); auto viewer = register_test_module().with_ui(std::move(ui_ptr)).build(); + auto room = mock_shared()->with_number(100); std::optional raised_room; auto token = viewer->on_room_selected += [&raised_room](const auto& room) { raised_room = room; }; - ui.on_select_room(0); + ui.on_select_room(room); ASSERT_TRUE(raised_room.has_value()); - ASSERT_EQ(raised_room.value(), 0u); + ASSERT_EQ(raised_room.value(), 100u); } /// Tests that the trigger selected event is raised when the user clicks on a trigger. diff --git a/trview.app/UI/GoTo.cpp b/trview.app/UI/GoTo.cpp index b989aabe7..5f63c9d7d 100644 --- a/trview.app/UI/GoTo.cpp +++ b/trview.app/UI/GoTo.cpp @@ -3,6 +3,23 @@ namespace trview { + std::string GoTo::GoToItem::type() const + { + if (std::holds_alternative>(item)) + { + return "Item"; + } + else if (std::holds_alternative>(item)) + { + return "Trigger"; + } + else if (std::holds_alternative>(item)) + { + return "Room"; + } + return "?"; + } + bool GoTo::visible() const { return _visible; @@ -15,16 +32,6 @@ namespace trview _current_input.clear(); } - std::string GoTo::name() const - { - return _name; - } - - void GoTo::set_name(const std::string& name) - { - _name = name; - } - void GoTo::set_items(const std::vector& items) { _items = items; @@ -37,7 +44,7 @@ namespace trview const auto viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos + ImVec2(viewport->Size.x * 0.5f, viewport->Size.y * 0.25f), 0, ImVec2(0.5f, 0.5f)); - const std::string id = std::format("Go To {}", _name); + const std::string id = "Find"; if (!_shown) { ImGui::OpenPopup(id.c_str()); @@ -107,7 +114,7 @@ namespace trview list_focused = true; } - const auto item_id = std::format("{} - {}", item.number, item.name); + const auto item_id = std::format("{} {} - {}", item.type(), item.number, item.name); if (first_item && ImGui::GetCurrentContext()->NavId == ImGui::GetCurrentWindow()->GetID(item_id.c_str()) && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) @@ -118,7 +125,7 @@ namespace trview if (ImGui::Selectable(item_id.c_str(), false, ImGuiSelectableFlags_DontClosePopups | static_cast(ImGuiSelectableFlags_SelectOnNav))) { - on_selected(item.number); + on_selected(item); } any_selected |= ImGui::GetCurrentContext()->NavId == ImGui::GetCurrentWindow()->GetID(item_id.c_str()); diff --git a/trview.app/UI/GoTo.h b/trview.app/UI/GoTo.h index a3c7c2c2c..0889b5c68 100644 --- a/trview.app/UI/GoTo.h +++ b/trview.app/UI/GoTo.h @@ -7,11 +7,16 @@ #pragma once #include +#include #include #include namespace trview { + struct IItem; + struct IRoom; + struct ITrigger; + /// This window presents the user with a box where they can enter the number of the thing /// that they want to go to. Then when they press enter, that will be the selected. class GoTo final @@ -21,6 +26,9 @@ namespace trview { uint32_t number; std::string name; + std::variant, std::weak_ptr, std::weak_ptr> item; + + std::string type() const; }; /// Gets whether the window is currently visible. @@ -32,19 +40,12 @@ namespace trview /// Event raised when the user selects a new room. The newly selected room is passed as /// a parameter when the event is raised. - Event on_selected; - - /// Get the name of the type of thing being selected. - std::string name() const; + Event on_selected; void render(); - /// Set the name of the type of thing that is being gone to. - void set_name(const std::string& name); - void set_items(const std::vector& items); private: - std::string _name; bool _visible{ false }; bool _shown{ false }; std::vector _items; diff --git a/trview.app/UI/IViewerUI.h b/trview.app/UI/IViewerUI.h index bb6d5468b..265c84091 100644 --- a/trview.app/UI/IViewerUI.h +++ b/trview.app/UI/IViewerUI.h @@ -4,7 +4,6 @@ #include #include -#include #include #include #include "IContextMenu.h" @@ -14,6 +13,9 @@ namespace trview { + struct IRoom; + struct IItem; + enum class Tool { None, @@ -73,10 +75,10 @@ namespace trview Event> on_sector_hover; /// Event raised when an item is selected. - Event on_select_item; + Event> on_select_item; /// Event raised when a room is selected. - Event on_select_room; + Event> on_select_room; /// Event raised when the user settings are changed. Event on_settings; diff --git a/trview.app/UI/ViewerUI.cpp b/trview.app/UI/ViewerUI.cpp index 777d9f074..e264ef746 100644 --- a/trview.app/UI/ViewerUI.cpp +++ b/trview.app/UI/ViewerUI.cpp @@ -40,40 +40,34 @@ namespace trview _map_renderer->set_cursor_position(client_cursor_position(_window)); }; - _token_store += shortcuts->add_shortcut(true, 'G') += [&]() + _token_store += shortcuts->add_shortcut(true, 'F') += [&]() { if (!is_input_active()) { _tooltip->set_visible(false); - _go_to->set_name("Room"); _go_to->toggle_visible(); if (auto level = _level.lock()) { - _go_to->set_items( - level->rooms() + const auto items = level->items() + | std::views::transform([](auto&& i) { return i.lock(); }) + | std::views::filter([](auto&& i) { return i != nullptr; }) + | std::views::transform([](auto&& i) -> GoTo::GoToItem { return { .number = i->number(), .name = i->type(), .item = i }; }) + | std::ranges::to(); + + const auto triggers = level->triggers() + | std::views::transform([](auto&& t) { return t.lock(); }) + | std::views::filter([](auto&& t) { return t != nullptr; }) + | std::views::transform([](auto&& t) -> GoTo::GoToItem { return { .number = t->number(), .name = trigger_type_name(t->type()), .item = t }; }) + | std::ranges::to(); + + const auto rooms = level->rooms() | std::views::transform([](auto&& r) { return r.lock(); }) | std::views::filter([](auto&& r) { return r != nullptr; }) - | std::views::transform([](auto&& r) -> GoTo::GoToItem { return { .number = r->number(), .name = std::format("Room {}", r->number()) }; }) - | std::ranges::to()); - } - } - }; + | std::views::transform([](auto&& r) -> GoTo::GoToItem { return { .number = r->number(), .name = std::format("Room {}", r->number()), .item = r }; }) + | std::ranges::to(); - _token_store += shortcuts->add_shortcut(true, 'E') += [&]() - { - if (!is_input_active()) - { - _tooltip->set_visible(false); - _go_to->set_name("Item"); - _go_to->toggle_visible(); - if (auto level = _level.lock()) - { - _go_to->set_items( - level->items() - | std::views::transform([](auto&& i) { return i.lock(); }) - | std::views::filter([](auto&& i) { return i != nullptr; }) - | std::views::transform([](auto&& i) -> GoTo::GoToItem { return { .number = i->number(), .name = i->type() }; }) - | std::ranges::to()); + const auto all = { items, triggers, rooms }; + _go_to->set_items(std::views::join(all) | std::ranges::to()); } } }; @@ -81,16 +75,20 @@ namespace trview generate_tool_window(); _go_to = std::make_unique(); - _token_store += _go_to->on_selected += [&](uint32_t index) + _token_store += _go_to->on_selected += [&](const GoTo::GoToItem& item) { _tooltip->set_visible(false); - if (_go_to->name() == "Item") + if (std::holds_alternative>(item.item)) + { + on_select_item(std::get>(item.item)); + } + else if (std::holds_alternative>(item.item)) { - on_select_item(index); + on_select_trigger(std::get>(item.item)); } - else + else if (std::holds_alternative>(item.item)) { - on_select_room(index); + on_select_room(std::get>(item.item)); } }; @@ -214,7 +212,13 @@ namespace trview _view_options->on_alternate_group += on_alternate_group; _room_navigator = std::make_unique(); - _room_navigator->on_room_selected += on_select_room; + _token_store += _room_navigator->on_room_selected += [&](const auto index) + { + if (auto level = _level.lock()) + { + on_select_room(level->room(index)); + } + }; _camera_controls->on_reset += on_camera_reset; _camera_controls->on_mode_selected += on_camera_mode; diff --git a/trview.app/Windows/Viewer.cpp b/trview.app/Windows/Viewer.cpp index 41f68e4bc..f4c881273 100644 --- a/trview.app/Windows/Viewer.cpp +++ b/trview.app/Windows/Viewer.cpp @@ -83,17 +83,14 @@ namespace trview std::unordered_map> scalars; scalars[Options::depth] = [this](int32_t value) { if (auto level = _level.lock()) { level->set_neighbour_depth(value); } }; - _token_store += _ui->on_select_item += [&](uint32_t index) - { - if (auto level = _level.lock()) + _token_store += _ui->on_select_item += [&](const auto& item) { on_item_selected(item); }; + _token_store += _ui->on_select_room += [&](const auto& room) { - if (const auto item = level->item(index).lock()) + if (auto r = room.lock()) { - on_item_selected(item); + on_room_selected(r->number()); } - } - }; - _token_store += _ui->on_select_room += [&](uint32_t room) { on_room_selected(room); }; + }; _token_store += _ui->on_toggle_changed += [this, toggles, persist_toggle_value](const std::string& name, bool value) { auto toggle = toggles.find(name); diff --git a/trview.sln b/trview.sln index 892314c5e..e43f436b6 100644 --- a/trview.sln +++ b/trview.sln @@ -59,8 +59,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{B3A7F8A1-CC6 doc\context_menu.png = doc\context_menu.png doc\filters_1.png = doc\filters_1.png doc\filters_2.png = doc\filters_2.png - doc\go_to_item.png = doc\go_to_item.png - doc\go_to_room.png = doc\go_to_room.png doc\items.png = doc\items.png doc\level_info.png = doc\level_info.png doc\lights.png = doc\lights.png