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