Skip to content

Conversation

@pajawojciech
Copy link

@pajawojciech pajawojciech commented Dec 2, 2025

Add search to manager orders

Adds search box to find orders by job name + arrow indicators to show which one you're looking at.

What it does

  • Search box appears on orders screen (Alt+S to focus)
  • Type keywords to filter (e.g. "iron picks" finds "forge iron picks")
  • Multi-word search works in any order ("picks iron" and "pi ir" also matches)
  • Alt+P/N to jump between matches. Also Enter and Shift+Enter works when focused
  • Shows "3 of 12" style counter
  • Arrow point at current match (highlighted) and at all matches

Implementation

  • Uses df::interface_button_building_new_jobst to get order name
  • New overlay: OrdersSearchOverlay enabled by default
  • Moved importexport overlay position to make room for search (and make new version of config)

What didn't work

  • Tried to clear search input when exiting orders window - couldn't find reliable way to detect window exit

Related issues

Dwarf.Fortress.2026-01-04.14-44-09.mp4

Adds search overlay to find and navigate manager orders with arrow indicators showing current search result. Search uses Alt+S to focus, Alt+P/N for prev/next navigation. Overlays are disabled by default.
Copy link
Contributor

@ChrisJohnsen ChrisJohnsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice workaround for the inability to filter the view like in the other info panel search widgets.

I like the labeled magic numbers in OrderHighlightOverlay. DF's layout behavior is not fun thing to have to replicate, but I think this would be fairly clear to someone that hasn't looked at this stuff as much (at least as long as they have DF at hand to try out different window sizes).

Re: not being able to detect exiting the window: yeah, I have also wanted something
like a callback for "this overlay is no longer active (not going to be rendered)".


  • Would utils.search_text be useful here? The main SortOverlay uses it for searching.
  • Could the first match automatically be highlighted on Enter?
    I can see how you might not want to highlight (thus likely move the scroll position) for each change in search input, but Enter seems like a nice place to jump to the first match.
  • The highlight should probably be cleared when the search text changes (especially when the highlighted order does not match the new search input).
  • It might just be my color vision being flaky, but I completely did not notice the highlight arrow at first. Now that I know what to look for, it isn't hard to spot. But my first cycling through the matches was confusing because it wasn't obvious which order was being indicated.

@pajawojciech pajawojciech marked this pull request as draft December 6, 2025 13:06
@pajawojciech
Copy link
Author

* Would utils.search_text be useful here? The main SortOverlay uses it for searching.

I switched to using utils.search_text instead of the custom search logic

* Could the first match automatically be highlighted on Enter?

Added Enter/Shift+Enter to cycle through matches using the default submit/submit2 methods.

* The highlight should probably be cleared when the search text changes (especially when the highlighted order does not match the new search input).

Fixed, now the highlight gets cleared whenever the search text changes.

* It might just be my color vision being flaky, but I completely did not notice the highlight arrow at first. Now that I know what to look for, it isn't hard to spot. But my first cycling through the matches was confusing because it wasn't obvious which order was being indicated.

I reshaped the arrow, moved it to the right side of icon and used more contrasting colors (black on white) to make it more visible.
obraz


@ChrisJohnsen Thanks for all the detailed feedback! I made each change in a separate commit to make the review easier. Let me know if there's anything else that needs adjusting.

@pajawojciech pajawojciech marked this pull request as ready for review December 7, 2025 11:18
Copy link
Contributor

@ChrisJohnsen ChrisJohnsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spotting of the need to hide when job_details.open. The other functionality changes all seem good (with a new note about order deletion handling).

I've put some more thoughts on the specifics of the code in this review.

@pajawojciech pajawojciech marked this pull request as draft December 8, 2025 19:15
@ChrisJohnsen
Copy link
Contributor

