xref: /freebsd-14.2/stand/lua/drawer.lua (revision 12eaa305)
1--
2-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3--
4-- Copyright (c) 2015 Pedro Souza <[email protected]>
5-- Copyright (c) 2018 Kyle Evans <[email protected]>
6-- All rights reserved.
7--
8-- Redistribution and use in source and binary forms, with or without
9-- modification, are permitted provided that the following conditions
10-- are met:
11-- 1. Redistributions of source code must retain the above copyright
12--    notice, this list of conditions and the following disclaimer.
13-- 2. Redistributions in binary form must reproduce the above copyright
14--    notice, this list of conditions and the following disclaimer in the
15--    documentation and/or other materials provided with the distribution.
16--
17-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27-- SUCH DAMAGE.
28--
29-- $FreeBSD$
30--
31
32local color = require("color")
33local config = require("config")
34local core = require("core")
35local screen = require("screen")
36
37local drawer = {}
38
39local fbsd_brand
40local none
41
42local function menuEntryName(drawing_menu, entry)
43	local name_handler = drawer.menu_name_handlers[entry.entry_type]
44
45	if name_handler ~= nil then
46		return name_handler(drawing_menu, entry)
47	end
48	if type(entry.name) == "function" then
49		return entry.name()
50	end
51	return entry.name
52end
53
54local function getBranddef(brand)
55	if brand == nil then
56		return nil
57	end
58	-- Look it up
59	local branddef = drawer.branddefs[brand]
60
61	-- Try to pull it in
62	if branddef == nil then
63		try_include('brand-' .. brand)
64		branddef = drawer.branddefs[brand]
65	end
66
67	return branddef
68end
69
70local function getLogodef(logo)
71	if logo == nil then
72		return nil
73	end
74	-- Look it up
75	local logodef = drawer.logodefs[logo]
76
77	-- Try to pull it in
78	if logodef == nil then
79		try_include('logo-' .. logo)
80		logodef = drawer.logodefs[logo]
81	end
82
83	return logodef
84end
85
86local function draw(x, y, logo)
87	for i = 1, #logo do
88		screen.setcursor(x, y + i - 1)
89		printc(logo[i])
90	end
91end
92
93local function drawmenu(menudef)
94	local x = drawer.menu_position.x
95	local y = drawer.menu_position.y
96
97	x = x + drawer.shift.x
98	y = y + drawer.shift.y
99
100	-- print the menu and build the alias table
101	local alias_table = {}
102	local entry_num = 0
103	local menu_entries = menudef.entries
104	local effective_line_num = 0
105	if type(menu_entries) == "function" then
106		menu_entries = menu_entries()
107	end
108	for _, e in ipairs(menu_entries) do
109		-- Allow menu items to be conditionally visible by specifying
110		-- a visible function.
111		if e.visible ~= nil and not e.visible() then
112			goto continue
113		end
114		effective_line_num = effective_line_num + 1
115		if e.entry_type ~= core.MENU_SEPARATOR then
116			entry_num = entry_num + 1
117			screen.setcursor(x, y + effective_line_num)
118
119			printc(entry_num .. ". " .. menuEntryName(menudef, e))
120
121			-- fill the alias table
122			alias_table[tostring(entry_num)] = e
123			if e.alias ~= nil then
124				for _, a in ipairs(e.alias) do
125					alias_table[a] = e
126				end
127			end
128		else
129			screen.setcursor(x, y + effective_line_num)
130			printc(menuEntryName(menudef, e))
131		end
132		::continue::
133	end
134	return alias_table
135end
136
137local function drawbox()
138	local x = drawer.menu_position.x - 3
139	local y = drawer.menu_position.y - 1
140	local w = drawer.frame_size.w
141	local h = drawer.frame_size.h
142
143	local framestyle = loader.getenv("loader_menu_frame") or "double"
144	local framespec = drawer.frame_styles[framestyle]
145	-- If we don't have a framespec for the current frame style, just don't
146	-- draw a box.
147	if framespec == nil then
148		return
149	end
150
151	local hl = framespec.horizontal
152	local vl = framespec.vertical
153
154	local tl = framespec.top_left
155	local bl = framespec.bottom_left
156	local tr = framespec.top_right
157	local br = framespec.bottom_right
158
159	x = x + drawer.shift.x
160	y = y + drawer.shift.y
161
162	screen.setcursor(x, y); printc(tl)
163	screen.setcursor(x, y + h); printc(bl)
164	screen.setcursor(x + w, y); printc(tr)
165	screen.setcursor(x + w, y + h); printc(br)
166
167	screen.setcursor(x + 1, y)
168	for _ = 1, w - 1 do
169		printc(hl)
170	end
171
172	screen.setcursor(x + 1, y + h)
173	for _ = 1, w - 1 do
174		printc(hl)
175	end
176
177	for i = 1, h - 1 do
178		screen.setcursor(x, y + i)
179		printc(vl)
180		screen.setcursor(x + w, y + i)
181		printc(vl)
182	end
183
184	local menu_header = loader.getenv("loader_menu_title") or
185	    "Welcome to FreeBSD"
186	local menu_header_align = loader.getenv("loader_menu_title_align")
187	local menu_header_x
188
189	if menu_header_align ~= nil then
190		menu_header_align = menu_header_align:lower()
191		if menu_header_align == "left" then
192			-- Just inside the left border on top
193			menu_header_x = x + 1
194		elseif menu_header_align == "right" then
195			-- Just inside the right border on top
196			menu_header_x = x + w - #menu_header
197		end
198	end
199	if menu_header_x == nil then
200		menu_header_x = x + (w / 2) - (#menu_header / 2)
201	end
202	screen.setcursor(menu_header_x, y)
203	printc(menu_header)
204end
205
206local function drawbrand()
207	local x = tonumber(loader.getenv("loader_brand_x")) or
208	    drawer.brand_position.x
209	local y = tonumber(loader.getenv("loader_brand_y")) or
210	    drawer.brand_position.y
211
212	local branddef = getBranddef(loader.getenv("loader_brand"))
213
214	if branddef == nil then
215		branddef = getBranddef(drawer.default_brand)
216	end
217
218	local graphic = branddef.graphic
219
220	x = x + drawer.shift.x
221	y = y + drawer.shift.y
222	draw(x, y, graphic)
223end
224
225local function drawlogo()
226	local x = tonumber(loader.getenv("loader_logo_x")) or
227	    drawer.logo_position.x
228	local y = tonumber(loader.getenv("loader_logo_y")) or
229	    drawer.logo_position.y
230
231	local logo = loader.getenv("loader_logo")
232	local colored = color.isEnabled()
233
234	local logodef = getLogodef(logo)
235
236	if logodef == nil or logodef.graphic == nil or
237	    (not colored and logodef.requires_color) then
238		-- Choose a sensible default
239		if colored then
240			logodef = getLogodef("orb")
241		else
242			logodef = getLogodef("orbbw")
243		end
244	end
245
246	if logodef ~= nil and logodef.graphic == none then
247		drawer.shift = logodef.shift
248	else
249		drawer.shift = drawer.default_shift
250	end
251
252	x = x + drawer.shift.x
253	y = y + drawer.shift.y
254
255	if logodef ~= nil and logodef.shift ~= nil then
256		x = x + logodef.shift.x
257		y = y + logodef.shift.y
258	end
259
260	draw(x, y, logodef.graphic)
261end
262
263fbsd_brand = {
264"  ______               ____   _____ _____  ",
265" |  ____|             |  _ \\ / ____|  __ \\ ",
266" | |___ _ __ ___  ___ | |_) | (___ | |  | |",
267" |  ___| '__/ _ \\/ _ \\|  _ < \\___ \\| |  | |",
268" | |   | | |  __/  __/| |_) |____) | |__| |",
269" | |   | | |    |    ||     |      |      |",
270" |_|   |_|  \\___|\\___||____/|_____/|_____/ "
271}
272none = {""}
273
274-- Module exports
275drawer.default_brand = 'fbsd'
276
277drawer.menu_name_handlers = {
278	-- Menu name handlers should take the menu being drawn and entry being
279	-- drawn as parameters, and return the name of the item.
280	-- This is designed so that everything, including menu separators, may
281	-- have their names derived differently. The default action for entry
282	-- types not specified here is to use entry.name directly.
283	[core.MENU_SEPARATOR] = function(_, entry)
284		if entry.name ~= nil then
285			if type(entry.name) == "function" then
286				return entry.name()
287			end
288			return entry.name
289		end
290		return ""
291	end,
292	[core.MENU_CAROUSEL_ENTRY] = function(_, entry)
293		local carid = entry.carousel_id
294		local caridx = config.getCarouselIndex(carid)
295		local choices = entry.items
296		if type(choices) == "function" then
297			choices = choices()
298		end
299		if #choices < caridx then
300			caridx = 1
301		end
302		return entry.name(caridx, choices[caridx], choices)
303	end,
304}
305
306drawer.brand_position = {x = 2, y = 1}
307drawer.logo_position = {x = 46, y = 4}
308drawer.menu_position = {x = 5, y = 10}
309drawer.frame_size = {w = 42, h = 13}
310drawer.default_shift = {x = 0, y = 0}
311drawer.shift = drawer.default_shift
312
313drawer.branddefs = {
314	-- Indexed by valid values for loader_brand in loader.conf(5). Valid
315	-- keys are: graphic (table depicting graphic)
316	["fbsd"] = {
317		graphic = fbsd_brand,
318	},
319	["none"] = {
320		graphic = none,
321	},
322}
323
324function drawer.addBrand(name, def)
325	drawer.branddefs[name] = def
326end
327
328function drawer.addLogo(name, def)
329	drawer.logodefs[name] = def
330end
331
332drawer.logodefs = {
333	-- Indexed by valid values for loader_logo in loader.conf(5). Valid keys
334	-- are: requires_color (boolean), graphic (table depicting graphic), and
335	-- shift (table containing x and y).
336	["tribute"] = {
337		graphic = fbsd_brand,
338	},
339	["tributebw"] = {
340		graphic = fbsd_brand,
341	},
342	["none"] = {
343		graphic = none,
344		shift = {x = 17, y = 0},
345	},
346}
347
348drawer.frame_styles = {
349	-- Indexed by valid values for loader_menu_frame in loader.conf(5).
350	-- All of the keys appearing below must be set for any menu frame style
351	-- added to drawer.frame_styles.
352	["ascii"] = {
353		horizontal	= "-",
354		vertical	= "|",
355		top_left	= "+",
356		bottom_left	= "+",
357		top_right	= "+",
358		bottom_right	= "+",
359	},
360	["single"] = {
361		horizontal	= "\xC4",
362		vertical	= "\xB3",
363		top_left	= "\xDA",
364		bottom_left	= "\xC0",
365		top_right	= "\xBF",
366		bottom_right	= "\xD9",
367	},
368	["double"] = {
369		horizontal	= "\xCD",
370		vertical	= "\xBA",
371		top_left	= "\xC9",
372		bottom_left	= "\xC8",
373		top_right	= "\xBB",
374		bottom_right	= "\xBC",
375	},
376}
377
378function drawer.drawscreen(menudef)
379	-- drawlogo() must go first.
380	-- it determines the positions of other elements
381	drawlogo()
382	drawbrand()
383	drawbox()
384	return drawmenu(menudef)
385end
386
387return drawer
388