SatCat5
gui_display.cc
1 // Copyright 2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/gui_display.h>
7 
21 
22 // Macros for each DrawArg format.
23 // (Disable false-alarm CppCheck warnings for unused union variables.)
24 inline constexpr DrawArg arg_ptr(const void* x) {
25  return DrawArg{.ptr = x}; // cpplint-suppress unreadVariable
26 }
27 inline constexpr DrawArg arg_color(u32 x) {
28  return DrawArg{.color = x}; // cpplint-suppress unreadVariable
29 }
30 inline constexpr DrawArg arg_count(u32 x) {
31  return DrawArg{.count = x}; // cpplint-suppress unreadVariable
32 }
33 inline constexpr DrawArg arg_rc(u16 r, u16 c) {
34  return DrawArg{.rc = {r,c}}; // cpplint-suppress unreadVariable
35 }
36 inline constexpr DrawArg arg_scroll(s16 x) {
37  return DrawArg{.scroll = x}; // cpplint-suppress unreadVariable
38 }
39 
40 // Define DrawCmd opcodes:
41 static constexpr u8
42  CMD_NONE = 0, // No-op
43  CMD_COLOR_FG = 1, // Foreground color
44  CMD_COLOR_BG = 2, // Background color
45  CMD_ICON8 = 3, // Icon8x8
46  CMD_ICON16 = 4, // Icon16x16
47  CMD_ICON32 = 5, // Icon32x32
48  CMD_MOVE = 6, // Move cursor
49  CMD_RECT = 7, // Solid rectangle
50  CMD_SCROLL = 8; // Scroll viewport
51 
52 bool DrawCmd::rc(u16 r, u16 c) const {
53  // For this DrawCmd's update region, is the pixel at (r,c) the
54  // foreground color (true) or the background color (false)?
55  // * Solid rectangle (CMD_RECT):
56  // The entire update region uses the same color.
57  // Use m_arg1 to select the true/false pixel value.
58  // * Icon (CMD_ICON8, CMD_ICON16, CMD_ICON32):
59  // Query the underlying icon for the true/false pixel value.
60  // Use m_arg1 to set a magnification factor, so we can upscale
61  // an 8x8 icon to fill 16x16 or 24x24 pixels as needed.
62  // Note: Text is rendered using a series of icon commands.
63  switch (m_opcode) {
64  case CMD_RECT: // Rectangle: Arg1 = Color
65  return !!m_arg1;
66  case CMD_ICON8: // Icon: Arg1 = Magnification
67  return icon8()->rc(r / m_arg1, c / m_arg1);
68  case CMD_ICON16: // Icon: Arg1 = Magnification
69  return icon16()->rc(r / m_arg1, c / m_arg1);
70  case CMD_ICON32: // Icon: Arg1 = Magnification
71  return icon32()->rc(r / m_arg1, c / m_arg1);
72  default: // All others -> Undefined
73  return false;
74  }
75 }
76 
77 u16 DrawCmd::height() const {
78  switch (m_opcode) {
79  case CMD_RECT: // Rectangle: Arg2 = Size (rows, cols)
80  return m_arg2.rc[0];
81  case CMD_ICON8: // Icon: Arg1 = Magnification
82  return icon8()->h() * m_arg1;
83  case CMD_ICON16: // Icon: Arg1 = Magnification
84  return icon16()->h() * m_arg1;
85  case CMD_ICON32: // Icon: Arg1 = Magnification
86  return icon32()->h() * m_arg1;
87  default: // All others -> Undefined
88  return 0;
89  }
90 }
91 
92 u16 DrawCmd::width() const {
93  switch (m_opcode) {
94  case CMD_RECT: // Rectangle: Arg2 = Size (rows, cols)
95  return m_arg2.rc[1];
96  case CMD_ICON8: // Icon: Arg1 = Magnification
97  return icon8()->w() * m_arg1;
98  case CMD_ICON16: // Icon: Arg1 = Magnification
99  return icon16()->w() * m_arg1;
100  case CMD_ICON32: // Icon: Arg1 = Magnification
101  return icon32()->w() * m_arg1;
102  default: // All others -> Undefined
103  return 0;
104  }
105 }
106 
107 void DrawCmd::update(Cursor& cursor) const {
108  switch (m_opcode) {
109  case CMD_COLOR_FG:
110  cursor.fg = m_arg2.color;
111  break;
112  case CMD_COLOR_BG:
113  cursor.bg = m_arg2.color;
114  break;
115  case CMD_MOVE:
116  cursor.r = m_arg2.rc[0];
117  cursor.c = m_arg2.rc[1];
118  break;
119  default:
120  cursor.c += width();
121  }
122 }
123 
125  : m_display(display)
126  , m_cursor_draw{0, 0, 0, 0}
127  , m_cursor_user{0, 0, 0, 0}
128  , m_buffer(0, 0)
129 {
130  // Nothing else to initialize.
131 }
132 
133 Canvas::Canvas(Display* display, u8* buffer, unsigned bsize)
134  : m_display(display)
135  , m_cursor_draw{0, 0, 0, 0}
136  , m_cursor_user{0, 0, 0, 0}
137  , m_buffer(buffer, bsize)
138 {
139  // Nothing else to initialize.
140 }
141 
142 bool Canvas::color_fg(u32 color) {
143  if (m_cursor_user.fg == color) return true; // Skip unchanged
144  DrawCmd cmd(CMD_COLOR_FG, 0, arg_color(color));
145  return enqueue(cmd) && finalize();
146 }
147 
148 bool Canvas::color_bg(u32 color) {
149  if (m_cursor_user.bg == color) return true; // Skip unchanged
150  DrawCmd cmd(CMD_COLOR_BG, 0, arg_color(color));
151  return enqueue(cmd) && finalize();
152 }
153 
154 bool Canvas::cursor(u16 r, u16 c) {
155  if (m_cursor_user.r == r && m_cursor_user.c == c) return true;
156  DrawCmd cmd(CMD_MOVE, 0, arg_rc(r, c));
157  return enqueue(cmd) && finalize();
158 }
159 
160 bool Canvas::clear(u32 color) {
161  return color_bg(color) && cursor(0, 0)
162  && draw_rect(m_display->height(), m_display->width(), false);
163 }
164 
165 bool Canvas::draw_icon(const Icon8x8* icon, u8 mag) {
166  DrawCmd cmd(CMD_ICON8, mag, arg_ptr(icon));
167  return enqueue(cmd) && finalize();
168 }
169 
170 bool Canvas::draw_icon(const Icon16x16* icon, u8 mag) {
171  DrawCmd cmd(CMD_ICON16, mag, arg_ptr(icon));
172  return enqueue(cmd) && finalize();
173 }
174 
175 bool Canvas::draw_icon(const Icon32x32* icon, u8 mag) {
176  DrawCmd cmd(CMD_ICON32, mag, arg_ptr(icon));
177  return enqueue(cmd) && finalize();
178 }
179 
180 bool Canvas::draw_rect(u16 h, u16 w, bool fg) {
181  DrawCmd cmd(CMD_RECT, fg ? 1 : 0, arg_rc(h, w));
182  return enqueue(cmd) && finalize();
183 }
184 
185 u16 Canvas::draw_text(const char* msg, const Font8x8& font, u8 mag) {
186  u16 rows = raw_text(msg, font, mag);
187  return (draw_eol(8*mag, rows) && finalize()) ? rows : 0;
188 }
189 
190 u16 Canvas::draw_text(const char* msg, const Font16x16& font, u8 mag) {
191  u16 rows = raw_text(msg, font, mag);
192  return (draw_eol(16*mag, rows) && finalize()) ? rows : 0;
193 }
194 
195 u16 Canvas::draw_text(const char* msg, const Font32x32& font, u8 mag) {
196  u16 rows = raw_text(msg, font, mag);
197  return (draw_eol(32*mag, rows) && finalize()) ? rows : 0;
198 }
199 
200 u16 Canvas::raw_text(const char* msg, const Font8x8& font, u8 mag) {
201  u16 rows = 0;
202  while (*msg) {
203  DrawCmd cmd(CMD_ICON8, mag, arg_ptr(font.icon(*msg)));
204  if (!draw_char(*msg++, cmd, rows)) return 0;
205  }
206  return rows;
207 }
208 
209 u16 Canvas::raw_text(const char* msg, const Font16x16& font, u8 mag) {
210  u16 rows = 0;
211  while (*msg) {
212  DrawCmd cmd(CMD_ICON16, mag, arg_ptr(font.icon(*msg)));
213  if (!draw_char(*msg++, cmd, rows)) return 0;
214  }
215  return rows;
216 }
217 
218 u16 Canvas::raw_text(const char* msg, const Font32x32& font, u8 mag) {
219  u16 rows = 0;
220  while (*msg) {
221  DrawCmd cmd(CMD_ICON32, mag, arg_ptr(font.icon(*msg)));
222  if (!draw_char(*msg++, cmd, rows)) return 0;
223  }
224  return rows;
225 }
226 
227 bool Canvas::scroll(s16 rows) {
228  DrawCmd cmd(CMD_SCROLL, 0, arg_scroll(rows));
229  return enqueue(cmd) && finalize();
230 }
231 
232 bool Canvas::cmd_retry() {
233  // Attempt or re-attempt execution of m_cmd_retry;
234  bool done = execute(m_cmd_retry);
235  if (done) m_cmd_retry.m_opcode = CMD_NONE; // Mark as executed?
236  if (!done) request_poll(); // Try again later?
237  return done; // Ready to proceed?
238 }
239 
240 // Draw a single character at the current cursor position,
241 // plus special handling for newline, tab, etc.
242 bool Canvas::draw_char(char ch, const DrawCmd& cmd, u16& total_rows) {
243  bool ok = true;
244  // Calculate remaining columns in this row.
245  u16 rem_cols = width() - m_cursor_user.c;
246  // If we've reached end-of-line, clear remainder and move cursor.
247  if ((ch == '\n') || (cmd.width() > rem_cols))
248  ok = ok && draw_eol(cmd.height(), total_rows);
249  // Render each printable character, special handling for others.
250  if (cmd.m_arg2.ptr) {
251  ok = ok && enqueue(cmd);
252  } else if (ch == '\t') {
253  DrawCmd tab(CMD_RECT, 0, arg_rc(cmd.height(), cmd.width()));
254  ok = ok && enqueue(tab);
255  }
256  return ok;
257 }
258 
259 bool Canvas::draw_eol(u16 height, u16& total_rows) {
260  // End-of-line: Fill remainder of line and move cursor position.
261  total_rows += height;
262  u16 rem_cols = width() - m_cursor_user.c;
263  DrawCmd fill(CMD_RECT, 0, arg_rc(height, rem_cols));
264  DrawCmd wrap(CMD_MOVE, 0, arg_rc(m_cursor_user.r + height, 0));
265  return (!rem_cols || enqueue(fill)) && enqueue(wrap);
266 }
267 
268 bool Canvas::enqueue(const DrawCmd& cmd) {
269  // Immediate mode: Commands go directly to the display device.
270  // Buffered mode: Write data to the queue instead.
271  bool ok = true;
272  if (!m_buffer.get_buff_size()) ok = execute(cmd);
273  else m_buffer.write_bytes(sizeof(DrawCmd), &cmd);
274  if (ok) cmd.update(m_cursor_user);
275  return true;
276 }
277 
278 bool Canvas::execute(const DrawCmd& cmd) {
279  // Pass this command to the display device?
280  bool ok = true;
281  if (cmd.m_opcode == CMD_SCROLL) {
282  ok = m_display->scroll(cmd.m_arg2.scroll);
283  } else if (cmd.width() && cmd.height()) {
284  ok = m_display->draw(m_cursor_draw, cmd);
285  }
286 
287  // Update cursor state.
288  if (ok) cmd.update(m_cursor_draw);
289  return ok;
290 }
291 
292 bool Canvas::finalize() {
293  if (!m_buffer.get_buff_size()) return true; // Immediate mode
294  bool ok = m_buffer.write_finalize(); // Buffered mode
295  if (ok) request_poll();
296  return ok;
297 }
298 
300  // Retry previous command if applicable, then pull new commands from queue.
301  while (cmd_retry() && m_buffer.get_read_ready() >= sizeof(DrawCmd)) {
302  m_buffer.read_bytes(sizeof(DrawCmd), &m_cmd_retry);
303  }
304 }
305 
307  Canvas* canvas, const LogColors& colors,
308  u16 row_min, u16 row_count)
309  : m_canvas(canvas)
310  , m_colors(colors)
311  , m_row_min(row_min)
312  , m_row_count(row_count ? row_count : (canvas->height() - row_min))
313  , m_row_next(0)
314 {
315  // Nothing else to initialize.
316  // TODO: Allow user to set the font?
317 }
318 
319 // Implement the required API from log::EventHandler.
320 void LogToDisplay::log_event(s8 priority, unsigned nbytes, const char* msg) {
321  // Set cursor position to the start of the current row.
322  m_canvas->cursor(m_row_next + m_row_min, 0);
323 
324  // Write the DEBUG / INFO / WARN / ERROR banner in the designated color.
325  if (priority <= satcat5::log::DEBUG) {
326  m_canvas->color_bg(m_colors.bg_debug);
327  m_canvas->color_fg(m_colors.fg_debug);
328  m_canvas->raw_text("DEBUG: ");
329  } else if (priority <= satcat5::log::INFO) {
330  m_canvas->color_bg(m_colors.bg_info);
331  m_canvas->color_fg(m_colors.fg_info);
332  m_canvas->raw_text("INFO: ");
333  } else if (priority <= satcat5::log::WARNING) {
334  m_canvas->color_fg(m_colors.fg_warn);
335  m_canvas->color_bg(m_colors.bg_warn);
336  m_canvas->raw_text("WARN: ");
337  } else {
338  m_canvas->color_bg(m_colors.bg_error);
339  m_canvas->color_fg(m_colors.fg_error);
340  m_canvas->raw_text("ERROR: ");
341  }
342 
343  // Write the rest of the log message.
344  m_canvas->color_bg(m_colors.bg_text);
345  m_canvas->color_fg(m_colors.fg_text);
346  u16 new_rows = m_canvas->draw_text(msg);
347 
348  // Scroll and update write position for next time.
349  m_canvas->scroll(new_rows);
350  m_row_next = (m_row_next + new_rows) % m_row_count;
351 }
User-facing interface for drawing graphical elements on a screen.
Definition: gui_display.h:140
u16 raw_text(const char *msg, const satcat5::gui::Font8x8 &font=BASIC_FONT, u8 mag=1)
Draw a partial line of text with the designated font.
Definition: gui_display.cc:200
bool clear(u32 color)
Clear the entire display contents.
Definition: gui_display.cc:160
bool cursor(u16 r, u16 c)
Set cursor position for subsequent commands.
Definition: gui_display.cc:154
bool draw_rect(u16 h, u16 w, bool fg=true)
Draw a solid rectangle using the specified color.
Definition: gui_display.cc:180
bool draw_icon(const satcat5::gui::Icon8x8 *icon, u8 mag=1)
Draw icon with the designated location and color.
Definition: gui_display.cc:165
bool color_bg(u32 color)
Set background color for subsequent commands.
Definition: gui_display.cc:148
bool scroll(s16 rows)
On supported displays, scroll the designated scrollable window.
Definition: gui_display.cc:227
void poll_demand() override
Deferred event handler, called after request().
Definition: gui_display.cc:299
u16 draw_text(const char *msg, const satcat5::gui::Font8x8 &font=BASIC_FONT, u8 mag=1)
Draw a full line of text with the designated font.
Definition: gui_display.cc:185
bool color_fg(u32 color)
Set foreground color for subsequent commands.
Definition: gui_display.cc:142
Canvas(satcat5::gui::Display *display)
Link this object to a Display in immediate mode.
Definition: gui_display.cc:124
Required API for display devices.
Definition: gui_display.h:105
virtual bool draw(const satcat5::gui::Cursor &cursor, const satcat5::gui::DrawCmd &cmd)=0
Draw pixels to the screen at the designated location.
u16 width() const
Total size of the display, in pixels.
Definition: gui_display.h:124
virtual bool scroll(s16 rows)
Optional: Advance the predefined viewport by N pixels.
Definition: gui_display.h:119
u16 height() const
Total size of the display, in pixels.
Definition: gui_display.h:123
Each "draw command" updates a rectangular region of pixels.
Definition: gui_display.h:61
u16 width() const
Size of the rectangular update region.
Definition: gui_display.cc:92
u16 height() const
Size of the rectangular update region.
Definition: gui_display.cc:77
void update(Cursor &cursor) const
Update cursor-state automatically for easier chaining.
Definition: gui_display.cc:107
The Font class maps characters to fixed-size icons.
Definition: gui_icons.h:86
const T * icon(char c) const
Fetch the icon associated with a given character.
Definition: gui_icons.h:93
Service for forwarding Log events to a display adapter.
Definition: gui_display.h:250
void log_event(s8 priority, unsigned nbytes, const char *msg) override
Implement the required API from log::EventHandler.
Definition: gui_display.cc:320
LogToDisplay(satcat5::gui::Canvas *canvas, const satcat5::gui::LogColors &colors, u16 row_min=0, u16 row_count=0)
Link this log service to the designated display/canvas.
Definition: gui_display.cc:306
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: pkt_buffer.cc:105
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
Definition: pkt_buffer.cc:187
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
Definition: pkt_buffer.cc:73
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: pkt_buffer.cc:177
unsigned get_buff_size() const
Accessor for children that need to delete underlying buffer.
Definition: pkt_buffer.h:138
void request_poll()
Call this method to request polling at a later time.
Definition: polling.cc:208
"Display" and "Canvas" API for rendering text and graphics
Cursor object tracks position and foreground/background colors.
Definition: gui_display.h:33
u32 fg
Foreground color (format defined by display)
Definition: gui_display.h:36
u16 r
Row coordinate (0 = top)
Definition: gui_display.h:34
u32 bg
Background color (format defined by display)
Definition: gui_display.h:37
u16 c
Column coordinate (0 = left)
Definition: gui_display.h:35
Plain-old-data struct for a 16x16 monochrome image.
Definition: gui_icons.h:46
bool rc(u16 r, u16 c) const
Get pixel value at designated row and column.
Definition: gui_icons.h:55
u16 w() const
Height of this icon.
Definition: gui_icons.h:59
u16 h() const
Width of this icon.
Definition: gui_icons.h:58
Plain-old-data struct for a 32x32 monochrome image.
Definition: gui_icons.h:65
u16 h() const
Width of this icon.
Definition: gui_icons.h:77
bool rc(u16 r, u16 c) const
Get pixel value at designated row and column.
Definition: gui_icons.h:74
u16 w() const
Height of this icon.
Definition: gui_icons.h:78
Plain-old-data struct for an 8x8 monochrome image.
Definition: gui_icons.h:25
bool rc(u16 r, u16 c) const
Get pixel value at designated row and column.
Definition: gui_icons.h:36
u16 w() const
Height of this icon.
Definition: gui_icons.h:40
u16 h() const
Width of this icon.
Definition: gui_icons.h:39
Color parameters for the LogToDisplay class.
Definition: gui_display.h:237
Argument for a draw command (see below).
Definition: gui_display.h:45
const void * ptr
Pointer to an icon or other object.
Definition: gui_display.h:46
u32 color
Display-specific color argument.
Definition: gui_display.h:47
s16 scroll
Scrolling parameter (signed)
Definition: gui_display.h:48
u16 rc[2]
A row and column (coordinate or size)
Definition: gui_display.h:50
u32 count
Any other counter.
Definition: gui_display.h:49