1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Implementation of a scene, a collection of text/image/menu items in an expo
4  *
5  * Copyright 2022 Google LLC
6  * Written by Simon Glass <sjg@chromium.org>
7  */
8 
9 #include <common.h>
10 #include <dm.h>
11 #include <expo.h>
12 #include <malloc.h>
13 #include <mapmem.h>
14 #include <video.h>
15 #include <video_console.h>
16 #include <linux/input.h>
17 #include "scene_internal.h"
18 
resolve_id(struct expo * exp,uint id)19 uint resolve_id(struct expo *exp, uint id)
20 {
21 	if (!id)
22 		id = exp->next_id++;
23 	else if (id >= exp->next_id)
24 		exp->next_id = id + 1;
25 
26 	return id;
27 }
28 
scene_new(struct expo * exp,const char * name,uint id,struct scene ** scnp)29 int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
30 {
31 	struct scene *scn;
32 
33 	scn = calloc(1, sizeof(struct scene));
34 	if (!scn)
35 		return log_msg_ret("expo", -ENOMEM);
36 	scn->name = strdup(name);
37 	if (!scn->name) {
38 		free(scn);
39 		return log_msg_ret("name", -ENOMEM);
40 	}
41 
42 	INIT_LIST_HEAD(&scn->obj_head);
43 	scn->id = resolve_id(exp, id);
44 	scn->expo = exp;
45 	list_add_tail(&scn->sibling, &exp->scene_head);
46 
47 	*scnp = scn;
48 
49 	return scn->id;
50 }
51 
scene_obj_destroy(struct scene_obj * obj)52 void scene_obj_destroy(struct scene_obj *obj)
53 {
54 	if (obj->type == SCENEOBJT_MENU)
55 		scene_menu_destroy((struct scene_obj_menu *)obj);
56 	free(obj->name);
57 	free(obj);
58 }
59 
scene_destroy(struct scene * scn)60 void scene_destroy(struct scene *scn)
61 {
62 	struct scene_obj *obj, *next;
63 
64 	list_for_each_entry_safe(obj, next, &scn->obj_head, sibling)
65 		scene_obj_destroy(obj);
66 
67 	free(scn->name);
68 	free(scn->title);
69 	free(scn);
70 }
71 
scene_title_set(struct scene * scn,const char * title)72 int scene_title_set(struct scene *scn, const char *title)
73 {
74 	free(scn->title);
75 	scn->title = strdup(title);
76 	if (!scn->title)
77 		return log_msg_ret("tit", -ENOMEM);
78 
79 	return 0;
80 }
81 
scene_obj_count(struct scene * scn)82 int scene_obj_count(struct scene *scn)
83 {
84 	struct scene_obj *obj;
85 	int count = 0;
86 
87 	list_for_each_entry(obj, &scn->obj_head, sibling)
88 		count++;
89 
90 	return count;
91 }
92 
scene_obj_find(struct scene * scn,uint id,enum scene_obj_t type)93 void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type)
94 {
95 	struct scene_obj *obj;
96 
97 	list_for_each_entry(obj, &scn->obj_head, sibling) {
98 		if (obj->id == id &&
99 		    (type == SCENEOBJT_NONE || obj->type == type))
100 			return obj;
101 	}
102 
103 	return NULL;
104 }
105 
scene_obj_add(struct scene * scn,const char * name,uint id,enum scene_obj_t type,uint size,struct scene_obj ** objp)106 int scene_obj_add(struct scene *scn, const char *name, uint id,
107 		  enum scene_obj_t type, uint size, struct scene_obj **objp)
108 {
109 	struct scene_obj *obj;
110 
111 	obj = calloc(1, size);
112 	if (!obj)
113 		return log_msg_ret("obj", -ENOMEM);
114 	obj->name = strdup(name);
115 	if (!obj->name) {
116 		free(obj);
117 		return log_msg_ret("name", -ENOMEM);
118 	}
119 
120 	obj->id = resolve_id(scn->expo, id);
121 	obj->scene = scn;
122 	obj->type = type;
123 	list_add_tail(&obj->sibling, &scn->obj_head);
124 	*objp = obj;
125 
126 	return obj->id;
127 }
128 
scene_img(struct scene * scn,const char * name,uint id,char * data,struct scene_obj_img ** imgp)129 int scene_img(struct scene *scn, const char *name, uint id, char *data,
130 	      struct scene_obj_img **imgp)
131 {
132 	struct scene_obj_img *img;
133 	int ret;
134 
135 	ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE,
136 			    sizeof(struct scene_obj_img),
137 			    (struct scene_obj **)&img);
138 	if (ret < 0)
139 		return log_msg_ret("obj", -ENOMEM);
140 
141 	img->data = data;
142 
143 	if (imgp)
144 		*imgp = img;
145 
146 	return img->obj.id;
147 }
148 
scene_txt(struct scene * scn,const char * name,uint id,uint str_id,struct scene_obj_txt ** txtp)149 int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
150 	      struct scene_obj_txt **txtp)
151 {
152 	struct scene_obj_txt *txt;
153 	int ret;
154 
155 	ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
156 			    sizeof(struct scene_obj_txt),
157 			    (struct scene_obj **)&txt);
158 	if (ret < 0)
159 		return log_msg_ret("obj", -ENOMEM);
160 
161 	txt->str_id = str_id;
162 
163 	if (txtp)
164 		*txtp = txt;
165 
166 	return txt->obj.id;
167 }
168 
scene_txt_str(struct scene * scn,const char * name,uint id,uint str_id,const char * str,struct scene_obj_txt ** txtp)169 int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
170 		  const char *str, struct scene_obj_txt **txtp)
171 {
172 	struct scene_obj_txt *txt;
173 	int ret;
174 
175 	ret = expo_str(scn->expo, name, str_id, str);
176 	if (ret < 0)
177 		return log_msg_ret("str", ret);
178 	else if (ret != str_id)
179 		return log_msg_ret("id", -EEXIST);
180 
181 	ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
182 			    sizeof(struct scene_obj_txt),
183 			    (struct scene_obj **)&txt);
184 	if (ret < 0)
185 		return log_msg_ret("obj", -ENOMEM);
186 
187 	txt->str_id = str_id;
188 
189 	if (txtp)
190 		*txtp = txt;
191 
192 	return txt->obj.id;
193 }
194 
scene_txt_set_font(struct scene * scn,uint id,const char * font_name,uint font_size)195 int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
196 		       uint font_size)
197 {
198 	struct scene_obj_txt *txt;
199 
200 	txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
201 	if (!txt)
202 		return log_msg_ret("find", -ENOENT);
203 	txt->font_name = font_name;
204 	txt->font_size = font_size;
205 
206 	return 0;
207 }
208 
scene_obj_set_pos(struct scene * scn,uint id,int x,int y)209 int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
210 {
211 	struct scene_obj *obj;
212 
213 	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
214 	if (!obj)
215 		return log_msg_ret("find", -ENOENT);
216 	obj->x = x;
217 	obj->y = y;
218 	if (obj->type == SCENEOBJT_MENU)
219 		scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
220 
221 	return 0;
222 }
223 
scene_obj_set_hide(struct scene * scn,uint id,bool hide)224 int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
225 {
226 	struct scene_obj *obj;
227 
228 	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
229 	if (!obj)
230 		return log_msg_ret("find", -ENOENT);
231 	obj->hide = hide;
232 
233 	return 0;
234 }
235 
scene_obj_get_hw(struct scene * scn,uint id,int * widthp)236 int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
237 {
238 	struct scene_obj *obj;
239 
240 	obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
241 	if (!obj)
242 		return log_msg_ret("find", -ENOENT);
243 
244 	switch (obj->type) {
245 	case SCENEOBJT_NONE:
246 	case SCENEOBJT_MENU:
247 		break;
248 	case SCENEOBJT_IMAGE: {
249 		struct scene_obj_img *img = (struct scene_obj_img *)obj;
250 		ulong width, height;
251 		uint bpix;
252 
253 		video_bmp_get_info(img->data, &width, &height, &bpix);
254 		if (widthp)
255 			*widthp = width;
256 		return height;
257 	}
258 	case SCENEOBJT_TEXT: {
259 		struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
260 		struct expo *exp = scn->expo;
261 
262 		if (widthp)
263 			*widthp = 16; /* fake value for now */
264 		if (txt->font_size)
265 			return txt->font_size;
266 		if (exp->display)
267 			return video_default_font_height(exp->display);
268 
269 		/* use a sensible default */
270 		return 16;
271 	}
272 	}
273 
274 	return 0;
275 }
276 
277 /**
278  * scene_obj_render() - Render an object
279  *
280  */
scene_obj_render(struct scene_obj * obj,bool text_mode)281 static int scene_obj_render(struct scene_obj *obj, bool text_mode)
282 {
283 	struct scene *scn = obj->scene;
284 	struct expo *exp = scn->expo;
285 	struct udevice *cons, *dev = exp->display;
286 	int x, y, ret;
287 
288 	cons = NULL;
289 	if (!text_mode) {
290 		ret = device_find_first_child_by_uclass(dev,
291 							UCLASS_VIDEO_CONSOLE,
292 							&cons);
293 	}
294 
295 	x = obj->x;
296 	y = obj->y;
297 
298 	switch (obj->type) {
299 	case SCENEOBJT_NONE:
300 		break;
301 	case SCENEOBJT_IMAGE: {
302 		struct scene_obj_img *img = (struct scene_obj_img *)obj;
303 
304 		if (!cons)
305 			return -ENOTSUPP;
306 		ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y,
307 					true);
308 		if (ret < 0)
309 			return log_msg_ret("img", ret);
310 		break;
311 	}
312 	case SCENEOBJT_TEXT: {
313 		struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
314 		const char *str;
315 
316 		if (!cons)
317 			return -ENOTSUPP;
318 
319 		if (txt->font_name || txt->font_size) {
320 			ret = vidconsole_select_font(cons,
321 						     txt->font_name,
322 						     txt->font_size);
323 		} else {
324 			ret = vidconsole_select_font(cons, NULL, 0);
325 		}
326 		if (ret && ret != -ENOSYS)
327 			return log_msg_ret("font", ret);
328 		vidconsole_set_cursor_pos(cons, x, y);
329 		str = expo_get_str(exp, txt->str_id);
330 		if (str)
331 			vidconsole_put_string(cons, str);
332 		break;
333 	}
334 	case SCENEOBJT_MENU: {
335 		struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
336 		/*
337 		 * With a vidconsole, the text and item pointer are rendered as
338 		 * normal objects so we don't need to do anything here. The menu
339 		 * simply controls where they are positioned.
340 		 */
341 		if (cons)
342 			return -ENOTSUPP;
343 
344 		ret = scene_menu_display(menu);
345 		if (ret < 0)
346 			return log_msg_ret("img", ret);
347 
348 		break;
349 	}
350 	}
351 
352 	return 0;
353 }
354 
scene_arrange(struct scene * scn)355 int scene_arrange(struct scene *scn)
356 {
357 	struct scene_obj *obj;
358 	int ret;
359 
360 	list_for_each_entry(obj, &scn->obj_head, sibling) {
361 		if (obj->type == SCENEOBJT_MENU) {
362 			struct scene_obj_menu *menu;
363 
364 			menu = (struct scene_obj_menu *)obj,
365 			ret = scene_menu_arrange(scn, menu);
366 			if (ret)
367 				return log_msg_ret("arr", ret);
368 		}
369 	}
370 
371 	return 0;
372 }
373 
scene_render(struct scene * scn)374 int scene_render(struct scene *scn)
375 {
376 	struct expo *exp = scn->expo;
377 	struct scene_obj *obj;
378 	int ret;
379 
380 	list_for_each_entry(obj, &scn->obj_head, sibling) {
381 		if (!obj->hide) {
382 			ret = scene_obj_render(obj, exp->text_mode);
383 			if (ret && ret != -ENOTSUPP)
384 				return log_msg_ret("ren", ret);
385 		}
386 	}
387 
388 	return 0;
389 }
390 
scene_send_key(struct scene * scn,int key,struct expo_action * event)391 int scene_send_key(struct scene *scn, int key, struct expo_action *event)
392 {
393 	struct scene_obj *obj;
394 	int ret;
395 
396 	list_for_each_entry(obj, &scn->obj_head, sibling) {
397 		if (obj->type == SCENEOBJT_MENU) {
398 			struct scene_obj_menu *menu;
399 
400 			menu = (struct scene_obj_menu *)obj,
401 			ret = scene_menu_send_key(scn, menu, key, event);
402 			if (ret)
403 				return log_msg_ret("key", ret);
404 
405 			/* only allow one menu */
406 			ret = scene_menu_arrange(scn, menu);
407 			if (ret)
408 				return log_msg_ret("arr", ret);
409 			break;
410 		}
411 	}
412 
413 	return 0;
414 }
415