xref: /freebsd/stand/lua/drawer.lua (revision d3d381b2b194b4d24853e92eecef55f262688d1a)
1--
2-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3--
4-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5-- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
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 menu_name_handlers
43local branddefs
44local logodefs
45local brand_position
46local logo_position
47local menu_position
48local frame_size
49local default_shift
50local shift
51
52local function menuEntryName(drawing_menu, entry)
53	local name_handler = menu_name_handlers[entry.entry_type]
54
55	if name_handler ~= nil then
56		return name_handler(drawing_menu, entry)
57	end
58	if type(entry.name) == "function" then
59		return entry.name()
60	end
61	return entry.name
62end
63
64local function getBranddef(brand)
65	if brand == nil then
66		return nil
67	end
68	-- Look it up
69	local branddef = branddefs[brand]
70
71	-- Try to pull it in
72	if branddef == nil then
73		try_include('brand-' .. brand)
74		branddef = branddefs[brand]
75	end
76
77	return branddef
78end
79
80local function getLogodef(logo)
81	if logo == nil then
82		return nil
83	end
84	-- Look it up
85	local logodef = logodefs[logo]
86
87	-- Try to pull it in
88	if logodef == nil then
89		try_include('logo-' .. logo)
90		logodef = logodefs[logo]
91	end
92
93	return logodef
94end
95
96local function draw(x, y, logo)
97	for i = 1, #logo do
98		screen.setcursor(x, y + i - 1)
99		printc(logo[i])
100	end
101end
102
103local function drawmenu(menudef)
104	local x = menu_position.x
105	local y = menu_position.y
106
107	x = x + shift.x
108	y = y + shift.y
109
110	-- print the menu and build the alias table
111	local alias_table = {}
112	local entry_num = 0
113	local menu_entries = menudef.entries
114	local effective_line_num = 0
115	if type(menu_entries) == "function" then
116		menu_entries = menu_entries()
117	end
118	for _, e in ipairs(menu_entries) do
119		-- Allow menu items to be conditionally visible by specifying
120		-- a visible function.
121		if e.visible ~= nil and not e.visible() then
122			goto continue
123		end
124		effective_line_num = effective_line_num + 1
125		if e.entry_type ~= core.MENU_SEPARATOR then
126			entry_num = entry_num + 1
127			screen.setcursor(x, y + effective_line_num)
128
129			printc(entry_num .. ". " .. menuEntryName(menudef, e))
130
131			-- fill the alias table
132			alias_table[tostring(entry_num)] = e
133			if e.alias ~= nil then
134				for _, a in ipairs(e.alias) do
135					alias_table[a] = e
136				end
137			end
138		else
139			screen.setcursor(x, y + effective_line_num)
140			printc(menuEntryName(menudef, e))
141		end
142		::continue::
143	end
144	return alias_table
145end
146
147local function drawbox()
148	local x = menu_position.x - 3
149	local y = menu_position.y - 1
150	local w = frame_size.w
151	local h = frame_size.h
152
153	local framestyle = loader.getenv("loader_menu_frame") or "double"
154	local framespec = drawer.frame_styles[framestyle]
155	-- If we don't have a framespec for the current frame style, just don't
156	-- draw a box.
157	if framespec == nil then
158		return
159	end
160
161	local hl = framespec.horizontal
162	local vl = framespec.vertical
163
164	local tl = framespec.top_left
165	local bl = framespec.bottom_left
166	local tr = framespec.top_right
167	local br = framespec.bottom_right
168
169	x = x + shift.x
170	y = y + shift.y
171
172	screen.setcursor(x, y); printc(tl)
173	screen.setcursor(x, y + h); printc(bl)
174	screen.setcursor(x + w, y); printc(tr)
175	screen.setcursor(x + w, y + h); printc(br)
176
177	screen.setcursor(x + 1, y)
178	for _ = 1, w - 1 do
179		printc(hl)
180	end
181
182	screen.setcursor(x + 1, y + h)
183	for _ = 1, w - 1 do
184		printc(hl)
185	end
186
187	for i = 1, h - 1 do
188		screen.setcursor(x, y + i)
189		printc(vl)
190		screen.setcursor(x + w, y + i)
191		printc(vl)
192	end
193
194	local menu_header = loader.getenv("loader_menu_title") or
195	    "Welcome to FreeBSD"
196	local menu_header_align = loader.getenv("loader_menu_title_align")
197	local menu_header_x
198
199	if menu_header_align ~= nil then
200		menu_header_align = menu_header_align:lower()
201		if menu_header_align == "left" then
202			-- Just inside the left border on top
203			menu_header_x = x + 1
204		elseif menu_header_align == "right" then
205			-- Just inside the right border on top
206			menu_header_x = x + w - #menu_header
207		end
208	end
209	if menu_header_x == nil then
210		menu_header_x = x + (w / 2) - (#menu_header / 2)
211	end
212	screen.setcursor(menu_header_x, y)
213	printc(menu_header)
214end
215
216local function drawbrand()
217	local x = tonumber(loader.getenv("loader_brand_x")) or
218	    brand_position.x
219	local y = tonumber(loader.getenv("loader_brand_y")) or
220	    brand_position.y
221
222	local branddef = getBranddef(loader.getenv("loader_brand"))
223
224	if branddef == nil then
225		branddef = getBranddef(drawer.default_brand)
226	end
227
228	local graphic = branddef.graphic
229
230	x = x + shift.x
231	y = y + shift.y
232	draw(x, y, graphic)
233end
234
235local function drawlogo()
236	local x = tonumber(loader.getenv("loader_logo_x")) or
237	    logo_position.x
238	local y = tonumber(loader.getenv("loader_logo_y")) or
239	    logo_position.y
240
241	local logo = loader.getenv("loader_logo")
242	local colored = color.isEnabled()
243
244	local logodef = getLogodef(logo)
245
246	if logodef == nil or logodef.graphic == nil or
247	    (not colored and logodef.requires_color) then
248		-- Choose a sensible default
249		if colored then
250			logodef = getLogodef(drawer.default_color_logodef)
251		else
252			logodef = getLogodef(drawer.default_bw_logodef)
253		end
254	end
255
256	if logodef ~= nil and logodef.graphic == none then
257		shift = logodef.shift
258	else
259		shift = default_shift
260	end
261
262	x = x + shift.x
263	y = y + shift.y
264
265	if logodef ~= nil and logodef.shift ~= nil then
266		x = x + logodef.shift.x
267		y = y + logodef.shift.y
268	end
269
270	draw(x, y, logodef.graphic)
271end
272
273fbsd_brand = {
274"  ______               ____   _____ _____  ",
275" |  ____|             |  _ \\ / ____|  __ \\ ",
276" | |___ _ __ ___  ___ | |_) | (___ | |  | |",
277" |  ___| '__/ _ \\/ _ \\|  _ < \\___ \\| |  | |",
278" | |   | | |  __/  __/| |_) |____) | |__| |",
279" | |   | | |    |    ||     |      |      |",
280" |_|   |_|  \\___|\\___||____/|_____/|_____/ "
281}
282none = {""}
283
284menu_name_handlers = {
285	-- Menu name handlers should take the menu being drawn and entry being
286	-- drawn as parameters, and return the name of the item.
287	-- This is designed so that everything, including menu separators, may
288	-- have their names derived differently. The default action for entry
289	-- types not specified here is to use entry.name directly.
290	[core.MENU_SEPARATOR] = function(_, entry)
291		if entry.name ~= nil then
292			if type(entry.name) == "function" then
293				return entry.name()
294			end
295			return entry.name
296		end
297		return ""
298	end,
299	[core.MENU_CAROUSEL_ENTRY] = function(_, entry)
300		local carid = entry.carousel_id
301		local caridx = config.getCarouselIndex(carid)
302		local choices = entry.items
303		if type(choices) == "function" then
304			choices = choices()
305		end
306		if #choices < caridx then
307			caridx = 1
308		end
309		return entry.name(caridx, choices[caridx], choices)
310	end,
311}
312
313branddefs = {
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
324logodefs = {
325	-- Indexed by valid values for loader_logo in loader.conf(5). Valid keys
326	-- are: requires_color (boolean), graphic (table depicting graphic), and
327	-- shift (table containing x and y).
328	["tribute"] = {
329		graphic = fbsd_brand,
330	},
331	["tributebw"] = {
332		graphic = fbsd_brand,
333	},
334	["none"] = {
335		graphic = none,
336		shift = {x = 17, y = 0},
337	},
338}
339
340brand_position = {x = 2, y = 1}
341logo_position = {x = 46, y = 4}
342menu_position = {x = 5, y = 10}
343frame_size = {w = 42, h = 13}
344default_shift = {x = 0, y = 0}
345shift = default_shift
346
347-- Module exports
348drawer.default_brand = 'fbsd'
349drawer.default_color_logodef = 'orb'
350drawer.default_bw_logodef = 'orbbw'
351
352function drawer.addBrand(name, def)
353	branddefs[name] = def
354end
355
356function drawer.addLogo(name, def)
357	logodefs[name] = def
358end
359
360drawer.frame_styles = {
361	-- Indexed by valid values for loader_menu_frame in loader.conf(5).
362	-- All of the keys appearing below must be set for any menu frame style
363	-- added to drawer.frame_styles.
364	["ascii"] = {
365		horizontal	= "-",
366		vertical	= "|",
367		top_left	= "+",
368		bottom_left	= "+",
369		top_right	= "+",
370		bottom_right	= "+",
371	},
372	["single"] = {
373		horizontal	= "\xC4",
374		vertical	= "\xB3",
375		top_left	= "\xDA",
376		bottom_left	= "\xC0",
377		top_right	= "\xBF",
378		bottom_right	= "\xD9",
379	},
380	["double"] = {
381		horizontal	= "\xCD",
382		vertical	= "\xBA",
383		top_left	= "\xC9",
384		bottom_left	= "\xC8",
385		top_right	= "\xBB",
386		bottom_right	= "\xBC",
387	},
388}
389
390function drawer.drawscreen(menudef)
391	-- drawlogo() must go first.
392	-- it determines the positions of other elements
393	drawlogo()
394	drawbrand()
395	drawbox()
396	return drawmenu(menudef)
397end
398
399return drawer
400