diff options
author | Bad Diode <bd@badd10de.dev> | 2021-05-18 16:40:24 +0200 |
---|---|---|
committer | Bad Diode <bd@badd10de.dev> | 2021-05-18 16:40:24 +0200 |
commit | 0c7265cf0de9d4ec95d28c5e103c00a63f4a1697 (patch) | |
tree | 4a1145e849e078395430a8d718c4bd69a06fb29f /src/text.h | |
download | uxngba-0c7265cf0de9d4ec95d28c5e103c00a63f4a1697.tar.gz uxngba-0c7265cf0de9d4ec95d28c5e103c00a63f4a1697.zip |
Proof of concept of UXN on the GBA
Diffstat (limited to 'src/text.h')
-rw-r--r-- | src/text.h | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/src/text.h b/src/text.h new file mode 100644 index 0000000..bfac4e4 --- /dev/null +++ b/src/text.h | |||
@@ -0,0 +1,232 @@ | |||
1 | #ifndef GBAEXP_TILES_H | ||
2 | #define GBAEXP_TILES_H | ||
3 | |||
4 | #include <stdarg.h> | ||
5 | #include <stdio.h> | ||
6 | |||
7 | #include "common.h" | ||
8 | #include "bitmap.h" | ||
9 | |||
10 | typedef enum { | ||
11 | TXT_MODE_TILED_BG, | ||
12 | TXT_MODE_MODE3, | ||
13 | } TextMode; | ||
14 | |||
15 | typedef struct Font { | ||
16 | // A pointer to an area of memory containing font data. | ||
17 | // TODO: Should we unpack each char everytime or unpack everything into RAM? | ||
18 | // Maybe this should be optional? | ||
19 | u16 *data; | ||
20 | // The char_map stores the index to the character position within the font | ||
21 | // array depending on the ascii number we want to render. This allows | ||
22 | // the usage reduced font sets, for example just uppercase letters and | ||
23 | // numbers. | ||
24 | u8 *char_map; | ||
25 | // Width and height of each font character. Only monospaced fonts are | ||
26 | // currently supported. | ||
27 | u8 char_width; | ||
28 | u8 char_height; | ||
29 | // The color of this font. | ||
30 | Color color; | ||
31 | } Font; | ||
32 | |||
33 | typedef struct TextEngine { | ||
34 | // Currently working on tiled backgrounds only. The X and Y positions | ||
35 | // correspond to the tile X and Y starting from the top left of the screen. | ||
36 | // For a 240x160 screen, we have 30x20 tiles available. | ||
37 | size_t cursor_x; | ||
38 | size_t cursor_y; | ||
39 | // Pointer to the memory being used for writing to the screen. | ||
40 | u16 *memory; | ||
41 | // TODO: Support other modes and monospaced fonts should be simple but we | ||
42 | // need to keep track of a couple of other variables, which may not be | ||
43 | // available for all modes. For example, tile modes can't have a font width | ||
44 | // smaller than the tile size, although bigger fonts (For example fonts | ||
45 | // composed of multiple tiles) should still be possible. This may not be | ||
46 | // a good time investment, but can be done in the future if needed. | ||
47 | // The mode controls how the text is rendered and how the memory is managed. | ||
48 | TextMode mode; | ||
49 | // The font used to render the text. | ||
50 | Font font; | ||
51 | } TextEngine; | ||
52 | |||
53 | static TextEngine text_engine = {0}; | ||
54 | |||
55 | static u8 default_char_map[256] = {0}; | ||
56 | |||
57 | void | ||
58 | txt_putc_tile(char c) { | ||
59 | if (c == '\0') { | ||
60 | return; | ||
61 | } | ||
62 | if (c == '\n') { | ||
63 | text_engine.cursor_x = 0; | ||
64 | text_engine.cursor_y++; | ||
65 | } else { | ||
66 | text_engine.memory[text_engine.cursor_x + 32 * text_engine.cursor_y] = c; | ||
67 | text_engine.cursor_x++; | ||
68 | if (text_engine.cursor_x >= 30) { | ||
69 | text_engine.cursor_x = 0; | ||
70 | text_engine.cursor_y++; | ||
71 | } | ||
72 | } | ||
73 | if (text_engine.cursor_y >= 20) { | ||
74 | text_engine.cursor_y = 0; | ||
75 | } | ||
76 | } | ||
77 | |||
78 | void | ||
79 | txt_putc_m3(char c) { | ||
80 | if (c == '\0') { | ||
81 | return; | ||
82 | } | ||
83 | if (c == '\n') { | ||
84 | text_engine.cursor_x = 0; | ||
85 | text_engine.cursor_y += text_engine.font.char_height; | ||
86 | } else { | ||
87 | u8 idx = text_engine.font.char_map[(int)c] * 2; | ||
88 | u32 *packed_char = text_engine.font.data; | ||
89 | packed_char += idx; | ||
90 | Tile tile = {0}; | ||
91 | unpack_tiles(packed_char, &tile, 1); | ||
92 | int x = text_engine.cursor_x; | ||
93 | int y = text_engine.cursor_y; | ||
94 | for (size_t i = 0; i < text_engine.font.char_height; ++i) { | ||
95 | for (size_t j = 0; j < text_engine.font.char_width; ++j) { | ||
96 | if ((tile.row[i] >> 4 * j) & 0x1) { | ||
97 | // put_pixel_m4(x + j, y + i, 1, backbuffer); | ||
98 | // TODO: Clean this up please. | ||
99 | put_pixel_m3(x + j, | ||
100 | y + i, | ||
101 | text_engine.font.color, | ||
102 | FRAMEBUFFER); | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | text_engine.cursor_x += text_engine.font.char_width; | ||
107 | if (text_engine.cursor_x >= SCREEN_WIDTH) { | ||
108 | text_engine.cursor_x = 0; | ||
109 | text_engine.cursor_y += text_engine.font.char_height; | ||
110 | } | ||
111 | } | ||
112 | if (text_engine.cursor_y >= SCREEN_HEIGHT) { | ||
113 | text_engine.cursor_y = 0; | ||
114 | } | ||
115 | } | ||
116 | |||
117 | void | ||
118 | txt_putc(char c) { | ||
119 | switch (text_engine.mode) { | ||
120 | case TXT_MODE_TILED_BG: { | ||
121 | txt_putc_tile(c); | ||
122 | } break; | ||
123 | case TXT_MODE_MODE3: { | ||
124 | txt_putc_m3(c); | ||
125 | } break; | ||
126 | } | ||
127 | } | ||
128 | |||
129 | static inline void | ||
130 | txt_puts(char *msg) { | ||
131 | while (*msg) { | ||
132 | txt_putc(*msg++); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | void | ||
137 | txt_init_tile(size_t bg, Font font, size_t cb_idx) { | ||
138 | // The screenblock for the tile map should start after the tile memory | ||
139 | // (MEM_VRAM + 0x2000 for 256 characters). Since each screenblock is | ||
140 | // composed of 1024 screenblock entries of u16 (2 bytes), we need an | ||
141 | // screenblock index offset of 8192 / (1024 * 2) = 4 screen blocks. | ||
142 | size_t sb_idx = cb_idx * 8 + 4; | ||
143 | |||
144 | // Set the background parameters for the text layer. | ||
145 | BG_CTRL(bg) = BG_CHARBLOCK(cb_idx) | BG_SCREENBLOCK(sb_idx) | BG_PRIORITY(3); | ||
146 | |||
147 | // Load font data in video memory. Each character is unpacked into a tile of | ||
148 | // 8 32bit values (4bpp), meaning that for the full ASCII set of 256 | ||
149 | // characters, we need 8192 bytes of VRAM (8 * 4 * 256). | ||
150 | text_engine.font = font; | ||
151 | unpack_tiles(font.data, &TILE_MEM[cb_idx], 256); | ||
152 | |||
153 | // Initialize default values. | ||
154 | if (font.color == 0) { | ||
155 | font.color = COLOR_WHITE; | ||
156 | } | ||
157 | |||
158 | // Load palette color. | ||
159 | PAL_BUFFER_BG[1] = font.color; | ||
160 | |||
161 | // Update text_engine variables. | ||
162 | text_engine.memory = SCREENBLOCK_MEM[sb_idx]; | ||
163 | } | ||
164 | |||
165 | void | ||
166 | txt_init_bitmap(TextMode mode, Font font) { | ||
167 | // NOTE: Only mode 3 is currently supported | ||
168 | assert(mode == TXT_MODE_MODE3); | ||
169 | |||
170 | // If font_map is NULL, initialize the standard 0-255 character map. | ||
171 | if (font.char_map == NULL) { | ||
172 | for (size_t i = 0; i < 256; ++i) { | ||
173 | default_char_map[i] = i; | ||
174 | } | ||
175 | font.char_map = &default_char_map; | ||
176 | } | ||
177 | |||
178 | // Initialize default values if set to zero. | ||
179 | if (font.char_width == 0) { | ||
180 | font.char_width = 8; | ||
181 | } | ||
182 | if (font.char_height == 0) { | ||
183 | font.char_height = 8; | ||
184 | } | ||
185 | if (font.color == 0) { | ||
186 | font.color = COLOR_WHITE; | ||
187 | } | ||
188 | |||
189 | // Prepare text engine. | ||
190 | text_engine.font = font; | ||
191 | text_engine.mode = mode; | ||
192 | } | ||
193 | |||
194 | // Print text to the screen with formatting. | ||
195 | void | ||
196 | txt_printf(char *msg, ...) { | ||
197 | va_list arg_list; | ||
198 | va_start(arg_list, msg); | ||
199 | char buf[512] = {0}; | ||
200 | vsprintf(buf, msg, arg_list); | ||
201 | txt_puts(buf); | ||
202 | } | ||
203 | |||
204 | // TODO: Update for working on bitmap modes. | ||
205 | void | ||
206 | txt_clear_line(void) { | ||
207 | for (size_t i = 0; i < 30; ++i) { | ||
208 | text_engine.memory[i + 32 * text_engine.cursor_y] = ' '; | ||
209 | } | ||
210 | text_engine.cursor_x = 0; | ||
211 | } | ||
212 | |||
213 | // TODO: Update for working on bitmap modes. | ||
214 | void | ||
215 | txt_clear_screen(void) { | ||
216 | for (size_t j = 0; j < 20; ++j) { | ||
217 | for (size_t i = 0; i < 30; ++i) { | ||
218 | text_engine.memory[i + 32 * j] = ' '; | ||
219 | } | ||
220 | } | ||
221 | text_engine.cursor_x = 0; | ||
222 | text_engine.cursor_y = 0; | ||
223 | } | ||
224 | |||
225 | void | ||
226 | txt_position(size_t tile_x, size_t tile_y) { | ||
227 | text_engine.cursor_x = tile_x; | ||
228 | text_engine.cursor_y = tile_y; | ||
229 | } | ||
230 | |||
231 | #endif // GBAEXP_TILES_H | ||
232 | |||