Skip to content

Demonstrate gui.dflayout library and use it in gui/mass-remove.toolbar #1426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

ChrisJohnsen
Copy link
Contributor

@ChrisJohnsen ChrisJohnsen commented Mar 30, 2025

I mentioned on Discord that I had worked up a data-driven way of calculating the positions of DF toolbars.

Here it is. Of course, it would be better to get position information directly from DF, but having a single place that calculates this stuff should make it easier to provide reliable positioning for all the new toolbar buttons that are being added.

Running internal/df-bottom-toolbars will launch a "demonstration" that can be used to check that the computed values match what DF does at various screen sizes and interface percentages.

Highlights of the positioning calculations:

  • Derived from black-box observations.
    • Not sure if it is feasible to have the RE team verify the magic/oddities noted below (7 columns between left and center, "'double" rounding when centering, 5 columns to the left of secondary)
  • All positioning is inside the interface area.
  • There are three fort-mode toolbars:
    • 1 flush-left (info buttons)
    • 1 "centered" (designations, etc.)
      • the centered toolbar is always at least 7 columns away from the left toolbar, so not always exactly "centered"

      • the centering involves a little bit of odd rounding

        math.ceil((interface_rect.width - self.width + 1) / 2)
        

        This odd double-rounding seems to work correctly and kind of matches part of gui.get_interface_rect() (was that re-implemented from reverse engineering?).

    • 1 flush-right (squad and world)
  • Most of the center toolbar "tools" can have a secondary toolbar that opens above the main toolbar
    • the secondary toolbar "wants to" be positioned directly above the button that opened it
      • when this is not possible because the secondary toolbar is too wide (possibly because of hidden advanced options that still influence placement), it may be pushed to the left to fit into the interface area
        • if the secondary toolbar is "being pushed from the right", there will be a 5 column gap between the right end of the secondary toolbar at the far-right side of the interface area

It was nice to be able to fix the top-/right-anchored overlay positioning error that the warmdamp button also has, but I'm not sure if it is worth it in the form I came up with (last commit). It was kind of a last-minute thing that I tried after getting everything else ready for upload. It could have been a separate set of "tooltip" and "icon" that the layout updating code swapped between, but that maybe seems worse?

@realSquidCoder
Copy link
Contributor

i feel like you should move the demo code into a new file under the devel folder and call what you need like you would for anything else that should use this

@ChrisJohnsen
Copy link
Contributor Author

Beyond the usual review stuff, here are some things that I think merit some review consideration:

  • Is this useful?
    This arose due to the recent crop of new buttons. If we don't expect many more (things are getting pretty tight on the primary toolbar), then maybe this isn't worthwhile to carry around. I found the related warmdamp code hard to understand at first glance since it wasn't obvious why various (magic) constants were being used. This library should let the caller ask "in the provided interface size, where is this DF toolbar button?" and do overlay position with the resulting information without having to reimplement the vagaries of DF's toolbar positioning (especially for small interface widths).

  • Where should it go?
    I just dropped it in internal since it is mostly a "library" module to be used by other code. But there are no other files directly in internal/, so this probably isn't the right place.

    • It could be moved to internal/toolbars/fort.lua?
    • It could be integrated into the gui module?
    • It could be integrated into the gui/dwarfmode module?
    • I haven't really played or watched adventure mode, so I don't know if there are similar mostly-static adventure mode elements that could also be "described" by this library.
  • Do the calculation details need to be RE-verified?
    I tried this code with different window sizes, different interface sizes, and "anchoring" the overlay to different sides. But there could always be some sneaky edge case that I did not recognize or encounter.

  • Is the (latest: 3c325cc) "de-magiced" mass-remove button overlay code clearer?
    Really, this was my ultimate purpose for this module.

    It seems to implement the same behavior that warmdamp allows w.r.t horizontal overlay repositioning, and applies the same (inferred) principle to vertical repositioning.

  • This needs documentation.
    The "API" is pretty small, but a description would go a ways toward making it more accessible.

  • Should existing toolbar-relative overlays be converted to use this?

  • Is the "demo" useful beyond review-time?

    • I put it in there for a quick visual demonstration that the calculated values matched what DF does across all the contexts that reviewers care to explore (window sizes, interface sizes, different active secondary toolbars, etc.).
    • If the library is going to be accepted and merged, maybe the "demo" should also stick around somewhere so that if DF adds new buttons, the update can be quickly visually checked.
  • Would it make sense to add the DFHack-added buttons to the "definitions"?

    • This would let this library serve as a "clearing house" for available button positions.
    • Handling things added outside the bounds of existing toolbars (sitemap, mass-remove) would need a bit of extra handling to keep them from influencing the placement of the toolbars.

