SettingsHelp.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
-- 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