feat(ui): Remake of Advanced Search#30
Conversation
Redesigned the Advanced Search dialog from a 4-column table into a more efficient 2-column layout with a dedicated filter panel and results view. Cleaned up rendering code, improved layout responsiveness, and streamlined the filter organization. - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp` — refactored UI into a 2-column layout with separate render functions for filters, updated bottom bar, and cleaned up rendering logic - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.h` — updated class structure, removed old column renderers, added new section-based render methods
Refactored the advanced search dialog UI, including improved result rendering with clipper support, updated layout, and modernized styling while removing unused dependencies. - `ImguiMapEditor/Controllers/SearchController.cpp` — removed unused `setItemPickerService` call from `onMapLoaded` - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp` — implemented `ImGuiListClipper` for search results, modernized dialog layout with `SeparatorText` and bordered footer, added `std::format` for label generation, and fixed tooltip/button handling - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.h` — removed unused `AppLogic::ItemPickerService` dependency and associated methods/state variables
Added state-based 'Searching...' UI feedback to AdvancedSearchDialog and SearchResultsWidget, and refactored the search results preview into a grid layout with card-based rendering. - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp` — added deferred search execution, state-based empty messages, grid layout for results, and search sorting logic - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.h` — added search state tracking flags and search execution method - `ImguiMapEditor/UI/Widgets/SearchResultsWidget.cpp` — added searching state feedback and setter methods for search buffer and searching flag - `ImguiMapEditor/UI/Widgets/SearchResultsWidget.h` — added search state flag and setter interfaces
Removed the redundant QuickSearchPopup component and its associated ItemPickerService to consolidate search functionality into the Advanced Search dialog. - `ImguiMapEditor/Application.cpp` — removed `quick_search` component wiring - `ImguiMapEditor/Application/CallbackMediator.cpp` — redirected Ctrl+F to open Advanced Search, removed quick search logic - `ImguiMapEditor/Application/CallbackMediator.h` — removed `ItemPickerService` and `QuickSearchPopup` forward declarations - `ImguiMapEditor/Application/Frame/RenderOrchestrator.cpp` — removed `quick_search_popup` rendering - `ImguiMapEditor/Application/Frame/RenderOrchestrator.h` — removed `quick_search_popup` from context - `ImguiMapEditor/CMakeLists.txt` — removed `ItemPickerService.cpp` and `QuickSearchPopup.cpp` from build - `ImguiMapEditor/Controllers/SearchController.cpp` — removed `QuickSearchPopup` and `ItemPickerService` initialization and usage - `ImguiMapEditor/Controllers/SearchController.h` — removed `ItemPickerService` header and `QuickSearchPopup` UI component - `ImguiMapEditor/Presentation/MenuBar.cpp` — removed Quick Find menu item and updated Find Items shortcut - `ImguiMapEditor/Presentation/MenuBar.h` — removed `on_quick_find_` callback - `ImguiMapEditor/Services/ItemPickerService.cpp` — [deleted] removed service - `ImguiMapEditor/Services/ItemPickerService.h` — [deleted] removed service header - `ImguiMapEditor/UI/Widgets/QuickSearchPopup.cpp` — [deleted] removed UI widget - `ImguiMapEditor/UI/Widgets/QuickSearchPopup.h` — [deleted] removed UI widget header
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughThis PR removes the QuickSearchPopup widget and ItemPickerService, consolidating search functionality into the AdvancedSearchDialog. Ctrl+F hotkey is remapped to open the advanced search dialog instead of the quick popup. The AdvancedSearchDialog is refactored with a grid-based preview UI replacing the previous list layout, implements deferred search execution via a new executeMapSearch() method, and reorganizes filter controls into dedicated render sections. SearchResultsWidget gains is_searching_ state tracking to display search progress. All callback wiring in Application, CallbackMediator, RenderOrchestrator, and MenuBar is updated to remove quick-search references and ensure consistent hotkey/menu integration. Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Reviewer's GuideRefactors the Advanced Search feature into a two-panel layout with improved state handling and result rendering, wires it to Ctrl+F and the Search menu as the primary catalog search, removes the legacy QuickSearchPopup and ItemPickerService, and enhances SearchResultsWidget with explicit searching state and messaging. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
AdvancedSearchDialog::updatePreviewResults, the conditionif (search_items && search_service_ && !property_filter_.hasAnySelected() || !type_filter_.creature)relies on&&/||precedence and is hard to read; consider adding parentheses to make the intended logic explicit (e.g.,if ((search_items && search_service_ && !property_filter_.hasAnySelected()) || !type_filter_.creature)or similar). - In the sorting lambda for
preview_results_, the server ID is narrowed touint16_t(uint16_t a_id = ...); if server IDs can exceed 65535, consider using the original wider type to avoid silent truncation affecting sort order.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `AdvancedSearchDialog::updatePreviewResults`, the condition `if (search_items && search_service_ && !property_filter_.hasAnySelected() || !type_filter_.creature)` relies on `&&`/`||` precedence and is hard to read; consider adding parentheses to make the intended logic explicit (e.g., `if ((search_items && search_service_ && !property_filter_.hasAnySelected()) || !type_filter_.creature)` or similar).
- In the sorting lambda for `preview_results_`, the server ID is narrowed to `uint16_t` (`uint16_t a_id = ...`); if server IDs can exceed 65535, consider using the original wider type to avoid silent truncation affecting sort order.
## Individual Comments
### Comment 1
<location path="ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp" line_range="401-407" />
<code_context>
- // Search items (only if not creature-only filter)
+
bool search_items = !type_filter_.creature || type_filter_.hasAnySelected();
if (search_items && search_service_ && !property_filter_.hasAnySelected() || !type_filter_.creature) {
- // If only creature is selected, skip item search
- if (!(type_filter_.creature && !type_filter_.depot && !type_filter_.mailbox &&
+ if (!(type_filter_.creature && !type_filter_.depot && !type_filter_.mailbox &&
!type_filter_.trash_holder && !type_filter_.container && !type_filter_.door &&
!type_filter_.magic_field && !type_filter_.teleport && !type_filter_.bed &&
!type_filter_.key && !type_filter_.podium)) {
-
+
auto item_results = search_service_->searchItemDatabase(
- search_buffer_,
- type_filter_,
</code_context>
<issue_to_address>
**issue (bug_risk):** Clarify boolean logic in item search condition to avoid unintended behavior.
The condition `if (search_items && search_service_ && !property_filter_.hasAnySelected() || !type_filter_.creature)` relies on `&&`/`||` precedence and is effectively `if ((search_items && search_service_ && !property_filter_.hasAnySelected()) || !type_filter_.creature)`. This allows the body to run whenever `!type_filter_.creature` is true, even if `search_items` is false or `search_service_` is null (which would then be dereferenced). Please add explicit grouping (e.g. `if (search_service_ && search_items && !property_filter_.hasAnySelected())`) and handle the `!type_filter_.creature` case separately if it’s intended as a special override.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| BestPractice | 1 medium |
| ErrorProne | 1 medium |
| Complexity | 3 medium |
🟢 Metrics 17 complexity · 0 duplication
Metric Results Complexity 17 Duplication 0
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
📋 Review Summary
This PR successfully remakes the Advanced Search dialog, replacing the older Quick Search popup with a more robust 2-column layout and grid-based results with sprite previews. It also improves the Search Results Widget by adding a "Searching..." state and better empty state handling.
🔍 General Feedback
- Architecture: The removal of
ItemPickerServiceand the consolidation of search logic intoMapSearchServiceandAdvancedSearchDialogis a good simplification. - UI/UX: The new grid-based results with tooltips and double-click to search map is a significant improvement over the old list-based quick search.
- Correctness: Found one logical bug in
updatePreviewResultsthat could prevent item results from showing when both creature and property filters are active. - Performance: The grid uses
ImGuiListClipper, which is good for performance with large result sets.
I've left a few inline comments regarding the logic bug and some minor UI polish opportunities.
|
|
||
| // Load sprite texture | ||
| uint32_t tex_id = 0; | ||
| bool sprite_rendered = false; |
There was a problem hiding this comment.
🔴 Bug in logic: This condition incorrectly skips item search if a creature filter is selected alongside any property filter, even if item types (like Depot) are also selected.
For example, if the user selects Type: Depot and Type: Creature and Property: Unpassable, they expect to see Unpassable Depots. However, because !type_filter_.creature is false and !property_filter_.hasAnySelected() is false, the entire condition evaluates to false, and no items are searched.
The item search should be performed if at least one item type is selected (or no types are selected) regardless of whether the creature filter is active, as searchItemDatabase correctly handles the property filtering.
| bool sprite_rendered = false; | |
| bool has_item_type_selected = type_filter_.hasAnySelected(); | |
| // ... search_service_->searchItemDatabase already handles filtering items by type and property. | |
| if (search_service_) { |
(Wait, I should check if hasAnySelected includes creature. If it does, then the suggested check is type_filter_.hasAnySelected()).
Actually, the inner check already filters out the "only creature" case. So you can probably just simplify this to if (search_service_) {.
| ImGui::SetClipboardText(std::to_string(result.item->server_id).c_str()); | ||
| Presentation::showSuccess("Item ID copied"); | ||
| // Grid flow | ||
| if (col < columns - 1 && idx + 1 < static_cast<int>(preview_results_.size())) { |
There was a problem hiding this comment.
🟡 The is_searching_ flag in the widget is set to true here, but AdvancedSearchDialog::executeMapSearch (called next frame) will immediately call results_widget_->setResults(), which sets is_searching_ back to false before the widget even renders its next frame. Similar to the dialog's own is_searching_ flag, this deferred logic doesn't actually result in a visible "Searching..." state for synchronous searches.
| executeMapSearch(); | ||
| } | ||
|
|
||
| ImGui::SetNextWindowSize(ImVec2(850, 600), ImGuiCond_Once); |
There was a problem hiding this comment.
🟡 The "Searching..." state will never be rendered because is_searching_ is set to false immediately before calling executeMapSearch() and then rendering the rest of the frame.
To fix this, executeMapSearch() should be deferred by one more frame, or is_searching_ should only be set to false AFTER the search completes (e.g. at the end of executeMapSearch).
However, since the search is synchronous and blocks the main thread, the user still won't see the spinner until the next frame anyway. If the search is fast, this doesn't matter much.
|
|
||
| // Tooltip on hover | ||
| if (hovered) { | ||
| ImGui::BeginTooltip(); |
There was a problem hiding this comment.
🟡 Sorting by uint16_t might be fragile if server IDs ever exceed 65535 (though unlikely in current OT protocol). Using int or uint32_t for a_id/b_id would be safer. Also, using a larger constant than UINT16_MAX for creatures (like 0xFFFFFFFF) would ensure they always stay at the end.
There was a problem hiding this comment.
Pull Request Overview
The remake of the Advanced Search dialog is currently not up to standards. A critical logic error in the filtering condition will cause a application crash when searching for items because operator precedence allows the code to bypass a null check on the search service. Furthermore, the 'Searching...' state implementation is functionally broken as it executes a blocking search on the main thread before the UI can render the updated state, resulting in a frame hitch rather than a progress indicator.
The PR also introduces a feature regression by removing the 'Copy' utility for IDs/Names and includes incomplete 'Select Item' functionality. Security-wise, the use of strncpy in the search widget should be replaced with safer alternatives to comply with secure coding standards.
About this PR
- The PR description is empty. Please provide context regarding the design requirements and confirm if the removal of the 'Copy ID/Name' feature was an intentional design decision.
- The 'Select Item' button is currently a disabled placeholder. If this feature is out of scope for this PR, consider removing the UI element until the backend logic is ready to avoid user confusion.
Test suggestions
- Search with name or ID correctly filters the preview results list
- Applying multiple type filters (OR) broadens the result set
- Applying property filters (AND) narrows the result set
- Double-clicking a result item triggers a map search through the SearchService
- Deferred search logic correctly triggers 'is_searching' state before executing the search
- Sorting correctly handles creatures by moving them to the end of the results
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Search with name or ID correctly filters the preview results list
2. Applying multiple type filters (OR) broadens the result set
3. Applying property filters (AND) narrows the result set
4. Double-clicking a result item triggers a map search through the SearchService
5. Deferred search logic correctly triggers 'is_searching' state before executing the search
6. Sorting correctly handles creatures by moving them to the end of the results
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| // Search items (only if not creature-only filter) | ||
|
|
||
| bool search_items = !type_filter_.creature || type_filter_.hasAnySelected(); | ||
| if (search_items && search_service_ && !property_filter_.hasAnySelected() || !type_filter_.creature) { |
There was a problem hiding this comment.
🔴 HIGH RISK
Critical: Potential null pointer dereference due to operator precedence. If type_filter_.creature is false, the search_service_ null check is bypassed. Wrap the search service and property filter checks in parentheses or add an explicit guard: (search_items && search_service_ && !property_filter_.hasAnySelected()) || !type_filter_.creature still risks the crash if the right side is true; the logic should likely be search_service_ && ((search_items && !property_filter_.hasAnySelected()) || !type_filter_.creature).
|
|
||
| void setResults(const std::vector<Domain::Search::MapSearchResult>& results); | ||
| void setSearching(bool searching) { is_searching_ = searching; } | ||
| void setSearchBuffer(const char* query) { strncpy(search_buffer_, query, sizeof(search_buffer_) - 1); search_buffer_[sizeof(search_buffer_) - 1] = '\0'; } |
There was a problem hiding this comment.
🔴 HIGH RISK
Suggestion: The use of strncpy is discouraged as it is error-prone regarding null-termination. Use snprintf or a safer string-copy alternative to prevent potential buffer overflows.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ImguiMapEditor/Application/CallbackMediator.cpp`:
- Around line 542-545: The hotkey handler registered via
ctx.hotkey->setAdvancedSearchCallback currently forces vs->show_search_results =
true so it never closes; change the lambda in that registration (the closure
capturing vs from ctx.view_settings) to toggle the flag instead (set
vs->show_search_results to its negation) so Ctrl+Shift+F both opens and closes
the Search Results widget.
In `@ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp`:
- Around line 467-475: AdvancedSearchDialog::render currently calls
executeMapSearch which invokes MapSearchService::search() inline (blocking the
UI); replace those synchronous calls so the creature/item branches use the
service's asynchronous search path (the same async API used elsewhere) instead
of MapSearchService::search(). Keep setting
results_widget_->setSearchBuffer(...) as before, invoke the async search on
search_service_ with the same search string and mode (for creature name and item
server_id), and wire the async completion callback to populate the local results
variable and update the results_widget_ (and cancel any prior async search if
the service supports cancellation) so the map scan runs off the render thread
rather than synchronously in executeMapSearch/AdvancedSearchDialog::render.
- Around line 400-408: The if-condition that gates calling
search_service_->searchItemDatabase can be true when search_service_ is null
because of operator precedence and the trailing "!type_filter_.creature" term;
update the condition so search_service_ is always required before dereferencing
(e.g. group the left-side checks with parentheses) and also extend the
creature-only exclusion list to include type_filter_.weapon, type_filter_.armor,
type_filter_.ammo, and type_filter_.rune so creature-only cases still suppress
item previews; specifically modify the boolean expression that uses
search_items, search_service_, property_filter_.hasAnySelected(), and all
type_filter_ flags guarding the call to search_service_->searchItemDatabase to
ensure (search_items && search_service_ && !property_filter_.hasAnySelected())
is evaluated together and the creature-only check includes
weapon/armor/ammo/rune.
In `@ImguiMapEditor/UI/Widgets/SearchResultsWidget.cpp`:
- Around line 434-437: Currently the widget sets is_searching_ = true before
checking on_search_async_, which leaves the widget stuck in searching state if
the callback isn't wired (SearchController::onMapLoaded). Change the logic so
you only set is_searching_ to true when on_search_async_ is valid: check
on_search_async_ first and then set is_searching_ and call
on_search_async_(search_buffer_, search_items_, search_creatures_); otherwise
ensure is_searching_ is explicitly set to false (or early-return) so the flag is
never left true when no backend callback exists.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c190ec71-08fd-48e9-8ef2-9a690ea687ea
📒 Files selected for processing (18)
ImguiMapEditor/Application.cppImguiMapEditor/Application/CallbackMediator.cppImguiMapEditor/Application/CallbackMediator.hImguiMapEditor/Application/Frame/RenderOrchestrator.cppImguiMapEditor/Application/Frame/RenderOrchestrator.hImguiMapEditor/CMakeLists.txtImguiMapEditor/Controllers/SearchController.cppImguiMapEditor/Controllers/SearchController.hImguiMapEditor/Presentation/MenuBar.cppImguiMapEditor/Presentation/MenuBar.hImguiMapEditor/Services/ItemPickerService.cppImguiMapEditor/Services/ItemPickerService.hImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cppImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.hImguiMapEditor/UI/Widgets/QuickSearchPopup.cppImguiMapEditor/UI/Widgets/QuickSearchPopup.hImguiMapEditor/UI/Widgets/SearchResultsWidget.cppImguiMapEditor/UI/Widgets/SearchResultsWidget.h
💤 Files with no reviewable changes (10)
- ImguiMapEditor/Services/ItemPickerService.cpp
- ImguiMapEditor/Controllers/SearchController.h
- ImguiMapEditor/UI/Widgets/QuickSearchPopup.h
- ImguiMapEditor/CMakeLists.txt
- ImguiMapEditor/Application.cpp
- ImguiMapEditor/Services/ItemPickerService.h
- ImguiMapEditor/Application/Frame/RenderOrchestrator.h
- ImguiMapEditor/UI/Widgets/QuickSearchPopup.cpp
- ImguiMapEditor/Application/CallbackMediator.h
- ImguiMapEditor/Presentation/MenuBar.h
| if (selected.is_creature && selected.creature) { | ||
| // Search map for this creature by name | ||
| results = search_service_->search( | ||
| selected.creature->name, | ||
| Services::MapSearchMode::ByName, | ||
| false, // search_items | ||
| true, // search_creatures | ||
| 1000 | ||
| ); | ||
| // Set widget search buffer so empty state shows "No results found" | ||
| if (results_widget_) results_widget_->setSearchBuffer(selected.creature->name.c_str()); | ||
| results = search_service_->search(selected.creature->name, Services::MapSearchMode::ByName, false, true, 1000); | ||
| } else if (selected.item) { | ||
| // Search map for this item by server ID | ||
| results = search_service_->search( | ||
| std::to_string(selected.item->server_id), | ||
| Services::MapSearchMode::ByServerId, | ||
| true, // search_items | ||
| false, // search_creatures | ||
| 1000 | ||
| ); | ||
| } | ||
|
|
||
| // Output to SearchResultsWidget | ||
| if (results_widget_) { | ||
| results_widget_->setResults(results); | ||
| } | ||
|
|
||
| // Auto-show the search results widget | ||
| if (view_settings_) { | ||
| *view_settings_ = true; | ||
| auto id_str = std::to_string(selected.item->server_id); | ||
| if (results_widget_) results_widget_->setSearchBuffer(id_str.c_str()); | ||
| results = search_service_->search(id_str, Services::MapSearchMode::ByServerId, true, false, 1000); | ||
| } |
There was a problem hiding this comment.
Don't scan the map synchronously from the render loop.
executeMapSearch() is called from AdvancedSearchDialog::render(), but it performs MapSearchService::search() inline. That search walks the map, so on large maps this still blocks the UI thread right after the one-frame "Searching..." state. Please route this through the existing async search path instead of calling search() directly here.
🧰 Tools
🪛 Cppcheck (2.20.0)
[style] 467-467: The function 'forEachTileOnFloor' is never used.
(unusedFunction)
[style] 474-474: The function 'forEachTileOnFloorMutable' is never used.
(unusedFunction)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp` around lines 467 - 475,
AdvancedSearchDialog::render currently calls executeMapSearch which invokes
MapSearchService::search() inline (blocking the UI); replace those synchronous
calls so the creature/item branches use the service's asynchronous search path
(the same async API used elsewhere) instead of MapSearchService::search(). Keep
setting results_widget_->setSearchBuffer(...) as before, invoke the async search
on search_service_ with the same search string and mode (for creature name and
item server_id), and wire the async completion callback to populate the local
results variable and update the results_widget_ (and cancel any prior async
search if the service supports cancellation) so the map scan runs off the render
thread rather than synchronously in
executeMapSearch/AdvancedSearchDialog::render.
Refactored search toggling behavior, improved safety in async search execution, and refined item database filtering to properly exclude creature-only searches. Replaced deprecated string copying in search buffer management. - `ImguiMapEditor/Application/CallbackMediator.cpp` — changed search results toggle from forced show to toggle behavior - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp` — expanded item exclusion logic to correctly skip item search when only creature or combat types are selected - `ImguiMapEditor/UI/Widgets/SearchResultsWidget.cpp` — added null check for `on_search_async_` before execution - `ImguiMapEditor/UI/Widgets/SearchResultsWidget.h` — replaced `strncpy` with `snprintf` for safer buffer writing
There was a problem hiding this comment.
📋 Review Summary
The PR successfully refactors the search functionality by consolidating the "Quick Search" and "Advanced Search" into a single, unified experience. The redesign of the AdvancedSearchDialog into a 2-column layout with a results grid is a significant UX improvement, and the use of ImGuiListClipper ensures it remains performant even with large result sets.
However, there are some concerns regarding the performance of the map search, which is currently executed synchronously on the main thread, potentially leading to UI freezes. Additionally, the handling of the "Searching..." state has some minor logical flaws that might prevent the spinner from being visible.
🔍 General Feedback
- Performance: offload
executeMapSearchto a background thread to prevent blocking the UI during map-wide searches. - UX: The grid layout is excellent, but a slightly larger cell size might help in identifying creatures and complex items.
- Consistency: Consider unifying the async search patterns across
SearchController,AdvancedSearchDialog, andSearchResultsWidget.
Great job on the UI overhaul! 🚀
| if (ImGui::Checkbox("Readable", &property_filter_.readable)) filters_changed_ = true; | ||
| if (ImGui::Checkbox("Pickupable", &property_filter_.pickupable)) filters_changed_ = true; | ||
| if (ImGui::Checkbox("Animation", &property_filter_.animation)) filters_changed_ = true; | ||
| if (ImGui::Checkbox("Ignore Look", &property_filter_.ignore_look)) filters_changed_ = true; |
There was a problem hiding this comment.
🟡 The 32x32 CELL_SIZE might be too cramped for some creature previews or larger items. While Tibia tiles are 32x32, multi-tile creatures might not be very recognizable at this scale. Increasing this slightly (e.g., to 48x48) might improve usability while still keeping a good density.
| } | ||
|
|
||
| void AdvancedSearchDialog::renderSearchSection() { | ||
| ImGui::SeparatorText(ICON_FA_MAGNIFYING_GLASS " Search"); |
There was a problem hiding this comment.
🟡 filters_changed_ is set to true on every keystroke in the search input. While searchItemDatabase is generally fast, iterating over ~30k item types and performing fuzzy matching on every character might cause micro-stutters. Consider adding a small debounce or only updating when the query length is >= 2-3 characters (unless it's numeric).
|
|
||
| void AdvancedSearchDialog::renderHintsSection() { | ||
| ImGui::SeparatorText(ICON_FA_CIRCLE_INFO " Hints"); | ||
| ImGui::TextDisabled("Double-click result to search map."); |
There was a problem hiding this comment.
🟢 Using ImGuiListClipper for the results grid is a good performance optimization for handling large search result sets.
| if (!result.item->name.empty()) { | ||
| ImGui::Text("Name: %s", result.item->name.c_str()); | ||
|
|
||
| // Tooltip on hover |
There was a problem hiding this comment.
🟡 setSearching is called here, but executeMapSearch in the next frame sets is_searching_ = false before actually finishing the work. This defeats the purpose of the spinner.
| } | ||
|
|
||
| void SearchResultsWidget::setResults(const std::vector<Domain::Search::MapSearchResult>& results) { | ||
| is_searching_ = false; |
There was a problem hiding this comment.
🟡 setResults clears is_searching_, which is correct if the search is async. However, if the caller is synchronous (like AdvancedSearchDialog), the UI will be frozen during the search anyway.
| const float spacing = ImGui::GetStyle().ItemSpacing.x; | ||
| const float footer_h = ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 2.0f + 20.0f; | ||
| const float content_h = ImGui::GetContentRegionAvail().y - footer_h; | ||
| const float filters_w = ImGui::GetContentRegionAvail().x * 0.35f; |
There was a problem hiding this comment.
🟡 The footer height calculation uses a magic number 20.0f. Consider using style variables (e.g., ImGuiStyleVar_ItemSpacing) or a more descriptive constant for better maintainability and scaling with different DPI/themes.
| ImGui::BeginTooltip(); | ||
| if (sprite_rendered) { | ||
| ImGui::Image(TexId(tex_id), ImVec2(PREVIEW_SIZE, PREVIEW_SIZE)); | ||
| ImGui::SameLine(); |
There was a problem hiding this comment.
🔴 executeMapSearch is called synchronously on the UI thread. Since it iterates over all map tiles (via search_service_->search), this will cause a significant UI freeze on large maps. It should be offloaded to a background thread, possibly by utilizing the SearchController::launchAsync mechanism already present in the codebase.
|
|
||
| ctx.menu_bar->setFindItemsCallback( | ||
| [ctx]() { ctx.view_settings->show_search_results = true; }); | ||
| [ctx]() { if (ctx.advanced_search) ctx.advanced_search->open(); }); |
There was a problem hiding this comment.
🟢 Consolidating quick search and advanced search into a single unified dialog simplifies the UI and keyboard shortcuts for the user.
| executeMapSearch(); | ||
| } | ||
|
|
||
| ImGui::SetNextWindowSize(ImVec2(850, 600), ImGuiCond_Once); |
There was a problem hiding this comment.
🟡 is_searching_ is set to false immediately before calling executeMapSearch. Combined with the fact that executeMapSearch is synchronous, the "Searching..." state and spinner in SearchResultsWidget will likely never be visible to the user, or only for a single frame.
if (pending_map_search_) {
pending_map_search_ = false;
is_searching_ = false; // Should remain true until search completes
executeMapSearch();
}| std::vector<size_t> filtered_indices_; | ||
| size_t filtered_count_ = 0; | ||
| bool filter_dirty_ = true; | ||
| bool is_searching_ = false; |
There was a problem hiding this comment.
🟡 The SearchResultsWidget now has its own is_searching_ state, but it seems to be duplicated with the logic in AdvancedSearchDialog. It might be cleaner to have SearchResultsWidget manage its own "searching" state or have a unified way to report progress if searches are moved to SearchController.
|
Warning Gemini encountered an error creating the summary. You can try again by commenting |
Removed the `is_searching_` state flag and associated UI logic, as deferred search execution was already handling the requirement effectively. Adjusted footer padding calculation for better UI spacing consistency. - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.cpp` — removed `is_searching_` management, updated search triggering logic, and refined footer height calculation - `ImguiMapEditor/UI/Dialogs/AdvancedSearchDialog.h` — removed `is_searching_` member variable
There was a problem hiding this comment.
📋 Review Summary
This PR remakes the Advanced Search dialog and consolidates it with the Quick Search functionality. The UI has been improved with a grid preview of results and better state management (e.g., "Searching..." state).
🔍 General Feedback
Overall, the refactoring is a good step towards a more unified search experience. The use of deferred search logic and ImGuiListClipper shows attention to performance and UI responsiveness.
I've noted a few minor issues regarding:
- Fragile logic in filter checking.
- Potential edge cases in sorting.
- Minor UX improvements for the loading state.
- Defensive programming for layout calculations.
Most of these are non-blocking but would improve the robustness of the search feature.
| constexpr float IMG_PADDING = 2.0f; | ||
| const float pad = ImGui::GetStyle().WindowPadding.x; | ||
| const float avail_w = ImGui::GetContentRegionAvail().x; | ||
| const int columns = std::max(1, static_cast<int>((avail_w - pad) / CELL_SIZE)); |
There was a problem hiding this comment.
🟡 The creature_only boolean calculation is fragile as it manually excludes every other flag in TypeFilter. If TypeFilter is updated with new types in the future, this logic will likely break or become incorrect.
Consider adding a method like TypeFilter::hasAnyItemTypeSelected() to encapsulate this check.
|
|
||
| if (ImGui::Begin(ICON_FA_MAGNIFYING_GLASS_PLUS " Advanced Search###AdvancedSearch", &is_open_, | ||
| ImGuiWindowFlags_NoCollapse)) { | ||
|
|
There was a problem hiding this comment.
🟡 If footer_h happens to be larger than the available vertical space, content_h will be negative, which can lead to undefined behavior or assertions in ImGui::BeginChild.
| const float content_h = std::max(0.0f, ImGui::GetContentRegionAvail().y - footer_h); |
| std::string text = std::format("{} {}", search_buffer_[0] ? ICON_FA_CIRCLE_EXCLAMATION : ICON_FA_KEYBOARD, msg); | ||
| const char* icon; | ||
| const char* msg; | ||
| if (is_searching_) { |
There was a problem hiding this comment.
🟢 The ICON_FA_SPINNER icon is static. For a better "searching" UX, you might want to rotate it or use a proper ImGui spinner if available.
| auto preview = Utils::GetCreaturePreview(*client_data_, *sprite_manager_, result.creature->outfit); | ||
| if (preview && preview.texture) { | ||
| tex_id = preview.texture->id(); | ||
| sprite_rendered = true; |
There was a problem hiding this comment.
🟡 Using UINT16_MAX to sort creatures at the end might cause unexpected ordering if an item in the database actually has a server_id of 65535. While rare in many OT versions, it's a magic number dependency.
Consider using a more robust comparison that explicitly checks is_creature first.
Summary by CodeRabbit
Refactor
UI