@realSquidCoder
Copy link
Contributor

@ChrisJohnsen Here are my thoughts:

* Is this useful?

Yes i think so

* Where should it go?

internal/toolbars/fort.lua would be my pick.

* Do the calculation details need to be RE-verified?

i'll leave this to myk

* This needs documentation.

strongly agreed

* Should existing toolbar-relative overlays be converted to use this?

i think that might be a good idea on an as we find them basis after this pr is merged (unless you already have a list). regardless tho i think thats outside the scope of this PR.

* Is the "demo" useful beyond review-time?

yes it is and as i stated before, i think it should move to devel/toolbar-buttons.lua

* Would it make sense to add the DFHack-added buttons to the "definitions"?

maybe

@ChrisJohnsen
Copy link
Contributor Author

I added a merge with DFHack/scripts:master that keeps this branch's gui/mass-remove.toolbar changes. Hopefully the unrelated merged commits don't interfere with GitHub's review functionality.

@myk002
Copy link
Member

myk002 commented Apr 3, 2025

and kind of matches part of gui.get_interface_rect() (was that re-implemented from reverse engineering?

yes. yes it was ; )

the top-/right-anchored overlay positioning error that the warmdamp button also has

What error does it have? I wasn't aware of any positioning errors for the warmdamp button.

Is this useful?

Yes, I'd rather have this positioning code in one place, even if just for the current use cases, rather than have magic constants sprinkled around

Where should it go?

Pure library code like this should go under https://github.com/DFHack/dfhack/tree/develop/library/lua -- this in particular should probably go under https://github.com/DFHack/dfhack/tree/develop/library/lua/gui . A new module would be fine.

so I don't know if there are similar mostly-static adventure mode elements that could also be "described" by this library

I believe there are, but we can focus on fort mode for now

Do the calculation details need to be RE-verified

Once we migrate the various widgets to using this library, we can play with the window and visually verify that they behave properly. Two DF windows can be brought up simultaneously to verify before/after behavior

This needs documentation

Docs would go in Lua API.rst

-- more later, have to go

@ChrisJohnsen
Copy link
Contributor Author

ChrisJohnsen commented Apr 3, 2025

Thank you for your time!

BTW, due to trying to rush out a preview, I have churned this PR more than I would have liked. I intend to rebase and squash a bunch of this stuff to get rid of some of the intermediate missteps, so except for curiosity, it probably makes sense to mostly review the code from the "files changed" view. Since the library code would need to swap repos anyway, I can squash out my intermediate mess then.

the top-/right-anchored overlay positioning error that the warmdamp button also has

What error does it have? I wasn't aware of any positioning errors for the warmdamp button.

My interpretation of what warmdamp does is that it effectively "reinterprets" the player-configured overlay position (relative to its default position) as "toolbar-relative". Once I started implementing variations on that idea (e.g., intermediate commits in this PR: 70262e0, 3c325cc), I found that the width-only modification that warmdamp does to "reinterpret" its overlay's position as "toolbar-relative" only works (as I inferred its intention) when the overlay is anchored to the left and bottom edges of the interface area (the bottom edge works without extra manipulation since the toolbar never moves relative to the bottom edge of the interface). When anchored to the top or right edges, the result is the normal overlay system interface-edge-anchoring (the overlay doesn't "stick with" the toolbar when the interface is resized).

Maybe this isn't an error. It felt inconsistent, but maybe the top and right edges were kept as "escape hatches" for extra placement flexibility. In 3c325cc I found that making the position toolbar-relative in all four directions meant that the overlay could only be positioned (relative to the toolbar) as far away as it could in a minimum-size interface area; I didn't expect this before trying it, but it does match the current left-anchored behavior of warmdamp. I also tried only applying the "padding" toward the anchoring edges, but that causes very strange jumps in effective button position when the overlay approaches and then touches a non-anchor edge (switching the anchoring edge, thus also the "padded side", when it does touch).