So, I hadn't looked into the details of collect_reactions from internal/quickfort/stockflow until now... I had assumed (yeah, my mistake) that its order fields were Lua tables that mimicked df::manager_order objects enough that (e.g.) make_order_key could use either a real order or a "mocked up" one.

But those order fields are actual df::manager_order objects (~3500 of them) that are allocated and filled out in (functions called by) collect_reactions . So if callers don't free them, they end up leaked. Since these objects aren't needed by this code after keys are derived from them, they should probably just be freed right away.

The other caller is internal/quickfort/orders; it accepts the leak and only calls collect_reactions once (storing it in a module global; disclaiming any need for world-varying or modded reactions).

@pajawojciech pajawojciech marked this pull request as draft December 17, 2025 17:26
@pajawojciech
Copy link
Author

pajawojciech commented Dec 30, 2025

If there was some other approach for recreating the UI text DF would display for any given order, that might work better than trying to match order attributes against a predefined, cached list. That would probably take some reverse engineering work to suss out what all DF considers when rendering the order names.

Thanks for the suggestion, I used workshop button trick to make this works. Looks good and fast 👍

@pajawojciech pajawojciech marked this pull request as ready for review December 30, 2025 11:51
@ChrisJohnsen
Copy link
Contributor

Interesting.

I see that the similar DFHack::Job::getName is already used in scripts/do-job-now.lua and plugins/lua/spectate.lua. It does seem to work well. It produced the matching text for everything I tried (existing orders of a couple of forts, plus "assemble instrument" (for some non-ASCII characters), new-fangled dye mixing, gem encrusting, specified-wood/bone/hair/yarn, differently-sized clothing). It does seem fast enough (3.5µs per query when I benched 2 million calls).

Despite the similarity with getName I wonder if it belongs in DFHack::Job. Though I don't see a better place in the existing code (nothing that specializes in manager_order)… I don't know if a new namespace (DFHack::ManagerOrder?) would be appropriate for a single function.

For fun I also benched a Lua-only implementation. It is slower but not so slow that I noticed any problems in interactive use (55µs per query in a bench of a quarter million; ~35µs after some tweaking). So it could conceivably live on the Lua side if there isn't a good place for it on the C++ side.

I noticed the addition of setting button->art_specifier as compared to getName. Was that required for a particular kind of order or just added for completeness in matching up fields between manager_order and interface_button_building_new_jobst? I wonder if getName should adopt that, too.

This last niggle also exists in getName, but I wonder about copying order->specdata.hist_figure_id and then also order->specdata as a whole… It looks like hist_figure_id covers the entire union, but assigning the whole thing seems more general. I guess it shouldn't hurt, but the first seems unnecessary?


There are a couple of small UI bugs:

  1. When a search is active (showing "Search: X of Y") and the player scrolls the list it still shows "Search: X of Y" even though the highlight is no longer drawn.

    It should probably revert to "Search" or "Search: Y matches"?

  2. When a search is active (showing "Search: X of Y") and the player adds (or removes) a matching order it still shows "Search: X of Y".

    It should probably revert to "Search", or "Search: Z matches" (new Z value reflecting new number of matches), or "Search: W of Z" (might need to take care with deleting the last match).

Copy link
Contributor

@ChrisJohnsen ChrisJohnsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a review to tick the requested-review box. My previous comment also mentions a couple of remaining UI bugs.

@ChrisJohnsen
Copy link
Contributor

Found a leftover while reviewing my local notes:

If getManagerOrderName ends up in the C++-exported Lua API (whether it ends up in Jobs or elsewhere), it should probably get an entry in docs/dev/Lua API.rst (similar to the one for dfhack.job.getName).

@pajawojciech
Copy link
Author

Here is a review to tick the requested-review box.

Sorry, but I don't understand what this is about.

My previous comment also mentions a couple of remaining UI bugs.

