1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Implementation of a expo, a collection of scenes providing menu options
4 *
5 * Copyright 2022 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
7 */
8
9#define LOG_CATEGORY	LOGC_EXPO
10
11#include <common.h>
12#include <dm.h>
13#include <expo.h>
14#include <malloc.h>
15#include <video.h>
16#include "scene_internal.h"
17
18int expo_new(const char *name, void *priv, struct expo **expp)
19{
20	struct expo *exp;
21
22	exp = calloc(1, sizeof(struct expo));
23	if (!exp)
24		return log_msg_ret("expo", -ENOMEM);
25	exp->name = strdup(name);
26	if (!exp->name) {
27		free(exp);
28		return log_msg_ret("name", -ENOMEM);
29	}
30	exp->priv = priv;
31	INIT_LIST_HEAD(&exp->scene_head);
32	INIT_LIST_HEAD(&exp->str_head);
33
34	*expp = exp;
35
36	return 0;
37}
38
39static void estr_destroy(struct expo_string *estr)
40{
41	free(estr);
42}
43
44void expo_destroy(struct expo *exp)
45{
46	struct scene *scn, *next;
47	struct expo_string *estr, *enext;
48
49	list_for_each_entry_safe(scn, next, &exp->scene_head, sibling)
50		scene_destroy(scn);
51
52	list_for_each_entry_safe(estr, enext, &exp->str_head, sibling)
53		estr_destroy(estr);
54
55	free(exp->name);
56	free(exp);
57}
58
59uint resolve_id(struct expo *exp, uint id)
60{
61	log_debug("resolve id %d\n", id);
62	if (!id)
63		id = exp->next_id++;
64	else if (id >= exp->next_id)
65		exp->next_id = id + 1;
66
67	return id;
68}
69
70void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
71{
72	exp->next_id = dyn_start;
73}
74
75int expo_str(struct expo *exp, const char *name, uint id, const char *str)
76{
77	struct expo_string *estr;
78
79	estr = calloc(1, sizeof(struct expo_string));
80	if (!estr)
81		return log_msg_ret("obj", -ENOMEM);
82
83	estr->id = resolve_id(exp, id);
84	estr->str = str;
85	list_add_tail(&estr->sibling, &exp->str_head);
86
87	return estr->id;
88}
89
90const char *expo_get_str(struct expo *exp, uint id)
91{
92	struct expo_string *estr;
93
94	list_for_each_entry(estr, &exp->str_head, sibling) {
95		if (estr->id == id)
96			return estr->str;
97	}
98
99	return NULL;
100}
101
102int expo_set_display(struct expo *exp, struct udevice *dev)
103{
104	struct udevice *cons;
105	int ret;
106
107	ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE,
108						&cons);
109	if (ret)
110		return log_msg_ret("con", ret);
111
112	exp->display = dev;
113	exp->cons = cons;
114
115	return 0;
116}
117
118int expo_calc_dims(struct expo *exp)
119{
120	struct scene *scn;
121	int ret;
122
123	if (!exp->cons)
124		return log_msg_ret("dim", -ENOTSUPP);
125
126	list_for_each_entry(scn, &exp->scene_head, sibling) {
127		/*
128		 * Do the menus last so that all the menus' text objects
129		 * are dimensioned
130		 */
131		ret = scene_calc_dims(scn, false);
132		if (ret)
133			return log_msg_ret("scn", ret);
134		ret = scene_calc_dims(scn, true);
135		if (ret)
136			return log_msg_ret("scn", ret);
137	}
138
139	return 0;
140}
141
142void expo_set_text_mode(struct expo *exp, bool text_mode)
143{
144	exp->text_mode = text_mode;
145}
146
147struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
148{
149	struct scene *scn;
150
151	list_for_each_entry(scn, &exp->scene_head, sibling) {
152		if (scn->id == scene_id)
153			return scn;
154	}
155
156	return NULL;
157}
158
159int expo_set_scene_id(struct expo *exp, uint scene_id)
160{
161	struct scene *scn;
162	int ret;
163
164	scn = expo_lookup_scene_id(exp, scene_id);
165	if (!scn)
166		return log_msg_ret("id", -ENOENT);
167	ret = scene_arrange(scn);
168	if (ret)
169		return log_msg_ret("arr", ret);
170
171	exp->scene_id = scene_id;
172
173	return 0;
174}
175
176int expo_first_scene_id(struct expo *exp)
177{
178	struct scene *scn;
179
180	if (list_empty(&exp->scene_head))
181		return -ENOENT;
182
183	scn = list_first_entry(&exp->scene_head, struct scene, sibling);
184
185	return scn->id;
186}
187
188int expo_render(struct expo *exp)
189{
190	struct udevice *dev = exp->display;
191	struct video_priv *vid_priv = dev_get_uclass_priv(dev);
192	struct scene *scn = NULL;
193	enum colour_idx back;
194	u32 colour;
195	int ret;
196
197	back = CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK) ? VID_BLACK : VID_WHITE;
198	colour = video_index_to_colour(vid_priv, back);
199	ret = video_fill(dev, colour);
200	if (ret)
201		return log_msg_ret("fill", ret);
202
203	if (exp->scene_id) {
204		scn = expo_lookup_scene_id(exp, exp->scene_id);
205		if (!scn)
206			return log_msg_ret("scn", -ENOENT);
207
208		ret = scene_render(scn);
209		if (ret)
210			return log_msg_ret("ren", ret);
211	}
212
213	video_sync(dev, true);
214
215	return scn ? 0 : -ECHILD;
216}
217
218int expo_send_key(struct expo *exp, int key)
219{
220	struct scene *scn = NULL;
221
222	if (exp->scene_id) {
223		int ret;
224
225		scn = expo_lookup_scene_id(exp, exp->scene_id);
226		if (!scn)
227			return log_msg_ret("scn", -ENOENT);
228
229		ret = scene_send_key(scn, key, &exp->action);
230		if (ret)
231			return log_msg_ret("key", ret);
232
233		/* arrange it to get any changes */
234		ret = scene_arrange(scn);
235		if (ret)
236			return log_msg_ret("arr", ret);
237	}
238
239	return scn ? 0 : -ECHILD;
240}
241
242int expo_action_get(struct expo *exp, struct expo_action *act)
243{
244	*act = exp->action;
245	exp->action.type = EXPOACT_NONE;
246
247	return act->type == EXPOACT_NONE ? -EAGAIN : 0;
248}
249
250int expo_apply_theme(struct expo *exp, ofnode node)
251{
252	struct scene *scn;
253	struct expo_theme *theme = &exp->theme;
254	int ret;
255
256	log_debug("Applying theme %s\n", ofnode_get_name(node));
257
258	memset(theme, '\0', sizeof(struct expo_theme));
259	ofnode_read_u32(node, "font-size", &theme->font_size);
260	ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
261	ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
262
263	list_for_each_entry(scn, &exp->scene_head, sibling) {
264		ret = scene_apply_theme(scn, theme);
265		if (ret)
266			return log_msg_ret("app", ret);
267	}
268
269	return 0;
270}
271
272int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
273			 void *priv)
274{
275	struct scene *scn;
276	int ret;
277
278	list_for_each_entry(scn, &exp->scene_head, sibling) {
279		ret = scene_iter_objs(scn, iter, priv);
280		if (ret)
281			return log_msg_ret("wr", ret);
282	}
283
284	return 0;
285}
286