Probably almost no one even bothers to move the warmdamp overlay, and in that case there is no problem. And if someone did move the button, it was probably not moved very far (just to another nearby gap in the dig toolbar); that wouldn't be a problem either. But if someone tried moving it to the far right end of the (advanced) options, the overlay could change its anchor to the right edge (it looks like it takes an interface with more than 236 columns to move the overlay over past the DF dig blueprint buttons without the overlay swapping over to right-edge anchoring). Even then, it would still look right except at different interface/window sizes. Probably many players always use full-screen.

@myk002
Copy link
Member

myk002 commented Apr 3, 2025

Probably almost no one even bothers to move the warmdamp overlay

This brings up an issue that I've become more and more aware of: many of our overlays are not actually repositionable. They have hard-coded behavior that depends on not moving from their default position.

I think this might be a distinction that we need to formalize. The fullscreen and full_interface attributes imply not being repositionable, but perhaps we should replace full_interface=true with repositionable=false and apply that to all the overlays that wouldn't behave well if moved.

@myk002
Copy link
Member

myk002 commented Apr 3, 2025

continuing:

Should existing toolbar-relative overlays be converted to use this

yes, please ; )

Is the "demo" useful beyond review-time?

as you said, it would probably be useful for verifying future updates. It can live as a devel/ script

Would it make sense to add the DFHack-added buttons to the "definitions"?

this would amount to cataloging the positions and dimensions of all DF panels and characterizing how they react to varying window sizes, which would be useful, but also a lot of work. we could try handling some of the current use cases, and then we can put a policy in place for extending this library if/when we need to add more.

@ChrisJohnsen
Copy link
Contributor Author

Okay, here is my tentative plan:

  • new PR to DFHack/dfhack
    • create module at library/lua/gui/toolbar-positions.lua for the toolbar definitions and position calculation code
    • integrate the docs into docs/dev/Lua API.rst
    • create tests at test/library/gui/toolbar-positions.lua
      • "pins down" the calculated values so future code tweaks can verify that unintended changes are not introduced
    • docs/changelog.txt entry under Lua
  • new PR to DFHack/scripts for devel/toolbar-positions.lua to hold the "demo" code
  • new PR to DFHack/scripts to change existing toolbar-relative overlays to use the library code; warmdamp, civ-alert?, sitemap, mass-remove, design are the ones I already know of, I'll check for more

This PR could be recycled for one or both of those last two, if you think that makes sense.

Any thoughts, or preferences for the name of the module? As named above, the module would be gui.toolbar-positions. There aren't any compound filenames in library/lua/gui, would that stick out too much? Thoughts on dash vs. underscore (I've see both in various places)? Would the simpler gui.toolbars be better?

@myk002
Copy link
Member

myk002 commented Apr 5, 2025

sgtm

calling it gui.toolbar-positions might be too narrow scoped. how about gui.df-layout (gui.dflayout?)? then we can add to it later to characterize more of the vanilla UI behavior. Dashes are fine -- there are some other modules with dashes in them (e.g. repeat-util, script-manager, etc.) -- but no dashes at all might be better.

you can reuse this PR for the demo script

@ChrisJohnsen ChrisJohnsen changed the title Data-driven DF toolbar position info; and use it in gui/mass-remove.toolbar Demonstrate gui.dflayout library and use it in gui/mass-remove.toolbar Apr 7, 2025
@ChrisJohnsen
Copy link
Contributor Author

This has been respun to use DFHack/dfhack#5385.

@myk002 myk002 added this to 51.11-r2 Apr 12, 2025
@github-project-automation github-project-automation bot moved this to Todo in 51.11-r2 Apr 12, 2025
@myk002 myk002 moved this from Todo to Review In Progress in 51.11-r2 Apr 13, 2025
Comment on lines 413 to 424
local function mass_remove_button_offsets(interface_size)
local erase_frame = erase_toolbar.frame(interface_size)
return {
l = erase_frame.l + erase_frame.w + 1,
w = erase_frame.w,
r = erase_frame.r - 1 - MR_BUTTON_WIDTH,

t = erase_frame.t,
h = erase_frame.h,
b = erase_frame.b,
}
end
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit torn by this. I like the idea of consolidating the logic for vanilla UI layout, but this function is neither shorter nor clearer than the status quo.

What would be nice is an API that would take a spec and return values that we can use directly.

For example, would it be possible to write:

local spec = { ... }
...
default_pos=dflayout.getOverlayDefaultPos(spec),
frame=dflayout.getOverlayFrame(spec),
...
MassRemoveToolbarOverlay.preUpdateLayout = dflayout.getPreUpdateLayoutFn(spec)

spec would possibly contain a reference to some UI element and a frame specifier to size/position the widget relative to that UI element.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, something like that would be nicer. I'll see what I can come up with for this. I suspect there might be some extra complexity here to fully support different kinds of alignment/offsetting…

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updates to DFHack/dfhack#5385 and this are up now.

It currently looks like this:

local PLACEMENT = dflayout.getOverlayPlacementInfo{
    size = { w = 20, h = 1 },
    ui_element = dflayout.elements.fort.secondary_toolbar_buttons.DIG.DIG_DIG,
    h_placement = 'align left edges',
    v_placement = 'above',
}
...
    default_pos=PLACEMENT.default_pos
...
TheOverlay.preUpdateLayout = PLACEMENT.preUpdateLayout_fn

Having one function seemed simpler (there is a bit of shared calculation between default_fn and preUpdateLayout_fn), but it could be multiple functions if you think that would be better.

frame is also available, but doesn't seem to really be needed for overlay widgets: OverlayWidget establishes an initial frame, and preUpdateLayout_fn will update it.

It also supports off-nominal default_pos values (like gui/mass-remove.toolbar here).


The toolbar UI elements (the only ones currently supported) all have static sizes and non-decreasing "insets" (from the interface area edges). The "placement" implementation works inside these limitations, but I would like to see if it can be generalized a bit to support variable-size UI elements (e.g., the rows of DF info window, as an aggregate UI element) and those without non-decreasing insets (e.g., the bottom inset of the last row of in DF info, which varies up and down as the interface area height changes (mod 3)).

I'll start experimenting with that next. It seems like something that might be worth having in the "final" initial implementation even if it doesn't include "definitions" for any such UI elements. The documentation encourages treating the "UI element" values as opaque to try to leave room for possible changes needed to support cases like these.

Linking can be made to work, but would require
- a ref declaration (label) in the Lua API docs, and
- the gui.dflayout section to be present in the scripts CI's view (it is
  still in a PR, so the scripts CI does not currently see it).

Just use normal code formatting.
Makes explicit the fields required from each "demo".

Remove demo_available(), just use demo.available() directly. ("Always
available if .available is nil" seems a bit awkward)
The interface percentage was not being saved, so it was constantly
initiating updates.

Once the spurious updates were fixed, it uncovered broken update
processing when activating a demo and when the fort toolbar demo was
told to update itself.
Have the toolbar demos set their own frames via computeFrame.

These overridden computeFrames also allow the button extent indicator
strings to draw over the edges of the Panel's border. The left, right,
and secondary toolbars don't have their own "borders" like the center
toolbar, so the first and last buttons in those toolbars previously had
their button indicators cut off by the Panel border.

Certainly not a good look for general use, but for a devel inspection
tool, it should be okay (and leaves the Panel borders showing the true
extent of each toolbar).

Only compute the button strings once for the static left, center, and
right toolbars.
Use hasFocus instead of "not defocused". hasFocus also checks that the
ZScreen is the topmost.

Check demo availability before updating from main window.
Using a local was causing it to revert and get "stuck on" when
re-running the script.
Previous code missed interface percentage updates when the ZScreen was
focused but not visible_when_not_focused.
Now they are right above the final "raise or create" sequence.
Keeps the secondary toolbar demo from needing to hook into the center
toolbar (or some other "always on" widget) to notice changes in the
secondary toolbar.
devel/dflayout now highlights the "demo" element when the mouse cursor
is over the (computed) real UI element
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Review In Progress
Development

Successfully merging this pull request may close these issues.

3 participants