UI bugs fixed and some more changes:

  • render method now contains periodically check for orders changes
  • search automatically refreshes when orders are added, removed, or modified
  • all matched orders are highlighted, not just the selected one
  • matching orders are highlighted in real-time as you type
  • highlights remain visible while scrolling for selected and matching orders

I love how it feels to use now that the order highlights stay visible. It's much more user-friendly. Thanks @ChrisJohnsen for the review!

obraz

@ChrisJohnsen
Copy link
Contributor

Here is a review to tick the requested-review box.

Sorry, but I don't understand what this is about.

Yeah, that was just GitHub noise. My first comment ("Interesting. …") was just a "comment" (not a "review"), so it did not clear the "review requested" state. I posted the second one ("Here is a review…") as a actual "review" to have GitHub mark the PR as "reviewed".

@ChrisJohnsen
Copy link
Contributor

Since all matches are now being highlighted search_cursor_visible is now synonymous with #search_matched_indices > 0, so a separate variable isn't really necessary.


You might want to use time-based updating instead of frame-based so that the update time doesn't vary (as much) with configured (or effective) frame rate.

The overlay system provides periodic calls to the overlay_onupdate method when it is defined. The period can be controlled by the overlay_onupdate_max_freq_seconds attribute/field. The default period is 5 seconds, but since the game is paused while these overlays are active it should be okay to request more frequent updates. A 1 second period would approximate your 50 frame interval (for the default 50 FPS setting), but float values between 0 and 1 also work here and could make it more responsive.

Hmm, this is another case where a "post input" callback would be handy. I've come across other uses for an overlay callback that occurs after DF has handled an input. Pretty much all player-originated order changes will need to done through some kind of input, so this overlay could (if a "post input" callback was available) just check whether the orders have changed after each input instead of periodically... I've added this use case to my notes.


Since the inter-overlay coordination has been awkward a couple of times (and just changed to be bi-directional with search_overlay_instance), it might be worth considering consolidating the overlays. I have shied away from suggesting it since it involves a bit of non-standard render usage, but it might end up being less awkward than the variable-mediated inter-overlay coordination.

The variable-mediated coordination relies to the pseudo-singleton nature of overlays (as managed by the overlay system) which isn't documented at all as far as I have seen.

Drawing the highlights outside the main widget's frame is kind of non-standard behavior and relies on the painter being clipped to the full interface area.

The view:render documentation does state that the parent's painter is supplied to render, but it is an assumption that an overlay widget's parent's painter is clipped to the full interface area (though not much of an assumption, in my opinion, since overlay widgets can normally be placed anywhere in the interface area). render is already present to cut off drawing when mi.job_details.open, so it would be easy to call a "draw highlights" function from there.

Neither is a perfect, but combining the overlays would get rid of all the non-constant variables.


A minor thing about "checksum": To me, the "checksum" strongly implies a small (or constant-sized) value. The concatenation of order names is serving a similar purpose (change detection), but it is basically just a copy of the data it is checking. Something like last_order_names might be better?

I experimented with a "checksum"-free version that just periodically, unconditionally called a modified update_filter that checked whether the new search_matched_indices was different from the old value and only reset search_current_match_idx when there was a change. It worked okay, but it did pull the frame rate down a bit (this was with a 0.1 second period, so fairly aggressive). The idea was that perform_search needed to get all the order names anyway, maybe that work could also serve to detect changes. But it looks like all the utils.search_text usage is more expensive than just collecting the order names an extra time.

@pajawojciech
Copy link
Author

Since all matches are now being highlighted search_cursor_visible is now synonymous with #search_matched_indices > 0, so a separate variable isn't really necessary.

Fixed


overlay_onupdate_max_freq_seconds

Great idea, done with 1 second period


consolidating the overlays

Done. Tried to do full_interface=true, but it makes no way to drag window using widget position adjustment ui, so it works without it


A minor thing about "checksum"

Renamed to concat_order_names() and cached_order_names


I made another UX improvement: scroll to selected order only when needed. Video in PR updated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants