feat: UI LAYOUT option, centered by default

Edge docking and zoom-linked UI scaling shipped as unconditional
behaviour. Both are departures from how the port composed the screen, so
they become a setting instead: UI LAYOUT = CENTERED (the default) or
DYNAMIC.

CENTERED is a fixed letterbox. Elements stay where they were drawn in the
160x144 canvas, and the UI does not follow the survey zoom, so screen
furniture neither moves nor resizes under the player. That is what the
pre-anchoring builds did. DYNAMIC is the current behaviour, unchanged.

Both halves matter together: gating only the anchoring would stop the
dialogue box moving but leave it resizing with the zoom, which is the same
complaint in a different form.

Gated at Renderer:setUIAnchor and Renderer:uiScale rather than at each
caller, so one switch covers the dialogue box, its YES/NO, the START menu
and anything anchored later, and no caller knows the option exists.
Game.dynamicUI answers true only for an explicit "dynamic", so a save
written before this keeps the layout it already had.

Independent of it, deliberately: BATTLE SIZE still works under either mode
(uiFill overrides the scale later, in endFrame), and a battle still holds
its own prompts inside its screen under DYNAMIC.

Also includes the Oak intro fix (previously #674): the speech fills white
over the UI canvas while its dialogue box docks to the window edge, so
under DYNAMIC black showed between the two. letterboxWhite closes it, and
the shrink beat's replica box rides the same anchor as the real box it
stands in for.
This commit is contained in:
spiritsnails
2026-08-02 10:47:27 -06:00
parent 0f45bb5792
commit fe7dcf33ec
8 changed files with 319 additions and 0 deletions
+19
View File
@@ -20,6 +20,16 @@ local OakSpeech = {}
OakSpeech.__index = OakSpeech
OakSpeech.isOpaque = true
-- The speech is a white field with a pic on it, and its dialogue box docks to
-- the WINDOW's bottom edge (Renderer:setUIAnchor, via TextBox). The white it
-- fills below is only the 160x144 UI canvas, so once the box moved to the
-- window edge the two stopped touching: black letterbox showed between the
-- bottom of Oak's white and the top of the box he is speaking from. Filling
-- the voids with the paper shade -- the same opt-in a battle uses -- puts the
-- box back on the field. Not a literal 1,1,1: the canvas is colorized, so
-- endFrame matches it with PaletteFX.paperShade.
OakSpeech.letterboxWhite = true
-- FadeInIntroPic runs a 6-step palette fade; MovePicLeft wipes the mon
-- sprite in from the right. Both play out before the beat's text prints.
local FADE_FRAMES = 24
@@ -611,6 +621,15 @@ function OakSpeech:draw()
love.graphics.draw(self.walkSheet, self.walkQuad, 64, 60)
end
if self.shrinkText then
-- This is a REPLICA of the dialogue box that just closed, redrawn at
-- TextBox's own rect (BOX_TX..BOX_TH = 0,12,20,6) so the last page holds
-- while the pic shrinks. The real box rides the bottom anchor, so this
-- one has to as well -- otherwise the text visibly jumps up a letterbox
-- on the frame the real box is swapped for this copy.
local r = self.game and self.game.renderer
if r and r.setUIAnchor then
r:setUIAnchor(0, 12 * 8, 20 * 8, 6 * 8, "bottom")
end
Font.drawBox(0, 12, 20, 6)
love.graphics.setColor(0, 0, 0, 1)
for i, line in ipairs(self.shrinkText) do