mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 09:00:51 +02:00
179 lines
6.9 KiB
Lua
179 lines
6.9 KiB
Lua
-- SELECT on a row explains what it does.
|
|
--
|
|
-- ------- why this exists at all
|
|
--
|
|
-- Every setting in this mod has ALWAYS carried a paragraph of help. It goes
|
|
-- into the schema handed to the mod manager (ModSetting:schema takes it), it
|
|
-- has been written and kept up to date beside every row in main.lua's
|
|
-- SETTINGS -- and nothing in the engine has ever drawn it. Not the OPTIONS
|
|
-- menu, whose row is a label and a value and has no room for a third thing;
|
|
-- not the mod manager's own page, which renders the same two lines. It was
|
|
-- authored, structured, accurate prose sitting in a field with no reader.
|
|
--
|
|
-- So it gets one. A row on this mod's menus says what it IS on one line and
|
|
-- what it is SET TO on the next, and SELECT says what that means -- which is
|
|
-- the question a row like RENDER DIST or 2D-3D B cannot answer in eighteen
|
|
-- characters however the label is worded.
|
|
--
|
|
-- SELECT rather than a button that already does something: A steps a setting,
|
|
-- B leaves, and the d-pad moves. SELECT is free on a menu -- the mod's own
|
|
-- SELECT hotkey is installed on OverworldController:handleInput, which only
|
|
-- runs while the overworld is the top state, so a menu can have the button
|
|
-- without taking anything from the map.
|
|
--
|
|
-- ------- the shape of it
|
|
--
|
|
-- The game's own dialogue box: drawn with Font.drawBox, so the border is the
|
|
-- ROM's own glyphs and a mod-supplied font theme retextures this along with
|
|
-- everything else (Font.BORDER) -- and anchored to the BOTTOM of the screen
|
|
-- with the menu still visible above it, which is where this game has put
|
|
-- every line of text anybody has ever read in it.
|
|
--
|
|
-- Sized to what it holds rather than to the screen. Each description is one
|
|
-- sentence, so most of these are five or six tiles tall and the row being
|
|
-- asked about is still on screen over the top of the box. A sentence long
|
|
-- enough to overflow scrolls instead of growing past MAX_LINES, a line at a
|
|
-- time on the d-pad -- which is a fallback, not the design: the answer to a
|
|
-- description that needs scrolling is a shorter description.
|
|
|
|
-- the mod namespace (see main.lua)
|
|
local V = ...
|
|
|
|
local Font = require("src.render.Font")
|
|
local Theme = require("src.ui.Theme")
|
|
local PaletteFX = require("src.render.PaletteFX")
|
|
|
|
local SettingsHelp = {}
|
|
SettingsHelp.__index = SettingsHelp
|
|
|
|
-- NOT opaque: the menu stays drawn underneath, so the row being asked about
|
|
-- is still on screen above the box. That is most of why the box is only as
|
|
-- tall as it needs to be.
|
|
SettingsHelp.isOpaque = false
|
|
|
|
-- The box spans the screen's twenty tiles and its border owns the outer ring,
|
|
-- so text runs from tile 1. Seventeen columns rather than eighteen: tile 18
|
|
-- is kept clear for the more-arrow, which would otherwise land on top of the
|
|
-- last character of any line that filled the width.
|
|
local COLS = 17
|
|
local PEN_X = 8
|
|
local SCREEN_ROWS = 18
|
|
-- title, plus the body, plus the two border rows
|
|
local CHROME_ROWS = 3
|
|
-- A sentence needing more than this scrolls. Eight lines of seventeen is 136
|
|
-- characters, which is a long sentence and a box two thirds up the screen.
|
|
local MAX_LINES = 8
|
|
|
|
-- Break a string into lines that fit, on word boundaries. Unbounded, unlike
|
|
-- StadiumScreen's -- that one is capping a save path to what a fixed plate can
|
|
-- show, and this one is the whole point of the screen.
|
|
local function wrapped(str, cols)
|
|
cols = cols or COLS
|
|
local lines, line = {}, nil
|
|
for word in tostring(str or ""):gmatch("%S+") do
|
|
local try = line and (line .. " " .. word) or word
|
|
if #try <= cols then
|
|
line = try
|
|
else
|
|
if line then lines[#lines + 1] = line end
|
|
-- a word longer than the line is broken across lines rather than cut;
|
|
-- nothing in the help text is that long today, but losing the end of a
|
|
-- sentence silently is not a failure mode worth leaving open
|
|
while #word > cols do
|
|
lines[#lines + 1] = word:sub(1, cols)
|
|
word = word:sub(cols + 1)
|
|
end
|
|
line = word
|
|
end
|
|
end
|
|
if line then lines[#lines + 1] = line end
|
|
return lines
|
|
end
|
|
|
|
SettingsHelp.wrapped = wrapped
|
|
|
|
function SettingsHelp.new(game, title, body)
|
|
return setmetatable({
|
|
game = game,
|
|
title = tostring(title or ""):gsub("%.%.$", ""),
|
|
lines = wrapped(body),
|
|
top = 0,
|
|
}, SettingsHelp)
|
|
end
|
|
|
|
-- How many body lines this box shows: all of them, unless there are more than
|
|
-- a box is allowed to be tall.
|
|
function SettingsHelp:bodyRows()
|
|
return math.min(#self.lines, MAX_LINES)
|
|
end
|
|
|
|
function SettingsHelp:maxTop()
|
|
return math.max(0, #self.lines - self:bodyRows())
|
|
end
|
|
|
|
-- Every button that could mean "done" closes it, including SELECT itself --
|
|
-- the press that opened the box is the one a player is most likely to reach
|
|
-- for to get rid of it. A is in there too: it steps a setting everywhere else
|
|
-- on these menus, and stepping one you cannot see would be worse than an
|
|
-- extra way out.
|
|
local DISMISS = { "a", "b", "start", "select" }
|
|
|
|
function SettingsHelp:update()
|
|
local input = self.game and self.game.input
|
|
if not input then return end
|
|
local maxTop = self:maxTop()
|
|
-- the d-pad only does anything when there is something below the fold; a
|
|
-- box showing its whole sentence has nowhere to go and says so by not
|
|
-- moving
|
|
if input:wasPressed("down") then
|
|
self.top = math.min(maxTop, self.top + 1)
|
|
return
|
|
elseif input:wasPressed("up") then
|
|
self.top = math.max(0, self.top - 1)
|
|
return
|
|
end
|
|
for _, btn in ipairs(DISMISS) do
|
|
if input:wasPressed(btn) then
|
|
local stack = self.game.stack
|
|
if self.game.data then
|
|
require("src.core.Sound").play(self.game.data, "Press_AB")
|
|
end
|
|
if stack and stack:top() == self then stack:pop() end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
function SettingsHelp:draw()
|
|
local body = self:bodyRows()
|
|
local th = body + CHROME_ROWS
|
|
local ty = SCREEN_ROWS - th -- anchored to the bottom of the screen
|
|
Font.drawBox(0, ty, 20, th)
|
|
love.graphics.setColor(0, 0, 0, 1)
|
|
-- the row's own name, so the box says what it is about even where it covers
|
|
-- the row that was asked
|
|
Font.draw(self.title, PEN_X, (ty + 1) * 8)
|
|
for i = 1, body do
|
|
local line = self.lines[self.top + i]
|
|
if not line then break end
|
|
Font.draw(line, PEN_X, (ty + 1 + i) * 8)
|
|
end
|
|
-- the same marker the options list uses for "there is more below this", so
|
|
-- it means here what it means there
|
|
if self.top < self:maxTop() then
|
|
Font.drawCode(Theme.moreArrow, 144, (ty + th - 2) * 8)
|
|
end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
-- Game:draw stops at the first state that HAS this method, so without one the
|
|
-- box would inherit whatever is underneath -- which is a menu of ours, whose
|
|
-- answer happens to be right. Stated anyway: the reason that answer is right
|
|
-- is not a property of this screen, and a future menu that paints something
|
|
-- of its own would silently repaint this box with it.
|
|
function SettingsHelp:sgbPalettes(game)
|
|
return PaletteFX.wholeNamed(game.data, "MEWMON")
|
|
end
|
|
|
|
return SettingsHelp
|