File: | Widgets.c |
Warning: | line 2018, column 3 2nd function call argument is an uninitialized value |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | #include "Widgets.h" | |||
2 | #include "Graphics.h" | |||
3 | #include "Drawer2D.h" | |||
4 | #include "ExtMath.h" | |||
5 | #include "Funcs.h" | |||
6 | #include "Window.h" | |||
7 | #include "Inventory.h" | |||
8 | #include "IsometricDrawer.h" | |||
9 | #include "Utils.h" | |||
10 | #include "Model.h" | |||
11 | #include "Screens.h" | |||
12 | #include "Platform.h" | |||
13 | #include "Server.h" | |||
14 | #include "Event.h" | |||
15 | #include "Chat.h" | |||
16 | #include "Game.h" | |||
17 | #include "Logger.h" | |||
18 | #include "Bitmap.h" | |||
19 | #include "Block.h" | |||
20 | #include "Input.h" | |||
21 | ||||
22 | #define Widget_UV(u1,v1, u2,v2)u1/256.0f,v1/256.0f,u2/256.0f,v2/256.0f Tex_UV(u1/256.0f,v1/256.0f, u2/256.0f,v2/256.0f)u1/256.0f,v1/256.0f,u2/256.0f,v2/256.0f | |||
23 | static void Widget_NullFunc(void* widget) { } | |||
24 | static int Widget_Pointer(void* elem, int id, int x, int y) { return false0; } | |||
25 | static void Widget_InputUp(void* elem, int key) { } | |||
26 | static int Widget_InputDown(void* elem, int key) { return false0; } | |||
27 | static void Widget_PointerUp(void* elem, int id, int x, int y) { } | |||
28 | static int Widget_PointerMove(void* elem, int id, int x, int y) { return false0; } | |||
29 | static int Widget_MouseScroll(void* elem, float delta) { return false0; } | |||
30 | ||||
31 | /*########################################################################################################################* | |||
32 | *-------------------------------------------------------TextWidget--------------------------------------------------------* | |||
33 | *#########################################################################################################################*/ | |||
34 | static void TextWidget_Render(void* widget, double delta) { | |||
35 | struct TextWidget* w = (struct TextWidget*)widget; | |||
36 | if (w->tex.ID) Texture_RenderShaded(&w->tex, w->col); | |||
37 | } | |||
38 | ||||
39 | static void TextWidget_Free(void* widget) { | |||
40 | struct TextWidget* w = (struct TextWidget*)widget; | |||
41 | Gfx_DeleteTexture(&w->tex.ID); | |||
42 | } | |||
43 | ||||
44 | static void TextWidget_Reposition(void* widget) { | |||
45 | struct TextWidget* w = (struct TextWidget*)widget; | |||
46 | Widget_CalcPosition(w); | |||
47 | w->tex.X = w->x; w->tex.Y = w->y; | |||
48 | } | |||
49 | ||||
50 | static void TextWidget_BuildMesh(void* widget, struct VertexTextured** vertices) { | |||
51 | struct TextWidget* w = (struct TextWidget*)widget; | |||
52 | Gfx_Make2DQuad(&w->tex, w->col, vertices); | |||
53 | } | |||
54 | ||||
55 | static int TextWidget_Render2(void* widget, int offset) { | |||
56 | struct TextWidget* w = (struct TextWidget*)widget; | |||
57 | if (w->tex.ID) { | |||
58 | Gfx_BindTexture(w->tex.ID); | |||
59 | Gfx_DrawVb_IndexedTris_Range(4, offset); | |||
60 | } | |||
61 | return offset + 4; | |||
62 | } | |||
63 | ||||
64 | static const struct WidgetVTABLE TextWidget_VTABLE = { | |||
65 | TextWidget_Render, TextWidget_Free, TextWidget_Reposition, | |||
66 | Widget_InputDown, Widget_InputUp, Widget_MouseScroll, | |||
67 | Widget_Pointer, Widget_PointerUp, Widget_PointerMove, | |||
68 | TextWidget_BuildMesh, TextWidget_Render2 | |||
69 | }; | |||
70 | void TextWidget_Init(struct TextWidget* w) { | |||
71 | Widget_Reset(w); | |||
72 | w->VTABLE = &TextWidget_VTABLE; | |||
73 | w->col = PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )); | |||
74 | } | |||
75 | ||||
76 | void TextWidget_Set(struct TextWidget* w, const cc_string* text, struct FontDesc* font) { | |||
77 | struct DrawTextArgs args; | |||
78 | Gfx_DeleteTexture(&w->tex.ID); | |||
79 | ||||
80 | if (Drawer2D_IsEmptyText(text)) { | |||
81 | w->tex.Width = 0; | |||
82 | w->tex.Height = Drawer2D_FontHeight(font, true1); | |||
83 | } else { | |||
84 | DrawTextArgs_Make(&args, text, font, true1); | |||
85 | Drawer2D_MakeTextTexture(&w->tex, &args); | |||
86 | } | |||
87 | ||||
88 | w->width = w->tex.Width; w->height = w->tex.Height; | |||
89 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
90 | } | |||
91 | ||||
92 | void TextWidget_SetConst(struct TextWidget* w, const char* text, struct FontDesc* font) { | |||
93 | cc_string str = String_FromReadonly(text); | |||
94 | TextWidget_Set(w, &str, font); | |||
95 | } | |||
96 | ||||
97 | ||||
98 | /*########################################################################################################################* | |||
99 | *------------------------------------------------------ButtonWidget-------------------------------------------------------* | |||
100 | *#########################################################################################################################*/ | |||
101 | #define BUTTON_uWIDTH(200.0f / 256.0f) (200.0f / 256.0f) | |||
102 | ||||
103 | static struct Texture btnShadowTex = { 0, Tex_Rect(0,0, 0,0)0,0,0,0, Widget_UV(0,66, 200,86)0/256.0f,66/256.0f,200/256.0f,86/256.0f }; | |||
104 | static struct Texture btnSelectedTex = { 0, Tex_Rect(0,0, 0,0)0,0,0,0, Widget_UV(0,86, 200,106)0/256.0f,86/256.0f,200/256.0f,106/256.0f }; | |||
105 | static struct Texture btnDisabledTex = { 0, Tex_Rect(0,0, 0,0)0,0,0,0, Widget_UV(0,46, 200,66)0/256.0f,46/256.0f,200/256.0f,66/256.0f }; | |||
106 | ||||
107 | static void ButtonWidget_Free(void* widget) { | |||
108 | struct ButtonWidget* w = (struct ButtonWidget*)widget; | |||
109 | Gfx_DeleteTexture(&w->tex.ID); | |||
110 | } | |||
111 | ||||
112 | static void ButtonWidget_Reposition(void* widget) { | |||
113 | struct ButtonWidget* w = (struct ButtonWidget*)widget; | |||
114 | w->width = max(w->tex.Width, w->minWidth)((w->tex.Width) > (w->minWidth) ? (w->tex.Width) : (w->minWidth)); | |||
115 | w->height = max(w->tex.Height, w->minHeight)((w->tex.Height) > (w->minHeight) ? (w->tex.Height ) : (w->minHeight)); | |||
116 | ||||
117 | Widget_CalcPosition(w); | |||
118 | w->tex.X = w->x + (w->width / 2 - w->tex.Width / 2); | |||
119 | w->tex.Y = w->y + (w->height / 2 - w->tex.Height / 2); | |||
120 | } | |||
121 | ||||
122 | static void ButtonWidget_Render(void* widget, double delta) { | |||
123 | PackedCol normCol = PackedCol_Make(224, 224, 224, 255)(((cc_uint8)(224) << 0) | ((cc_uint8)(224) << 8) | ((cc_uint8)(224) << 16) | ((cc_uint8)(255) << 24 )); | |||
124 | PackedCol activeCol = PackedCol_Make(255, 255, 160, 255)(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(160) << 16) | ((cc_uint8)(255) << 24 )); | |||
125 | PackedCol disabledCol = PackedCol_Make(160, 160, 160, 255)(((cc_uint8)(160) << 0) | ((cc_uint8)(160) << 8) | ((cc_uint8)(160) << 16) | ((cc_uint8)(255) << 24 )); | |||
126 | PackedCol col; | |||
127 | ||||
128 | struct ButtonWidget* w = (struct ButtonWidget*)widget; | |||
129 | struct Texture back; | |||
130 | float scale; | |||
131 | ||||
132 | back = w->active ? btnSelectedTex : btnShadowTex; | |||
133 | if (w->disabled) back = btnDisabledTex; | |||
134 | ||||
135 | back.ID = Gui.ClassicTexture ? Gui.GuiClassicTex : Gui.GuiTex; | |||
136 | back.X = w->x; back.Width = w->width; | |||
137 | back.Y = w->y; back.Height = w->height; | |||
138 | ||||
139 | /* TODO: Does this 400 need to take DPI into account */ | |||
140 | if (w->width >= 400) { | |||
141 | /* Button can be drawn normally */ | |||
142 | Texture_Render(&back); | |||
143 | } else { | |||
144 | /* Split button down the middle */ | |||
145 | scale = (w->width / 400.0f) * 0.5f; | |||
146 | Gfx_BindTexture(back.ID); /* avoid bind twice */ | |||
147 | ||||
148 | back.Width = (w->width / 2); | |||
149 | back.uv.U1 = 0.0f; back.uv.U2 = BUTTON_uWIDTH(200.0f / 256.0f) * scale; | |||
150 | Gfx_Draw2DTexture(&back, w->col); | |||
151 | ||||
152 | back.X += (w->width / 2); | |||
153 | back.uv.U1 = BUTTON_uWIDTH(200.0f / 256.0f) * (1.0f - scale); back.uv.U2 = BUTTON_uWIDTH(200.0f / 256.0f); | |||
154 | Gfx_Draw2DTexture(&back, w->col); | |||
155 | } | |||
156 | ||||
157 | if (!w->tex.ID) return; | |||
158 | col = w->disabled ? disabledCol : (w->active ? activeCol : normCol); | |||
159 | Texture_RenderShaded(&w->tex, col); | |||
160 | } | |||
161 | ||||
162 | static void ButtonWidget_BuildMesh(void* widget, struct VertexTextured** vertices) { | |||
163 | PackedCol normCol = PackedCol_Make(224, 224, 224, 255)(((cc_uint8)(224) << 0) | ((cc_uint8)(224) << 8) | ((cc_uint8)(224) << 16) | ((cc_uint8)(255) << 24 )); | |||
164 | PackedCol activeCol = PackedCol_Make(255, 255, 160, 255)(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(160) << 16) | ((cc_uint8)(255) << 24 )); | |||
165 | PackedCol disabledCol = PackedCol_Make(160, 160, 160, 255)(((cc_uint8)(160) << 0) | ((cc_uint8)(160) << 8) | ((cc_uint8)(160) << 16) | ((cc_uint8)(255) << 24 )); | |||
166 | PackedCol col; | |||
167 | ||||
168 | struct ButtonWidget* w = (struct ButtonWidget*)widget; | |||
169 | struct Texture back; | |||
170 | float scale; | |||
171 | ||||
172 | back = w->active ? btnSelectedTex : btnShadowTex; | |||
173 | if (w->disabled) back = btnDisabledTex; | |||
174 | back.X = w->x; back.Width = w->width; | |||
175 | back.Y = w->y; back.Height = w->height; | |||
176 | ||||
177 | /* TODO: Does this 400 need to take DPI into account */ | |||
178 | if (w->width >= 400) { | |||
179 | /* Button can be drawn normally */ | |||
180 | Gfx_Make2DQuad(&back, w->col, vertices); | |||
181 | *vertices += 4; /* always use up 8 vertices for body */ | |||
182 | } else { | |||
183 | /* Split button down the middle */ | |||
184 | scale = (w->width / 400.0f) * 0.5f; | |||
185 | ||||
186 | back.Width = (w->width / 2); | |||
187 | back.uv.U1 = 0.0f; back.uv.U2 = BUTTON_uWIDTH(200.0f / 256.0f) * scale; | |||
188 | Gfx_Make2DQuad(&back, w->col, vertices); | |||
189 | ||||
190 | back.X += (w->width / 2); | |||
191 | back.uv.U1 = BUTTON_uWIDTH(200.0f / 256.0f) * (1.0f - scale); back.uv.U2 = BUTTON_uWIDTH(200.0f / 256.0f); | |||
192 | Gfx_Make2DQuad(&back, w->col, vertices); | |||
193 | } | |||
194 | ||||
195 | col = w->disabled ? disabledCol : (w->active ? activeCol : normCol); | |||
196 | Gfx_Make2DQuad(&w->tex, col, vertices); | |||
197 | } | |||
198 | ||||
199 | static int ButtonWidget_Render2(void* widget, int offset) { | |||
200 | struct ButtonWidget* w = (struct ButtonWidget*)widget; | |||
201 | Gfx_BindTexture(Gui.ClassicTexture ? Gui.GuiClassicTex : Gui.GuiTex); | |||
202 | /* TODO: Does this 400 need to take DPI into account */ | |||
203 | Gfx_DrawVb_IndexedTris_Range(w->width >= 400 ? 4 : 8, offset); | |||
204 | ||||
205 | if (w->tex.ID) { | |||
206 | Gfx_BindTexture(w->tex.ID); | |||
207 | Gfx_DrawVb_IndexedTris_Range(4, offset + 8); | |||
208 | } | |||
209 | return offset + 12; | |||
210 | } | |||
211 | ||||
212 | static const struct WidgetVTABLE ButtonWidget_VTABLE = { | |||
213 | ButtonWidget_Render, ButtonWidget_Free, ButtonWidget_Reposition, | |||
214 | Widget_InputDown, Widget_InputUp, Widget_MouseScroll, | |||
215 | Widget_Pointer, Widget_PointerUp, Widget_PointerMove, | |||
216 | ButtonWidget_BuildMesh, ButtonWidget_Render2 | |||
217 | }; | |||
218 | void ButtonWidget_Make(struct ButtonWidget* w, int minWidth, Widget_LeftClick onClick, cc_uint8 horAnchor, cc_uint8 verAnchor, int xOffset, int yOffset) { | |||
219 | ButtonWidget_Init(w, minWidth, onClick); | |||
220 | Widget_SetLocation(w, horAnchor, verAnchor, xOffset, yOffset); | |||
221 | } | |||
222 | ||||
223 | void ButtonWidget_Init(struct ButtonWidget* w, int minWidth, Widget_LeftClick onClick) { | |||
224 | Widget_Reset(w); | |||
225 | w->VTABLE = &ButtonWidget_VTABLE; | |||
226 | w->col = PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )); | |||
227 | w->optName = NULL((void*)0); | |||
228 | w->minWidth = Display_ScaleX(minWidth); | |||
229 | w->minHeight = Display_ScaleY(40); | |||
230 | w->MenuClick = onClick; | |||
231 | } | |||
232 | ||||
233 | void ButtonWidget_Set(struct ButtonWidget* w, const cc_string* text, struct FontDesc* font) { | |||
234 | struct DrawTextArgs args; | |||
235 | Gfx_DeleteTexture(&w->tex.ID); | |||
236 | ||||
237 | if (Drawer2D_IsEmptyText(text)) { | |||
238 | w->tex.Width = 0; | |||
239 | w->tex.Height = Drawer2D_FontHeight(font, true1); | |||
240 | } else { | |||
241 | DrawTextArgs_Make(&args, text, font, true1); | |||
242 | Drawer2D_MakeTextTexture(&w->tex, &args); | |||
243 | } | |||
244 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
245 | } | |||
246 | ||||
247 | void ButtonWidget_SetConst(struct ButtonWidget* w, const char* text, struct FontDesc* font) { | |||
248 | cc_string str = String_FromReadonly(text); | |||
249 | ButtonWidget_Set(w, &str, font); | |||
250 | } | |||
251 | ||||
252 | ||||
253 | /*########################################################################################################################* | |||
254 | *-----------------------------------------------------ScrollbarWidget-----------------------------------------------------* | |||
255 | *#########################################################################################################################*/ | |||
256 | #define SCROLL_BACK_COL(((cc_uint8)(10) << 0) | ((cc_uint8)(10) << 8) | ( (cc_uint8)(10) << 16) | ((cc_uint8)(220) << 24)) PackedCol_Make( 10, 10, 10, 220)(((cc_uint8)(10) << 0) | ((cc_uint8)(10) << 8) | ( (cc_uint8)(10) << 16) | ((cc_uint8)(220) << 24)) | |||
257 | #define SCROLL_BAR_COL(((cc_uint8)(100) << 0) | ((cc_uint8)(100) << 8) | ((cc_uint8)(100) << 16) | ((cc_uint8)(220) << 24 )) PackedCol_Make(100, 100, 100, 220)(((cc_uint8)(100) << 0) | ((cc_uint8)(100) << 8) | ((cc_uint8)(100) << 16) | ((cc_uint8)(220) << 24 )) | |||
258 | #define SCROLL_HOVER_COL(((cc_uint8)(122) << 0) | ((cc_uint8)(122) << 8) | ((cc_uint8)(122) << 16) | ((cc_uint8)(220) << 24 )) PackedCol_Make(122, 122, 122, 220)(((cc_uint8)(122) << 0) | ((cc_uint8)(122) << 8) | ((cc_uint8)(122) << 16) | ((cc_uint8)(220) << 24 )) | |||
259 | ||||
260 | static void ScrollbarWidget_ClampTopRow(struct ScrollbarWidget* w) { | |||
261 | int maxTop = w->rowsTotal - w->rowsVisible; | |||
262 | if (w->topRow >= maxTop) w->topRow = maxTop; | |||
263 | if (w->topRow < 0) w->topRow = 0; | |||
264 | } | |||
265 | ||||
266 | static float ScrollbarWidget_GetScale(struct ScrollbarWidget* w) { | |||
267 | float rows = (float)w->rowsTotal; | |||
268 | return (w->height - w->borderY * 2) / rows; | |||
269 | } | |||
270 | ||||
271 | static void ScrollbarWidget_GetScrollbarCoords(struct ScrollbarWidget* w, int* y, int* height) { | |||
272 | float scale = ScrollbarWidget_GetScale(w); | |||
273 | *y = Math_Ceil(w->topRow * scale) + w->borderY; | |||
274 | *height = Math_Ceil(w->rowsVisible * scale); | |||
275 | *height = min(*y + *height, w->height - w->borderY)((*y + *height) < (w->height - w->borderY) ? (*y + * height) : (w->height - w->borderY)) - *y; | |||
276 | } | |||
277 | ||||
278 | static void ScrollbarWidget_Render(void* widget, double delta) { | |||
279 | struct ScrollbarWidget* w = (struct ScrollbarWidget*)widget; | |||
280 | int x, y, width, height; | |||
281 | PackedCol barCol; | |||
282 | cc_bool hovered; | |||
283 | ||||
284 | x = w->x; width = w->width; | |||
285 | Gfx_Draw2DFlat(x, w->y, width, w->height, SCROLL_BACK_COL(((cc_uint8)(10) << 0) | ((cc_uint8)(10) << 8) | ( (cc_uint8)(10) << 16) | ((cc_uint8)(220) << 24))); | |||
286 | ||||
287 | ScrollbarWidget_GetScrollbarCoords(w, &y, &height); | |||
288 | x += w->borderX; y += w->y; | |||
289 | width -= w->borderX * 2; | |||
290 | ||||
291 | hovered = Gui_ContainsPointers(x, y, width, height); | |||
292 | barCol = hovered ? SCROLL_HOVER_COL(((cc_uint8)(122) << 0) | ((cc_uint8)(122) << 8) | ((cc_uint8)(122) << 16) | ((cc_uint8)(220) << 24 )) : SCROLL_BAR_COL(((cc_uint8)(100) << 0) | ((cc_uint8)(100) << 8) | ((cc_uint8)(100) << 16) | ((cc_uint8)(220) << 24 )); | |||
293 | Gfx_Draw2DFlat(x, y, width, height, barCol); | |||
294 | ||||
295 | if (height < 20) return; | |||
296 | x += w->nubsWidth; y += (height / 2); | |||
297 | width -= w->nubsWidth * 2; | |||
298 | ||||
299 | Gfx_Draw2DFlat(x, y + w->offsets[0], width, w->borderY, SCROLL_BACK_COL(((cc_uint8)(10) << 0) | ((cc_uint8)(10) << 8) | ( (cc_uint8)(10) << 16) | ((cc_uint8)(220) << 24))); | |||
300 | Gfx_Draw2DFlat(x, y + w->offsets[1], width, w->borderY, SCROLL_BACK_COL(((cc_uint8)(10) << 0) | ((cc_uint8)(10) << 8) | ( (cc_uint8)(10) << 16) | ((cc_uint8)(220) << 24))); | |||
301 | Gfx_Draw2DFlat(x, y + w->offsets[2], width, w->borderY, SCROLL_BACK_COL(((cc_uint8)(10) << 0) | ((cc_uint8)(10) << 8) | ( (cc_uint8)(10) << 16) | ((cc_uint8)(220) << 24))); | |||
302 | } | |||
303 | ||||
304 | static int ScrollbarWidget_PointerDown(void* widget, int id, int x, int y) { | |||
305 | struct ScrollbarWidget* w = (struct ScrollbarWidget*)widget; | |||
306 | int posY, height; | |||
307 | ||||
308 | if (w->draggingId == id) return TOUCH_TYPE_GUI1; | |||
309 | if (x < w->x || x >= w->x + w->width + w->padding) return false0; | |||
310 | /* only intercept pointer that's dragging scrollbar */ | |||
311 | if (w->draggingId) return false0; | |||
312 | ||||
313 | y -= w->y; | |||
314 | ScrollbarWidget_GetScrollbarCoords(w, &posY, &height); | |||
315 | ||||
316 | if (y < posY) { | |||
317 | w->topRow -= w->rowsVisible; | |||
318 | } else if (y >= posY + height) { | |||
319 | w->topRow += w->rowsVisible; | |||
320 | } else { | |||
321 | w->draggingId = id; | |||
322 | w->dragOffset = y - posY; | |||
323 | } | |||
324 | ScrollbarWidget_ClampTopRow(w); | |||
325 | return TOUCH_TYPE_GUI1; | |||
326 | } | |||
327 | ||||
328 | static void ScrollbarWidget_PointerUp(void* widget, int id, int x, int y) { | |||
329 | struct ScrollbarWidget* w = (struct ScrollbarWidget*)widget; | |||
330 | if (w->draggingId != id) return; | |||
331 | w->draggingId = 0; | |||
332 | w->dragOffset = 0; | |||
333 | } | |||
334 | ||||
335 | static int ScrollbarWidget_MouseScroll(void* widget, float delta) { | |||
336 | struct ScrollbarWidget* w = (struct ScrollbarWidget*)widget; | |||
337 | int steps = Utils_AccumulateWheelDelta(&w->scrollingAcc, delta); | |||
338 | ||||
339 | w->topRow -= steps; | |||
340 | ScrollbarWidget_ClampTopRow(w); | |||
341 | return true1; | |||
342 | } | |||
343 | ||||
344 | static int ScrollbarWidget_PointerMove(void* widget, int id, int x, int y) { | |||
345 | struct ScrollbarWidget* w = (struct ScrollbarWidget*)widget; | |||
346 | float scale; | |||
347 | ||||
348 | if (w->draggingId == id) { | |||
349 | y -= w->y; | |||
350 | scale = ScrollbarWidget_GetScale(w); | |||
351 | w->topRow = (int)((y - w->dragOffset) / scale); | |||
352 | ScrollbarWidget_ClampTopRow(w); | |||
353 | return true1; | |||
354 | } | |||
355 | return false0; | |||
356 | } | |||
357 | ||||
358 | static const struct WidgetVTABLE ScrollbarWidget_VTABLE = { | |||
359 | ScrollbarWidget_Render, Widget_NullFunc, Widget_CalcPosition, | |||
360 | Widget_InputDown, Widget_InputUp, ScrollbarWidget_MouseScroll, | |||
361 | ScrollbarWidget_PointerDown, ScrollbarWidget_PointerUp, ScrollbarWidget_PointerMove | |||
362 | }; | |||
363 | void ScrollbarWidget_Create(struct ScrollbarWidget* w) { | |||
364 | Widget_Reset(w); | |||
365 | w->VTABLE = &ScrollbarWidget_VTABLE; | |||
366 | w->width = Display_ScaleX(22); | |||
367 | w->borderX = Display_ScaleX(2); | |||
368 | w->borderY = Display_ScaleY(2); | |||
369 | w->nubsWidth = Display_ScaleX(3); | |||
370 | ||||
371 | w->offsets[0] = Display_ScaleY(-1 - 4); | |||
372 | w->offsets[1] = Display_ScaleY(-1); | |||
373 | w->offsets[2] = Display_ScaleY(-1 + 4); | |||
374 | ||||
375 | w->rowsTotal = 0; | |||
376 | w->rowsVisible = 0; | |||
377 | w->topRow = 0; | |||
378 | w->scrollingAcc = 0.0f; | |||
379 | w->draggingId = 0; | |||
380 | w->dragOffset = 0; | |||
381 | ||||
382 | /* It's easy to accidentally touch a bit to the right of the */ | |||
383 | /* scrollbar with your finger, so just add some padding */ | |||
384 | if (!Input_TouchMode0) return; | |||
385 | w->padding = Display_ScaleX(15); | |||
386 | } | |||
387 | ||||
388 | ||||
389 | /*########################################################################################################################* | |||
390 | *------------------------------------------------------HotbarWidget-------------------------------------------------------* | |||
391 | *#########################################################################################################################*/ | |||
392 | #define HotbarWidget_TileX(w, idx)(int)(w->x + w->slotXOffset + w->slotWidth * (idx)) (int)(w->x + w->slotXOffset + w->slotWidth * (idx)) | |||
393 | ||||
394 | static void HotbarWidget_RenderHotbarOutline(struct HotbarWidget* w) { | |||
395 | PackedCol white = PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )); | |||
396 | GfxResourceID tex; | |||
397 | int x; | |||
398 | ||||
399 | tex = Gui.ClassicTexture ? Gui.GuiClassicTex : Gui.GuiTex; | |||
400 | w->backTex.ID = tex; | |||
401 | Texture_Render(&w->backTex); | |||
402 | ||||
403 | x = HotbarWidget_TileX(w, Inventory.SelectedIndex)(int)(w->x + w->slotXOffset + w->slotWidth * (Inventory .SelectedIndex)); | |||
404 | w->selTex.ID = tex; | |||
405 | w->selTex.X = (int)(x - w->selWidth / 2); | |||
406 | Gfx_Draw2DTexture(&w->selTex, white); | |||
407 | } | |||
408 | ||||
409 | static void HotbarWidget_RenderHotbarBlocks(struct HotbarWidget* w) { | |||
410 | /* TODO: Should hotbar use its own VB? */ | |||
411 | struct VertexTextured vertices[INVENTORY_BLOCKS_PER_HOTBAR9 * ISOMETRICDRAWER_MAXVERTICES16]; | |||
412 | float scale; | |||
413 | int i, x, y; | |||
414 | ||||
415 | IsometricDrawer_BeginBatch(vertices, Models.Vb); | |||
416 | scale = w->elemSize / 2.0f; | |||
417 | ||||
418 | for (i = 0; i < INVENTORY_BLOCKS_PER_HOTBAR9; i++) { | |||
419 | x = HotbarWidget_TileX(w, i)(int)(w->x + w->slotXOffset + w->slotWidth * (i)); | |||
420 | y = w->y + (w->height / 2); | |||
421 | ||||
422 | #ifdef CC_BUILD_TOUCH | |||
423 | if (i == HOTBAR_MAX_INDEX(9 - 1) && Input_TouchMode0) continue; | |||
424 | #endif | |||
425 | IsometricDrawer_DrawBatch(Inventory_Get(i)(Inventory.Table[Inventory.Offset + (i)]), scale, x, y); | |||
426 | } | |||
427 | IsometricDrawer_EndBatch(); | |||
428 | } | |||
429 | ||||
430 | static int HotbarWidget_ScrolledIndex(struct HotbarWidget* w, float delta, int index, int dir) { | |||
431 | int steps = Utils_AccumulateWheelDelta(&w->scrollAcc, delta); | |||
432 | index += (dir * steps) % INVENTORY_BLOCKS_PER_HOTBAR9; | |||
433 | ||||
434 | if (index < 0) index += INVENTORY_BLOCKS_PER_HOTBAR9; | |||
435 | if (index >= INVENTORY_BLOCKS_PER_HOTBAR9) { | |||
436 | index -= INVENTORY_BLOCKS_PER_HOTBAR9; | |||
437 | } | |||
438 | return index; | |||
439 | } | |||
440 | ||||
441 | static void HotbarWidget_Reposition(void* widget) { | |||
442 | struct HotbarWidget* w = (struct HotbarWidget*)widget; | |||
443 | float scaleX = w->scale * DisplayInfo.ScaleX; | |||
444 | float scaleY = w->scale * DisplayInfo.ScaleY; | |||
445 | int y; | |||
446 | ||||
447 | w->width = (int)(182 * scaleX); | |||
448 | w->height = Math_Floor(22.0f * scaleY); | |||
449 | Widget_CalcPosition(w); | |||
450 | ||||
451 | w->selWidth = (float)Math_Ceil(24.0f * scaleX); | |||
452 | w->elemSize = 13.5f * scaleX; | |||
453 | w->slotXOffset = 11.1f * scaleX; | |||
454 | w->slotWidth = 20.0f * scaleX; | |||
455 | ||||
456 | Tex_SetRect(w->backTex, w->x,w->y, w->width,w->height)w->backTex.X = w->x; w->backTex.Y = w->y; w->backTex .Width = w->width; w->backTex.Height = w->height;; | |||
457 | Tex_SetUV(w->backTex, 0,0, 182/256.0f,22/256.0f)w->backTex.uv.U1 = 0; w->backTex.uv.V1 = 0; w->backTex .uv.U2 = 182/256.0f; w->backTex.uv.V2 = 22/256.0f;; | |||
458 | ||||
459 | y = w->y + (w->height - (int)(23.0f * scaleY)); | |||
460 | Tex_SetRect(w->selTex, 0,y, (int)w->selWidth,w->height)w->selTex.X = 0; w->selTex.Y = y; w->selTex.Width = ( int)w->selWidth; w->selTex.Height = w->height;; | |||
461 | Tex_SetUV(w->selTex, 0,22/256.0f, 24/256.0f,44/256.0f)w->selTex.uv.U1 = 0; w->selTex.uv.V1 = 22/256.0f; w-> selTex.uv.U2 = 24/256.0f; w->selTex.uv.V2 = 44/256.0f;; | |||
462 | } | |||
463 | ||||
464 | static void HotbarWidget_Render(void* widget, double delta) { | |||
465 | struct HotbarWidget* w = (struct HotbarWidget*)widget; | |||
466 | HotbarWidget_RenderHotbarOutline(w); | |||
467 | HotbarWidget_RenderHotbarBlocks(w); | |||
468 | ||||
469 | #ifdef CC_BUILD_TOUCH | |||
470 | if (!Input_TouchMode0) return; | |||
471 | w->ellipsisTex.X = HotbarWidget_TileX(w, HOTBAR_MAX_INDEX)(int)(w->x + w->slotXOffset + w->slotWidth * ((9 - 1 ))) - w->ellipsisTex.Width / 2; | |||
472 | w->ellipsisTex.Y = w->y + (w->height / 2) - w->ellipsisTex.Height / 2; | |||
473 | Texture_Render(&w->ellipsisTex); | |||
474 | #endif | |||
475 | } | |||
476 | ||||
477 | static int HotbarWidget_KeyDown(void* widget, int key) { | |||
478 | struct HotbarWidget* w = (struct HotbarWidget*)widget; | |||
479 | int index; | |||
480 | if (key < '1' || key > '9') return false0; | |||
481 | ||||
482 | index = key - '1'; | |||
483 | if (KeyBind_IsPressed(KEYBIND_HOTBAR_SWITCH)) { | |||
484 | /* Pick from first to ninth row */ | |||
485 | Inventory_SetHotbarIndex(index); | |||
486 | w->altHandled = true1; | |||
487 | } else { | |||
488 | Inventory_SetSelectedIndex(index); | |||
489 | } | |||
490 | return true1; | |||
491 | } | |||
492 | ||||
493 | static void HotbarWidget_InputUp(void* widget, int key) { | |||
494 | struct HotbarWidget* w = (struct HotbarWidget*)widget; | |||
495 | /* Need to handle these cases: | |||
496 | a) user presses alt then number | |||
497 | b) user presses alt | |||
498 | We only do case b) if case a) did not happen */ | |||
499 | if (key != KeyBinds[KEYBIND_HOTBAR_SWITCH]) return; | |||
500 | if (w->altHandled) { w->altHandled = false0; return; } /* handled already */ | |||
501 | ||||
502 | /* Don't switch hotbar when alt+tabbing to another window */ | |||
503 | if (WindowInfo.Focused) Inventory_SwitchHotbar(); | |||
504 | } | |||
505 | ||||
506 | static int HotbarWidget_PointerDown(void* widget, int id, int x, int y) { | |||
507 | struct HotbarWidget* w = (struct HotbarWidget*)widget; | |||
508 | int width, height; | |||
509 | int i, cellX, cellY; | |||
510 | ||||
511 | if (!Widget_Contains(w, x, y)) return false0; | |||
512 | width = (int)w->slotWidth; | |||
513 | height = w->height; | |||
514 | ||||
515 | for (i = 0; i < INVENTORY_BLOCKS_PER_HOTBAR9; i++) { | |||
516 | cellX = (int)(w->x + width * i); | |||
517 | cellY = w->y; | |||
518 | if (!Gui_Contains(cellX, cellY, width, height, x, y)) continue; | |||
519 | ||||
520 | #ifdef CC_BUILD_TOUCH | |||
521 | if (i == HOTBAR_MAX_INDEX(9 - 1) && Input_TouchMode0) { | |||
522 | InventoryScreen_Show(); return TOUCH_TYPE_GUI1; | |||
523 | } | |||
524 | #endif | |||
525 | Inventory_SetSelectedIndex(i); | |||
526 | return TOUCH_TYPE_GUI1; | |||
527 | } | |||
528 | return false0; | |||
529 | } | |||
530 | ||||
531 | static int HotbarWidget_MouseScroll(void* widget, float delta) { | |||
532 | struct HotbarWidget* w = (struct HotbarWidget*)widget; | |||
533 | int index; | |||
534 | ||||
535 | if (KeyBind_IsPressed(KEYBIND_HOTBAR_SWITCH)) { | |||
536 | index = Inventory.Offset / INVENTORY_BLOCKS_PER_HOTBAR9; | |||
537 | index = HotbarWidget_ScrolledIndex(w, delta, index, 1); | |||
538 | Inventory_SetHotbarIndex(index); | |||
539 | w->altHandled = true1; | |||
540 | } else { | |||
541 | index = HotbarWidget_ScrolledIndex(w, delta, Inventory.SelectedIndex, -1); | |||
542 | Inventory_SetSelectedIndex(index); | |||
543 | } | |||
544 | return true1; | |||
545 | } | |||
546 | ||||
547 | static void HotbarWidget_Free(void* widget) { | |||
548 | #ifdef CC_BUILD_TOUCH | |||
549 | struct HotbarWidget* w = (struct HotbarWidget*)widget; | |||
550 | if (!Input_TouchMode0) return; | |||
551 | ||||
552 | Gfx_DeleteTexture(&w->ellipsisTex.ID); | |||
553 | #endif | |||
554 | } | |||
555 | ||||
556 | static const struct WidgetVTABLE HotbarWidget_VTABLE = { | |||
557 | HotbarWidget_Render, HotbarWidget_Free, HotbarWidget_Reposition, | |||
558 | HotbarWidget_KeyDown, HotbarWidget_InputUp, HotbarWidget_MouseScroll, | |||
559 | HotbarWidget_PointerDown, Widget_PointerUp, Widget_PointerMove | |||
560 | }; | |||
561 | void HotbarWidget_Create(struct HotbarWidget* w) { | |||
562 | Widget_Reset(w); | |||
563 | w->VTABLE = &HotbarWidget_VTABLE; | |||
564 | w->horAnchor = ANCHOR_CENTRE; | |||
565 | w->verAnchor = ANCHOR_MAX; | |||
566 | w->scale = 1; | |||
567 | } | |||
568 | ||||
569 | void HotbarWidget_SetFont(struct HotbarWidget* w, struct FontDesc* font) { | |||
570 | #ifdef CC_BUILD_TOUCH | |||
571 | static const cc_string dots = String_FromConst("..."){ "...", (sizeof("...") - 1), (sizeof("...") - 1)}; | |||
572 | struct DrawTextArgs args; | |||
573 | if (!Input_TouchMode0) return; | |||
574 | ||||
575 | DrawTextArgs_Make(&args, &dots, font, true1); | |||
576 | Drawer2D_MakeTextTexture(&w->ellipsisTex, &args); | |||
577 | #endif | |||
578 | } | |||
579 | ||||
580 | ||||
581 | /*########################################################################################################################* | |||
582 | *-------------------------------------------------------TableWidget-------------------------------------------------------* | |||
583 | *#########################################################################################################################*/ | |||
584 | static int Table_X(struct TableWidget* w) { return w->x - w->paddingX; } | |||
585 | static int Table_Y(struct TableWidget* w) { return w->y - w->paddingTopY; } | |||
586 | static int Table_Width(struct TableWidget* w) { | |||
587 | return w->blocksPerRow * w->cellSizeX + w->paddingX * 2; | |||
588 | } | |||
589 | static int Table_Height(struct TableWidget* w) { | |||
590 | return w->rowsVisible * w->cellSizeY + w->paddingTopY + w->paddingMaxY; | |||
591 | } | |||
592 | ||||
593 | #define TABLE_MAX_VERTICES(8 * 10 * 16) (8 * 10 * ISOMETRICDRAWER_MAXVERTICES16) | |||
594 | ||||
595 | static cc_bool TableWidget_GetCoords(struct TableWidget* w, int i, int* cellX, int* cellY) { | |||
596 | int x, y; | |||
597 | x = i % w->blocksPerRow; | |||
598 | y = i / w->blocksPerRow - w->scroll.topRow; | |||
599 | ||||
600 | *cellX = w->x + w->cellSizeX * x; | |||
601 | *cellY = w->y + w->cellSizeY * y + 3; | |||
602 | return y >= 0 && y < w->rowsVisible; | |||
603 | } | |||
604 | ||||
605 | static void TableWidget_MoveCursorToSelected(struct TableWidget* w) { | |||
606 | int x, y, idx; | |||
607 | if (w->selectedIndex == -1) return; | |||
608 | ||||
609 | idx = w->selectedIndex; | |||
610 | TableWidget_GetCoords(w, idx, &x, &y); | |||
611 | ||||
612 | x += w->cellSizeX / 2; y += w->cellSizeY / 2; | |||
613 | Cursor_SetPosition(x, y); | |||
614 | } | |||
615 | ||||
616 | static void TableWidget_MakeBlockDesc(cc_string* desc, BlockID block) { | |||
617 | cc_string name; | |||
618 | int block_ = block; | |||
619 | if (Game_PureClassic(Game_ClassicMode && !Game_ClassicHacks)) { String_AppendConst(desc, "Select block"); return; } | |||
620 | if (block == BLOCK_AIR) return; | |||
621 | ||||
622 | name = Block_UNSAFE_GetName(block); | |||
623 | String_AppendString(desc, &name); | |||
624 | if (Game_ClassicMode) return; | |||
625 | ||||
626 | String_Format1(desc, " (ID %i&f", &block_); | |||
627 | if (!Blocks.CanPlace[block]) { String_AppendConst(desc, ", place &cNo&f"); } | |||
628 | if (!Blocks.CanDelete[block]) { String_AppendConst(desc, ", delete &cNo&f"); } | |||
629 | String_Append(desc, ')'); | |||
630 | } | |||
631 | ||||
632 | static void TableWidget_UpdateDescTexPos(struct TableWidget* w) { | |||
633 | w->descTex.X = w->x + w->width / 2 - w->descTex.Width / 2; | |||
634 | w->descTex.Y = w->y - w->descTex.Height - 5; | |||
635 | } | |||
636 | ||||
637 | static void TableWidget_RecreateDescTex(struct TableWidget* w) { | |||
638 | if (w->selectedIndex == w->lastCreatedIndex) return; | |||
639 | if (w->blocksCount == 0) return; | |||
640 | w->lastCreatedIndex = w->selectedIndex; | |||
641 | ||||
642 | Gfx_DeleteTexture(&w->descTex.ID); | |||
643 | if (w->selectedIndex == -1) return; | |||
644 | TableWidget_MakeDescTex(w, w->blocks[w->selectedIndex]); | |||
645 | } | |||
646 | ||||
647 | void TableWidget_MakeDescTex(struct TableWidget* w, BlockID block) { | |||
648 | cc_string desc; char descBuffer[STRING_SIZE64 * 2]; | |||
649 | struct DrawTextArgs args; | |||
650 | ||||
651 | Gfx_DeleteTexture(&w->descTex.ID); | |||
652 | String_InitArray(desc, descBuffer)desc.buffer = descBuffer; desc.length = 0; desc.capacity = sizeof (descBuffer);; | |||
653 | TableWidget_MakeBlockDesc(&desc, block); | |||
654 | if (!desc.length) return; | |||
655 | ||||
656 | DrawTextArgs_Make(&args, &desc, w->font, true1); | |||
657 | Drawer2D_MakeTextTexture(&w->descTex, &args); | |||
658 | TableWidget_UpdateDescTexPos(w); | |||
659 | } | |||
660 | ||||
661 | static cc_bool TableWidget_RowEmpty(struct TableWidget* w, int start) { | |||
662 | int i, end = min(start + w->blocksPerRow, Array_Elems(Inventory.Map))((start + w->blocksPerRow) < ((sizeof(Inventory.Map) / sizeof (Inventory.Map[0]))) ? (start + w->blocksPerRow) : ((sizeof (Inventory.Map) / sizeof(Inventory.Map[0])))); | |||
663 | ||||
664 | for (i = start; i < end; i++) { | |||
665 | if (Inventory.Map[i] != BLOCK_AIR) return false0; | |||
666 | } | |||
667 | return true1; | |||
668 | } | |||
669 | ||||
670 | void TableWidget_RecreateBlocks(struct TableWidget* w) { | |||
671 | int i, max = Game_UseCPEBlocks ? BLOCK_COUNT : BLOCK_ORIGINAL_COUNT; | |||
672 | BlockID block; | |||
673 | w->blocksCount = 0; | |||
674 | ||||
675 | for (i = 0; i < Array_Elems(Inventory.Map)(sizeof(Inventory.Map) / sizeof(Inventory.Map[0])); ) { | |||
676 | if ((i % w->blocksPerRow) == 0 && TableWidget_RowEmpty(w, i)) { | |||
677 | i += w->blocksPerRow; continue; | |||
678 | } | |||
679 | ||||
680 | block = Inventory.Map[i]; | |||
681 | if (block < max) { w->blocks[w->blocksCount++] = block; } | |||
682 | i++; | |||
683 | } | |||
684 | ||||
685 | w->rowsTotal = Math_CeilDiv(w->blocksCount, w->blocksPerRow); | |||
686 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
687 | } | |||
688 | ||||
689 | static void TableWidget_Render(void* widget, double delta) { | |||
690 | struct TableWidget* w = (struct TableWidget*)widget; | |||
691 | struct VertexTextured vertices[TABLE_MAX_VERTICES(8 * 10 * 16)]; | |||
692 | int cellSizeX, cellSizeY, size; | |||
693 | float off; | |||
694 | int i, x, y; | |||
695 | ||||
696 | /* These were sourced by taking a screenshot of vanilla */ | |||
697 | /* Then using paint to extract the colour components */ | |||
698 | /* Then using wolfram alpha to solve the glblendfunc equation */ | |||
699 | PackedCol topBackCol = PackedCol_Make( 34, 34, 34, 168)(((cc_uint8)(34) << 0) | ((cc_uint8)(34) << 8) | ( (cc_uint8)(34) << 16) | ((cc_uint8)(168) << 24)); | |||
700 | PackedCol bottomBackCol = PackedCol_Make( 57, 57, 104, 202)(((cc_uint8)(57) << 0) | ((cc_uint8)(57) << 8) | ( (cc_uint8)(104) << 16) | ((cc_uint8)(202) << 24)); | |||
701 | PackedCol topSelCol = PackedCol_Make(255, 255, 255, 142)(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(142) << 24 )); | |||
702 | PackedCol bottomSelCol = PackedCol_Make(255, 255, 255, 192)(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(192) << 24 )); | |||
703 | ||||
704 | Gfx_Draw2DGradient(Table_X(w), Table_Y(w), | |||
705 | Table_Width(w), Table_Height(w), topBackCol, bottomBackCol); | |||
706 | ||||
707 | if (w->rowsVisible < w->rowsTotal) { | |||
708 | Elem_Render(&w->scroll, delta)(&w->scroll)->VTABLE->Render(&w->scroll, delta ); | |||
709 | } | |||
710 | ||||
711 | cellSizeX = w->cellSizeX; | |||
712 | cellSizeY = w->cellSizeY; | |||
713 | if (w->selectedIndex != -1 && Game_ClassicMode && w->blocks[w->selectedIndex] != BLOCK_AIR) { | |||
714 | TableWidget_GetCoords(w, w->selectedIndex, &x, &y); | |||
715 | ||||
716 | /* TODO: Need two size arguments, in case X/Y dpi differs */ | |||
717 | off = cellSizeX * 0.1f; | |||
718 | size = (int)(cellSizeX + off * 2); | |||
719 | Gfx_Draw2DGradient((int)(x - off), (int)(y - off), | |||
720 | size, size, topSelCol, bottomSelCol); | |||
721 | } | |||
722 | Gfx_SetTexturing(true1); | |||
723 | Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED); | |||
724 | ||||
725 | IsometricDrawer_BeginBatch(vertices, w->vb); | |||
726 | for (i = 0; i < w->blocksCount; i++) { | |||
727 | if (!TableWidget_GetCoords(w, i, &x, &y)) continue; | |||
728 | ||||
729 | /* We want to always draw the selected block on top of others */ | |||
730 | /* TODO: Need two size arguments, in case X/Y dpi differs */ | |||
731 | if (i == w->selectedIndex) continue; | |||
732 | IsometricDrawer_DrawBatch(w->blocks[i], cellSizeX * 0.7f / 2.0f, | |||
733 | x + cellSizeX / 2, y + cellSizeY / 2); | |||
734 | } | |||
735 | ||||
736 | i = w->selectedIndex; | |||
737 | if (i != -1) { | |||
738 | TableWidget_GetCoords(w, i, &x, &y); | |||
739 | ||||
740 | IsometricDrawer_DrawBatch(w->blocks[i], | |||
741 | (cellSizeX + w->selBlockExpand) * 0.7f / 2.0f, | |||
742 | x + cellSizeX / 2, y + cellSizeY / 2); | |||
743 | } | |||
744 | IsometricDrawer_EndBatch(); | |||
745 | ||||
746 | if (w->descTex.ID) { Texture_Render(&w->descTex); } | |||
747 | Gfx_SetTexturing(false0); | |||
748 | } | |||
749 | ||||
750 | static void TableWidget_Free(void* widget) { | |||
751 | struct TableWidget* w = (struct TableWidget*)widget; | |||
752 | Gfx_DeleteDynamicVbGfx_DeleteVb(&w->vb); | |||
753 | Gfx_DeleteTexture(&w->descTex.ID); | |||
754 | w->lastCreatedIndex = -1000; | |||
755 | } | |||
756 | ||||
757 | void TableWidget_Recreate(struct TableWidget* w) { | |||
758 | Elem_Free(w)(w)->VTABLE->Free(w); | |||
759 | Gfx_RecreateDynamicVb(&w->vb, VERTEX_FORMAT_TEXTURED, TABLE_MAX_VERTICES(8 * 10 * 16)); | |||
760 | TableWidget_RecreateDescTex(w); | |||
761 | } | |||
762 | ||||
763 | static void TableWidget_Reposition(void* widget) { | |||
764 | struct TableWidget* w = (struct TableWidget*)widget; | |||
765 | float scale = w->scale; | |||
766 | int cellSize; | |||
767 | ||||
768 | cellSize = (int)(50 * Math_SqrtF(scale)__builtin_sqrtf(scale)); | |||
769 | w->cellSizeX = Display_ScaleX(cellSize); | |||
770 | w->cellSizeY = Display_ScaleY(cellSize); | |||
771 | ||||
772 | w->selBlockExpand = 25.0f * Math_SqrtF(scale)__builtin_sqrtf(scale); | |||
773 | w->rowsVisible = min(8, w->rowsTotal)((8) < (w->rowsTotal) ? (8) : (w->rowsTotal)); /* 8 rows max */ | |||
774 | ||||
775 | do { | |||
776 | w->width = w->cellSizeX * w->blocksPerRow; | |||
777 | w->height = w->cellSizeY * w->rowsVisible; | |||
778 | Widget_CalcPosition(w); | |||
779 | TableWidget_UpdateDescTexPos(w); | |||
780 | ||||
781 | /* Does the table fit on screen? */ | |||
782 | if (Game_ClassicMode || Table_Y(w) >= 0) break; | |||
783 | w->rowsVisible--; | |||
784 | } while (w->rowsVisible > 1); | |||
785 | ||||
786 | w->scroll.x = Table_X(w) + Table_Width(w); | |||
787 | w->scroll.y = Table_Y(w); | |||
788 | w->scroll.height = Table_Height(w); | |||
789 | w->scroll.rowsTotal = w->rowsTotal; | |||
790 | w->scroll.rowsVisible = w->rowsVisible; | |||
791 | } | |||
792 | ||||
793 | static void TableWidget_ScrollRelative(struct TableWidget* w, int delta) { | |||
794 | int start = w->selectedIndex, index = start; | |||
795 | index += delta; | |||
796 | if (index < 0) index -= delta; | |||
797 | if (index >= w->blocksCount) index -= delta; | |||
798 | w->selectedIndex = index; | |||
799 | ||||
800 | /* adjust scrollbar by number of rows moved up/down */ | |||
801 | w->scroll.topRow += (index / w->blocksPerRow) - (start / w->blocksPerRow); | |||
802 | ScrollbarWidget_ClampTopRow(&w->scroll); | |||
803 | ||||
804 | TableWidget_RecreateDescTex(w); | |||
805 | TableWidget_MoveCursorToSelected(w); | |||
806 | } | |||
807 | ||||
808 | static int TableWidget_PointerDown(void* widget, int id, int x, int y) { | |||
809 | struct TableWidget* w = (struct TableWidget*)widget; | |||
810 | w->pendingClose = false0; | |||
811 | ||||
812 | if (Elem_HandlesPointerDown(&w->scroll, id, x, y)(&w->scroll)->VTABLE->HandlesPointerDown(&w-> scroll, id, x, y)) { | |||
813 | return TOUCH_TYPE_GUI1; | |||
814 | } else if (w->selectedIndex != -1 && w->blocks[w->selectedIndex] != BLOCK_AIR) { | |||
815 | Inventory_SetSelectedBlock(w->blocks[w->selectedIndex]); | |||
816 | w->pendingClose = true1; | |||
817 | return TOUCH_TYPE_GUI1; | |||
818 | } else if (Gui_Contains(Table_X(w), Table_Y(w), Table_Width(w), Table_Height(w), x, y)) { | |||
819 | return TOUCH_TYPE_GUI1; | |||
820 | } | |||
821 | return false0; | |||
822 | } | |||
823 | ||||
824 | static void TableWidget_PointerUp(void* widget, int id, int x, int y) { | |||
825 | struct TableWidget* w = (struct TableWidget*)widget; | |||
826 | Elem_OnPointerUp(&w->scroll, id, x, y)(&w->scroll)->VTABLE->OnPointerUp(&w->scroll , id, x, y); | |||
827 | } | |||
828 | ||||
829 | static int TableWidget_MouseScroll(void* widget, float delta) { | |||
830 | struct TableWidget* w = (struct TableWidget*)widget; | |||
831 | int origTopRow, index; | |||
832 | ||||
833 | cc_bool bounds = Gui_ContainsPointers(Table_X(w), Table_Y(w), | |||
834 | Table_Width(w) + w->scroll.width, Table_Height(w)); | |||
835 | if (!bounds) return false0; | |||
836 | ||||
837 | origTopRow = w->scroll.topRow; | |||
838 | Elem_HandlesMouseScroll(&w->scroll, delta)(&w->scroll)->VTABLE->HandlesMouseScroll(&w-> scroll, delta); | |||
839 | if (w->selectedIndex == -1) return true1; | |||
840 | ||||
841 | index = w->selectedIndex; | |||
842 | index += (w->scroll.topRow - origTopRow) * w->blocksPerRow; | |||
843 | if (index >= w->blocksCount) index = -1; | |||
844 | ||||
845 | w->selectedIndex = index; | |||
846 | TableWidget_RecreateDescTex(w); | |||
847 | return true1; | |||
848 | } | |||
849 | ||||
850 | static int TableWidget_PointerMove(void* widget, int id, int x, int y) { | |||
851 | struct TableWidget* w = (struct TableWidget*)widget; | |||
852 | int cellSizeX, cellSizeY, maxHeight; | |||
853 | int i, cellX, cellY; | |||
854 | ||||
855 | if (Elem_HandlesPointerMove(&w->scroll, id, x, y)(&w->scroll)->VTABLE->HandlesPointerMove(&w-> scroll, id, x, y)) return true1; | |||
856 | if (w->lastX == x && w->lastY == y) return true1; | |||
857 | w->lastX = x; w->lastY = y; | |||
858 | ||||
859 | w->selectedIndex = -1; | |||
860 | cellSizeX = w->cellSizeX; | |||
861 | cellSizeY = w->cellSizeY; | |||
862 | maxHeight = cellSizeY * w->rowsVisible; | |||
863 | ||||
864 | if (Gui_Contains(w->x, w->y + 3, w->width, maxHeight - 3 * 2, x, y)) { | |||
865 | for (i = 0; i < w->blocksCount; i++) { | |||
866 | TableWidget_GetCoords(w, i, &cellX, &cellY); | |||
867 | ||||
868 | if (Gui_Contains(cellX, cellY, cellSizeX, cellSizeY, x, y)) { | |||
869 | w->selectedIndex = i; | |||
870 | break; | |||
871 | } | |||
872 | } | |||
873 | } | |||
874 | TableWidget_RecreateDescTex(w); | |||
875 | return true1; | |||
876 | } | |||
877 | ||||
878 | static int TableWidget_KeyDown(void* widget, int key) { | |||
879 | struct TableWidget* w = (struct TableWidget*)widget; | |||
880 | if (w->selectedIndex == -1) return false0; | |||
881 | ||||
882 | if (key == KEY_LEFT || key == KEY_KP4) { | |||
883 | TableWidget_ScrollRelative(w, -1); | |||
884 | } else if (key == KEY_RIGHT || key == KEY_KP6) { | |||
885 | TableWidget_ScrollRelative(w, 1); | |||
886 | } else if (key == KEY_UP || key == KEY_KP8) { | |||
887 | TableWidget_ScrollRelative(w, -w->blocksPerRow); | |||
888 | } else if (key == KEY_DOWN || key == KEY_KP2) { | |||
889 | TableWidget_ScrollRelative(w, w->blocksPerRow); | |||
890 | } else { | |||
891 | return false0; | |||
892 | } | |||
893 | return true1; | |||
894 | } | |||
895 | ||||
896 | static const struct WidgetVTABLE TableWidget_VTABLE = { | |||
897 | TableWidget_Render, TableWidget_Free, TableWidget_Reposition, | |||
898 | TableWidget_KeyDown, Widget_InputUp, TableWidget_MouseScroll, | |||
899 | TableWidget_PointerDown, TableWidget_PointerUp, TableWidget_PointerMove | |||
900 | }; | |||
901 | void TableWidget_Create(struct TableWidget* w) { | |||
902 | Widget_Reset(w); | |||
903 | w->VTABLE = &TableWidget_VTABLE; | |||
904 | w->lastCreatedIndex = -1000; | |||
905 | ScrollbarWidget_Create(&w->scroll); | |||
906 | ||||
907 | w->horAnchor = ANCHOR_CENTRE; | |||
908 | w->verAnchor = ANCHOR_CENTRE; | |||
909 | w->lastX = -20; w->lastY = -20; | |||
910 | w->scale = 1; | |||
911 | ||||
912 | w->paddingX = Display_ScaleX(15); | |||
913 | w->paddingTopY = Display_ScaleY(15 + 20); | |||
914 | w->paddingMaxY = Display_ScaleX(15); | |||
915 | } | |||
916 | ||||
917 | void TableWidget_SetBlockTo(struct TableWidget* w, BlockID block) { | |||
918 | int i; | |||
919 | w->selectedIndex = -1; | |||
920 | ||||
921 | for (i = 0; i < w->blocksCount; i++) { | |||
922 | if (w->blocks[i] == block) w->selectedIndex = i; | |||
923 | } | |||
924 | /* When holding air, inventory should open at middle */ | |||
925 | if (block == BLOCK_AIR) w->selectedIndex = -1; | |||
926 | ||||
927 | w->scroll.topRow = w->selectedIndex / w->blocksPerRow; | |||
928 | w->scroll.topRow -= (w->rowsVisible - 1); | |||
929 | ScrollbarWidget_ClampTopRow(&w->scroll); | |||
930 | TableWidget_MoveCursorToSelected(w); | |||
931 | TableWidget_RecreateDescTex(w); | |||
932 | } | |||
933 | ||||
934 | void TableWidget_OnInventoryChanged(struct TableWidget* w) { | |||
935 | TableWidget_RecreateBlocks(w); | |||
936 | if (w->selectedIndex >= w->blocksCount) { | |||
937 | w->selectedIndex = w->blocksCount - 1; | |||
938 | } | |||
939 | w->lastX = -1; w->lastY = -1; | |||
940 | ||||
941 | w->scroll.topRow = w->selectedIndex / w->blocksPerRow; | |||
942 | ScrollbarWidget_ClampTopRow(&w->scroll); | |||
943 | TableWidget_RecreateDescTex(w); | |||
944 | } | |||
945 | ||||
946 | ||||
947 | /*########################################################################################################################* | |||
948 | *-------------------------------------------------------InputWidget-------------------------------------------------------* | |||
949 | *#########################################################################################################################*/ | |||
950 | static void InputWidget_Reset(struct InputWidget* w) { | |||
951 | Widget_Reset(w); | |||
952 | w->caretPos = -1; | |||
953 | w->caretOffset = Display_ScaleY(2); | |||
954 | w->OnTextChanged = NULL((void*)0); | |||
955 | } | |||
956 | ||||
957 | static void InputWidget_FormatLine(struct InputWidget* w, int i, cc_string* line) { | |||
958 | cc_string src = w->lines[i]; | |||
959 | if (!w->convertPercents) { String_AppendString(line, &src); return; } | |||
960 | ||||
961 | for (i = 0; i < src.length; i++) { | |||
962 | char c = src.buffer[i]; | |||
963 | if (c == '%' && Drawer2D_ValidColCodeAt(&src, i + 1)) { c = '&'; } | |||
964 | String_Append(line, c); | |||
965 | } | |||
966 | } | |||
967 | ||||
968 | static void InputWidget_CalculateLineSizes(struct InputWidget* w) { | |||
969 | cc_string line; char lineBuffer[STRING_SIZE64]; | |||
970 | struct DrawTextArgs args; | |||
971 | int y; | |||
972 | ||||
973 | for (y = 0; y < INPUTWIDGET_MAX_LINES3; y++) { | |||
974 | w->lineWidths[y] = 0; | |||
975 | } | |||
976 | w->lineWidths[0] = w->prefixWidth; | |||
977 | DrawTextArgs_MakeEmpty(&args, w->font, true1); | |||
978 | ||||
979 | String_InitArray(line, lineBuffer)line.buffer = lineBuffer; line.length = 0; line.capacity = sizeof (lineBuffer);; | |||
980 | for (y = 0; y < w->GetMaxLines(); y++) { | |||
981 | line.length = 0; | |||
982 | InputWidget_FormatLine(w, y, &line); | |||
983 | ||||
984 | args.text = line; | |||
985 | w->lineWidths[y] += Drawer2D_TextWidth(&args); | |||
986 | } | |||
987 | } | |||
988 | ||||
989 | static char InputWidget_GetLastCol(struct InputWidget* w, int x, int y) { | |||
990 | cc_string line; char lineBuffer[STRING_SIZE64]; | |||
991 | char col; | |||
992 | String_InitArray(line, lineBuffer)line.buffer = lineBuffer; line.length = 0; line.capacity = sizeof (lineBuffer);; | |||
993 | ||||
994 | for (; y >= 0; y--) { | |||
995 | line.length = 0; | |||
996 | InputWidget_FormatLine(w, y, &line); | |||
997 | ||||
998 | col = Drawer2D_LastCol(&line, x); | |||
999 | if (col) return col; | |||
1000 | if (y > 0) { x = w->lines[y - 1].length; } | |||
1001 | } | |||
1002 | return '\0'; | |||
1003 | } | |||
1004 | ||||
1005 | static void InputWidget_UpdateCaret(struct InputWidget* w) { | |||
1006 | static const cc_string caret = String_FromConst("_"){ "_", (sizeof("_") - 1), (sizeof("_") - 1)}; | |||
1007 | BitmapCol col; | |||
1008 | cc_string line; char lineBuffer[STRING_SIZE64]; | |||
1009 | struct DrawTextArgs args; | |||
1010 | int maxChars, lineWidth; | |||
1011 | char colCode; | |||
1012 | ||||
1013 | if (!w->caretTex.ID) { | |||
1014 | DrawTextArgs_Make(&args, &caret, w->font, true1); | |||
1015 | Drawer2D_MakeTextTexture(&w->caretTex, &args); | |||
1016 | w->caretWidth = (cc_uint16)((w->caretTex.Width * 3) / 4); | |||
1017 | } | |||
1018 | ||||
1019 | maxChars = w->GetMaxLines() * INPUTWIDGET_LEN64; | |||
1020 | if (w->caretPos >= maxChars) w->caretPos = -1; | |||
1021 | WordWrap_GetCoords(w->caretPos, w->lines, w->GetMaxLines(), &w->caretX, &w->caretY); | |||
1022 | ||||
1023 | DrawTextArgs_MakeEmpty(&args, w->font, false0); | |||
1024 | w->caretAccumulator = 0; | |||
1025 | w->caretTex.Width = w->caretWidth; | |||
1026 | ||||
1027 | /* Caret is at last character on line */ | |||
1028 | if (w->caretX == INPUTWIDGET_LEN64) { | |||
1029 | lineWidth = w->lineWidths[w->caretY]; | |||
1030 | } else { | |||
1031 | String_InitArray(line, lineBuffer)line.buffer = lineBuffer; line.length = 0; line.capacity = sizeof (lineBuffer);; | |||
1032 | InputWidget_FormatLine(w, w->caretY, &line); | |||
1033 | ||||
1034 | args.text = String_UNSAFE_Substring(&line, 0, w->caretX); | |||
1035 | lineWidth = Drawer2D_TextWidth(&args); | |||
1036 | if (w->caretY == 0) lineWidth += w->prefixWidth; | |||
1037 | ||||
1038 | if (w->caretX < line.length) { | |||
1039 | args.text = String_UNSAFE_Substring(&line, w->caretX, 1); | |||
1040 | args.useShadow = true1; | |||
1041 | w->caretTex.Width = Drawer2D_TextWidth(&args); | |||
1042 | } | |||
1043 | } | |||
1044 | ||||
1045 | w->caretTex.X = w->x + w->padding + lineWidth; | |||
1046 | w->caretTex.Y = (w->inputTex.Y + w->caretOffset) + w->caretY * w->lineHeight; | |||
1047 | colCode = InputWidget_GetLastCol(w, w->caretX, w->caretY); | |||
1048 | ||||
1049 | if (colCode) { | |||
1050 | col = Drawer2D_GetCol(colCode)Drawer2D_Cols[(cc_uint8)colCode]; | |||
1051 | /* Component order might be different to BitmapCol */ | |||
1052 | w->caretCol = PackedCol_Make(BitmapCol_R(col), BitmapCol_G(col),(((cc_uint8)(((cc_uint8)(col >> 16))) << 0) | ((cc_uint8 )(((cc_uint8)(col >> 8))) << 8) | ((cc_uint8)(((cc_uint8 )(col >> 0))) << 16) | ((cc_uint8)(((cc_uint8)(col >> 24))) << 24)) | |||
1053 | BitmapCol_B(col), BitmapCol_A(col))(((cc_uint8)(((cc_uint8)(col >> 16))) << 0) | ((cc_uint8 )(((cc_uint8)(col >> 8))) << 8) | ((cc_uint8)(((cc_uint8 )(col >> 0))) << 16) | ((cc_uint8)(((cc_uint8)(col >> 24))) << 24)); | |||
1054 | } else { | |||
1055 | w->caretCol = PackedCol_Scale(PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )), 0.8f); | |||
1056 | } | |||
1057 | } | |||
1058 | ||||
1059 | static void InputWidget_RenderCaret(struct InputWidget* w, double delta) { | |||
1060 | float second; | |||
1061 | if (!w->showCaret) return; | |||
1062 | w->caretAccumulator += delta; | |||
1063 | ||||
1064 | second = Math_Mod1((float)w->caretAccumulator); | |||
1065 | if (second < 0.5f) Texture_RenderShaded(&w->caretTex, w->caretCol); | |||
1066 | } | |||
1067 | ||||
1068 | static void InputWidget_OnPressedEnter(void* widget) { | |||
1069 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1070 | InputWidget_Clear(w); | |||
1071 | w->height = w->lineHeight; | |||
1072 | /* TODO get rid of this awful hack.. */ | |||
1073 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
1074 | } | |||
1075 | ||||
1076 | void InputWidget_Clear(struct InputWidget* w) { | |||
1077 | int i; | |||
1078 | w->text.length = 0; | |||
1079 | ||||
1080 | for (i = 0; i < Array_Elems(w->lines)(sizeof(w->lines) / sizeof(w->lines[0])); i++) { | |||
1081 | w->lines[i] = String_Empty; | |||
1082 | } | |||
1083 | ||||
1084 | w->caretPos = -1; | |||
1085 | Gfx_DeleteTexture(&w->inputTex.ID); | |||
1086 | /* TODO: Maybe call w->OnTextChanged */ | |||
1087 | } | |||
1088 | ||||
1089 | static cc_bool InputWidget_AllowedChar(void* widget, char c) { | |||
1090 | return Server.SupportsFullCP437 || (Convert_CP437ToUnicode(c) == c); | |||
1091 | } | |||
1092 | ||||
1093 | static void InputWidget_AppendChar(struct InputWidget* w, char c) { | |||
1094 | if (w->caretPos == -1) { | |||
1095 | String_InsertAt(&w->text, w->text.length, c); | |||
1096 | } else { | |||
1097 | String_InsertAt(&w->text, w->caretPos, c); | |||
1098 | w->caretPos++; | |||
1099 | if (w->caretPos >= w->text.length) { w->caretPos = -1; } | |||
1100 | } | |||
1101 | } | |||
1102 | ||||
1103 | static cc_bool InputWidget_TryAppendChar(struct InputWidget* w, char c) { | |||
1104 | int maxChars = w->GetMaxLines() * INPUTWIDGET_LEN64; | |||
1105 | if (w->text.length >= maxChars) return false0; | |||
1106 | if (!w->AllowedChar(w, c)) return false0; | |||
1107 | ||||
1108 | InputWidget_AppendChar(w, c); | |||
1109 | return true1; | |||
1110 | } | |||
1111 | ||||
1112 | static int InputWidget_DoAppendText(struct InputWidget* w, const cc_string* text) { | |||
1113 | int i, appended = 0; | |||
1114 | for (i = 0; i < text->length; i++) { | |||
1115 | if (InputWidget_TryAppendChar(w, text->buffer[i])) appended++; | |||
1116 | } | |||
1117 | return appended; | |||
1118 | } | |||
1119 | ||||
1120 | void InputWidget_AppendText(struct InputWidget* w, const cc_string* text) { | |||
1121 | int appended = InputWidget_DoAppendText(w, text); | |||
1122 | if (appended) InputWidget_UpdateText(w); | |||
1123 | } | |||
1124 | ||||
1125 | void InputWidget_Append(struct InputWidget* w, char c) { | |||
1126 | if (!InputWidget_TryAppendChar(w, c)) return; | |||
1127 | InputWidget_UpdateText(w); | |||
1128 | } | |||
1129 | ||||
1130 | static void InputWidget_DeleteChar(struct InputWidget* w) { | |||
1131 | if (!w->text.length) return; | |||
1132 | ||||
1133 | if (w->caretPos == -1) { | |||
1134 | String_DeleteAt(&w->text, w->text.length - 1); | |||
1135 | } else if (w->caretPos > 0) { | |||
1136 | w->caretPos--; | |||
1137 | String_DeleteAt(&w->text, w->caretPos); | |||
1138 | } | |||
1139 | } | |||
1140 | ||||
1141 | static void InputWidget_BackspaceKey(struct InputWidget* w) { | |||
1142 | int i, len; | |||
1143 | ||||
1144 | if (Key_IsActionPressed()(Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])) { | |||
1145 | if (w->caretPos == -1) { w->caretPos = w->text.length - 1; } | |||
1146 | len = WordWrap_GetBackLength(&w->text, w->caretPos); | |||
1147 | if (!len) return; | |||
1148 | ||||
1149 | w->caretPos -= len; | |||
1150 | if (w->caretPos < 0) { w->caretPos = 0; } | |||
1151 | ||||
1152 | for (i = 0; i <= len; i++) { | |||
1153 | String_DeleteAt(&w->text, w->caretPos); | |||
1154 | } | |||
1155 | ||||
1156 | if (w->caretPos >= w->text.length) { w->caretPos = -1; } | |||
1157 | if (w->caretPos == -1 && w->text.length > 0) { | |||
1158 | String_InsertAt(&w->text, w->text.length, ' '); | |||
1159 | } else if (w->caretPos >= 0 && w->text.buffer[w->caretPos] != ' ') { | |||
1160 | String_InsertAt(&w->text, w->caretPos, ' '); | |||
1161 | } | |||
1162 | InputWidget_UpdateText(w); | |||
1163 | } else if (w->text.length > 0 && w->caretPos != 0) { | |||
1164 | InputWidget_DeleteChar(w); | |||
1165 | InputWidget_UpdateText(w); | |||
1166 | } | |||
1167 | } | |||
1168 | ||||
1169 | static void InputWidget_DeleteKey(struct InputWidget* w) { | |||
1170 | if (w->text.length > 0 && w->caretPos != -1) { | |||
1171 | String_DeleteAt(&w->text, w->caretPos); | |||
1172 | if (w->caretPos >= w->text.length) { w->caretPos = -1; } | |||
1173 | InputWidget_UpdateText(w); | |||
1174 | } | |||
1175 | } | |||
1176 | ||||
1177 | static void InputWidget_LeftKey(struct InputWidget* w) { | |||
1178 | if (Key_IsActionPressed()(Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])) { | |||
1179 | if (w->caretPos == -1) { w->caretPos = w->text.length - 1; } | |||
1180 | w->caretPos -= WordWrap_GetBackLength(&w->text, w->caretPos); | |||
1181 | InputWidget_UpdateCaret(w); | |||
1182 | return; | |||
1183 | } | |||
1184 | ||||
1185 | if (w->text.length > 0) { | |||
1186 | if (w->caretPos == -1) { w->caretPos = w->text.length; } | |||
1187 | w->caretPos--; | |||
1188 | if (w->caretPos < 0) { w->caretPos = 0; } | |||
1189 | InputWidget_UpdateCaret(w); | |||
1190 | } | |||
1191 | } | |||
1192 | ||||
1193 | static void InputWidget_RightKey(struct InputWidget* w) { | |||
1194 | if (Key_IsActionPressed()(Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])) { | |||
1195 | w->caretPos += WordWrap_GetForwardLength(&w->text, w->caretPos); | |||
1196 | if (w->caretPos >= w->text.length) { w->caretPos = -1; } | |||
1197 | InputWidget_UpdateCaret(w); | |||
1198 | return; | |||
1199 | } | |||
1200 | ||||
1201 | if (w->text.length > 0 && w->caretPos != -1) { | |||
1202 | w->caretPos++; | |||
1203 | if (w->caretPos >= w->text.length) { w->caretPos = -1; } | |||
1204 | InputWidget_UpdateCaret(w); | |||
1205 | } | |||
1206 | } | |||
1207 | ||||
1208 | static void InputWidget_HomeKey(struct InputWidget* w) { | |||
1209 | if (!w->text.length) return; | |||
1210 | w->caretPos = 0; | |||
1211 | InputWidget_UpdateCaret(w); | |||
1212 | } | |||
1213 | ||||
1214 | static void InputWidget_EndKey(struct InputWidget* w) { | |||
1215 | w->caretPos = -1; | |||
1216 | InputWidget_UpdateCaret(w); | |||
1217 | } | |||
1218 | ||||
1219 | static void InputWidget_CopyFromClipboard(cc_string* text, void* w) { | |||
1220 | InputWidget_AppendText((struct InputWidget*)w, text); | |||
1221 | } | |||
1222 | ||||
1223 | static cc_bool InputWidget_OtherKey(struct InputWidget* w, int key) { | |||
1224 | int maxChars = w->GetMaxLines() * INPUTWIDGET_LEN64; | |||
1225 | if (!Key_IsActionPressed()(Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])) return false0; | |||
1226 | ||||
1227 | if (key == 'V' && w->text.length < maxChars) { | |||
1228 | Clipboard_RequestText(InputWidget_CopyFromClipboard, w); | |||
1229 | return true1; | |||
1230 | } else if (key == 'C') { | |||
1231 | if (!w->text.length) return true1; | |||
1232 | Clipboard_SetText(&w->text); | |||
1233 | return true1; | |||
1234 | } | |||
1235 | return false0; | |||
1236 | } | |||
1237 | ||||
1238 | void InputWidget_UpdateText(struct InputWidget* w) { | |||
1239 | int lines = w->GetMaxLines(); | |||
1240 | if (lines > 1) { | |||
1241 | WordWrap_Do(&w->text, w->lines, lines, INPUTWIDGET_LEN64); | |||
1242 | } else { | |||
1243 | w->lines[0] = w->text; | |||
1244 | } | |||
1245 | ||||
1246 | Gfx_DeleteTexture(&w->inputTex.ID); | |||
1247 | InputWidget_CalculateLineSizes(w); | |||
1248 | w->RemakeTexture(w); | |||
1249 | InputWidget_UpdateCaret(w); | |||
1250 | Window_SetKeyboardText(&w->text); | |||
1251 | if (w->OnTextChanged) w->OnTextChanged(w); | |||
1252 | } | |||
1253 | ||||
1254 | void InputWidget_SetText(struct InputWidget* w, const cc_string* str) { | |||
1255 | InputWidget_Clear(w); | |||
1256 | InputWidget_DoAppendText(w, str); | |||
1257 | InputWidget_UpdateText(w); | |||
1258 | } | |||
1259 | ||||
1260 | static void InputWidget_Free(void* widget) { | |||
1261 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1262 | Gfx_DeleteTexture(&w->inputTex.ID); | |||
1263 | Gfx_DeleteTexture(&w->caretTex.ID); | |||
1264 | } | |||
1265 | ||||
1266 | static void InputWidget_Reposition(void* widget) { | |||
1267 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1268 | int oldX = w->x, oldY = w->y; | |||
1269 | Widget_CalcPosition(w); | |||
1270 | ||||
1271 | w->caretTex.X += w->x - oldX; w->caretTex.Y += w->y - oldY; | |||
1272 | w->inputTex.X += w->x - oldX; w->inputTex.Y += w->y - oldY; | |||
1273 | } | |||
1274 | ||||
1275 | static int InputWidget_KeyDown(void* widget, int key) { | |||
1276 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1277 | if (key == KEY_LEFT) { | |||
1278 | InputWidget_LeftKey(w); | |||
1279 | } else if (key == KEY_RIGHT) { | |||
1280 | InputWidget_RightKey(w); | |||
1281 | } else if (key == KEY_BACKSPACE) { | |||
1282 | InputWidget_BackspaceKey(w); | |||
1283 | } else if (key == KEY_DELETE) { | |||
1284 | InputWidget_DeleteKey(w); | |||
1285 | } else if (key == KEY_HOME) { | |||
1286 | InputWidget_HomeKey(w); | |||
1287 | } else if (key == KEY_END) { | |||
1288 | InputWidget_EndKey(w); | |||
1289 | } else if (!InputWidget_OtherKey(w, key)) { | |||
1290 | return false0; | |||
1291 | } | |||
1292 | return true1; | |||
1293 | } | |||
1294 | ||||
1295 | static int InputWidget_PointerDown(void* widget, int id, int x, int y) { | |||
1296 | cc_string line; char lineBuffer[STRING_SIZE64]; | |||
1297 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1298 | struct DrawTextArgs args; | |||
1299 | int cx, cy, offset = 0; | |||
1300 | int charX, charWidth, charHeight; | |||
1301 | ||||
1302 | x -= w->inputTex.X; y -= w->inputTex.Y; | |||
1303 | DrawTextArgs_MakeEmpty(&args, w->font, true1); | |||
1304 | charHeight = w->lineHeight; | |||
1305 | String_InitArray(line, lineBuffer)line.buffer = lineBuffer; line.length = 0; line.capacity = sizeof (lineBuffer);; | |||
1306 | ||||
1307 | for (cy = 0; cy < w->GetMaxLines(); cy++) { | |||
1308 | line.length = 0; | |||
1309 | InputWidget_FormatLine(w, cy, &line); | |||
1310 | if (!line.length) continue; | |||
1311 | ||||
1312 | for (cx = 0; cx < line.length; cx++) { | |||
1313 | args.text = String_UNSAFE_Substring(&line, 0, cx); | |||
1314 | charX = Drawer2D_TextWidth(&args); | |||
1315 | if (cy == 0) charX += w->prefixWidth; | |||
1316 | ||||
1317 | args.text = String_UNSAFE_Substring(&line, cx, 1); | |||
1318 | charWidth = Drawer2D_TextWidth(&args); | |||
1319 | ||||
1320 | if (Gui_Contains(charX, cy * charHeight, charWidth, charHeight, x, y)) { | |||
1321 | w->caretPos = offset + cx; | |||
1322 | InputWidget_UpdateCaret(w); | |||
1323 | return TOUCH_TYPE_GUI1; | |||
1324 | } | |||
1325 | } | |||
1326 | offset += line.length; | |||
1327 | } | |||
1328 | ||||
1329 | w->caretPos = -1; | |||
1330 | InputWidget_UpdateCaret(w); | |||
1331 | return TOUCH_TYPE_GUI1; | |||
1332 | } | |||
1333 | ||||
1334 | ||||
1335 | /*########################################################################################################################* | |||
1336 | *-----------------------------------------------------MenuInputDesc-------------------------------------------------------* | |||
1337 | *#########################################################################################################################*/ | |||
1338 | static void Hex_Range(struct MenuInputDesc* d, cc_string* range) { | |||
1339 | String_AppendConst(range, "&7(#000000 - #FFFFFF)"); | |||
1340 | } | |||
1341 | ||||
1342 | static cc_bool Hex_ValidChar(struct MenuInputDesc* d, char c) { | |||
1343 | return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); | |||
1344 | } | |||
1345 | ||||
1346 | static cc_bool Hex_ValidString(struct MenuInputDesc* d, const cc_string* s) { | |||
1347 | return s->length <= 6; | |||
1348 | } | |||
1349 | ||||
1350 | static cc_bool Hex_ValidValue(struct MenuInputDesc* d, const cc_string* s) { | |||
1351 | cc_uint8 rgb[3]; | |||
1352 | return PackedCol_TryParseHex(s, rgb); | |||
1353 | } | |||
1354 | ||||
1355 | static void Hex_Default(struct MenuInputDesc* d, cc_string* value) { | |||
1356 | PackedCol_ToHex(value, d->meta.h.Default); | |||
1357 | } | |||
1358 | ||||
1359 | const struct MenuInputVTABLE HexInput_VTABLE = { | |||
1360 | Hex_Range, Hex_ValidChar, Hex_ValidString, Hex_ValidValue, Hex_Default | |||
1361 | }; | |||
1362 | ||||
1363 | static void Int_Range(struct MenuInputDesc* d, cc_string* range) { | |||
1364 | String_Format2(range, "&7(%i - %i)", &d->meta.i.Min, &d->meta.i.Max); | |||
1365 | } | |||
1366 | ||||
1367 | static cc_bool Int_ValidChar(struct MenuInputDesc* d, char c) { | |||
1368 | return (c >= '0' && c <= '9') || c == '-'; | |||
1369 | } | |||
1370 | ||||
1371 | static cc_bool Int_ValidString(struct MenuInputDesc* d, const cc_string* s) { | |||
1372 | int value; | |||
1373 | if (s->length == 1 && s->buffer[0] == '-') return true1; /* input is just a minus sign */ | |||
1374 | return Convert_ParseInt(s, &value); | |||
1375 | } | |||
1376 | ||||
1377 | static cc_bool Int_ValidValue(struct MenuInputDesc* d, const cc_string* s) { | |||
1378 | int value, min = d->meta.i.Min, max = d->meta.i.Max; | |||
1379 | return Convert_ParseInt(s, &value) && min <= value && value <= max; | |||
1380 | } | |||
1381 | ||||
1382 | static void Int_Default(struct MenuInputDesc* d, cc_string* value) { | |||
1383 | String_AppendInt(value, d->meta.i.Default); | |||
1384 | } | |||
1385 | ||||
1386 | const struct MenuInputVTABLE IntInput_VTABLE = { | |||
1387 | Int_Range, Int_ValidChar, Int_ValidString, Int_ValidValue, Int_Default | |||
1388 | }; | |||
1389 | ||||
1390 | static void Seed_Range(struct MenuInputDesc* d, cc_string* range) { | |||
1391 | String_AppendConst(range, "&7(an integer)"); | |||
1392 | } | |||
1393 | static void Seed_NoDefault(struct MenuInputDesc* d, cc_string* value) { } | |||
1394 | ||||
1395 | const struct MenuInputVTABLE SeedInput_VTABLE = { | |||
1396 | Seed_Range, Int_ValidChar, Int_ValidString, Int_ValidValue, Seed_NoDefault | |||
1397 | }; | |||
1398 | ||||
1399 | static void Float_Range(struct MenuInputDesc* d, cc_string* range) { | |||
1400 | String_Format2(range, "&7(%f2 - %f2)", &d->meta.f.Min, &d->meta.f.Max); | |||
1401 | } | |||
1402 | ||||
1403 | static cc_bool Float_ValidChar(struct MenuInputDesc* d, char c) { | |||
1404 | return (c >= '0' && c <= '9') || c == '-' || c == '.' || c == ','; | |||
1405 | } | |||
1406 | ||||
1407 | static cc_bool Float_ValidString(struct MenuInputDesc* d, const cc_string* s) { | |||
1408 | float value; | |||
1409 | if (s->length == 1 && Float_ValidChar(d, s->buffer[0])) return true1; | |||
1410 | return Convert_ParseFloat(s, &value); | |||
1411 | } | |||
1412 | ||||
1413 | static cc_bool Float_ValidValue(struct MenuInputDesc* d, const cc_string* s) { | |||
1414 | float value, min = d->meta.f.Min, max = d->meta.f.Max; | |||
1415 | return Convert_ParseFloat(s, &value) && min <= value && value <= max; | |||
1416 | } | |||
1417 | ||||
1418 | static void Float_Default(struct MenuInputDesc* d, cc_string* value) { | |||
1419 | String_AppendFloat(value, d->meta.f.Default, 3); | |||
1420 | } | |||
1421 | ||||
1422 | const struct MenuInputVTABLE FloatInput_VTABLE = { | |||
1423 | Float_Range, Float_ValidChar, Float_ValidString, Float_ValidValue, Float_Default | |||
1424 | }; | |||
1425 | ||||
1426 | static void Path_Range(struct MenuInputDesc* d, cc_string* range) { | |||
1427 | String_AppendConst(range, "&7(Enter name)"); | |||
1428 | } | |||
1429 | ||||
1430 | static cc_bool Path_ValidChar(struct MenuInputDesc* d, char c) { | |||
1431 | return !(c == '/' || c == '\\' || c == '?' || c == '*' || c == ':' | |||
1432 | || c == '<' || c == '>' || c == '|' || c == '"' || c == '.'); | |||
1433 | } | |||
1434 | static cc_bool Path_ValidString(struct MenuInputDesc* d, const cc_string* s) { return true1; } | |||
1435 | ||||
1436 | const struct MenuInputVTABLE PathInput_VTABLE = { | |||
1437 | Path_Range, Path_ValidChar, Path_ValidString, Path_ValidString, Seed_NoDefault | |||
1438 | }; | |||
1439 | ||||
1440 | static void String_Range(struct MenuInputDesc* d, cc_string* range) { | |||
1441 | String_AppendConst(range, "&7(Enter text)"); | |||
1442 | } | |||
1443 | ||||
1444 | static cc_bool String_ValidChar(struct MenuInputDesc* d, char c) { | |||
1445 | return c != '&'; | |||
1446 | } | |||
1447 | ||||
1448 | static cc_bool String_ValidString(struct MenuInputDesc* d, const cc_string* s) { | |||
1449 | return s->length <= STRING_SIZE64; | |||
1450 | } | |||
1451 | ||||
1452 | const struct MenuInputVTABLE StringInput_VTABLE = { | |||
1453 | String_Range, String_ValidChar, String_ValidString, String_ValidString, Seed_NoDefault | |||
1454 | }; | |||
1455 | ||||
1456 | ||||
1457 | /*########################################################################################################################* | |||
1458 | *-----------------------------------------------------TextInputWidget-----------------------------------------------------* | |||
1459 | *#########################################################################################################################*/ | |||
1460 | static void TextInputWidget_Render(void* widget, double delta) { | |||
1461 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1462 | Texture_Render(&w->inputTex); | |||
1463 | InputWidget_RenderCaret(w, delta); | |||
1464 | } | |||
1465 | ||||
1466 | static void TextInputWidget_BuildMesh(void* widget, struct VertexTextured** vertices) { | |||
1467 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1468 | Gfx_Make2DQuad(&w->inputTex, PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )), vertices); | |||
1469 | Gfx_Make2DQuad(&w->caretTex, w->caretCol, vertices); | |||
1470 | } | |||
1471 | ||||
1472 | static int TextInputWidget_Render2(void* widget, int offset) { | |||
1473 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1474 | Gfx_BindTexture(w->inputTex.ID); | |||
1475 | Gfx_DrawVb_IndexedTris_Range(4, offset); | |||
1476 | offset += 4; | |||
1477 | ||||
1478 | if (w->showCaret && Math_Mod1((float)w->caretAccumulator) < 0.5f) { | |||
1479 | Gfx_BindTexture(w->caretTex.ID); | |||
1480 | Gfx_DrawVb_IndexedTris_Range(4, offset); | |||
1481 | } | |||
1482 | return offset + 4; | |||
1483 | } | |||
1484 | ||||
1485 | static void TextInputWidget_RemakeTexture(void* widget) { | |||
1486 | cc_string range; char rangeBuffer[STRING_SIZE64]; | |||
1487 | struct TextInputWidget* w = (struct TextInputWidget*)widget; | |||
1488 | PackedCol backCol = PackedCol_Make(30, 30, 30, 200)(((cc_uint8)(30) << 0) | ((cc_uint8)(30) << 8) | ( (cc_uint8)(30) << 16) | ((cc_uint8)(200) << 24)); | |||
1489 | struct MenuInputDesc* desc; | |||
1490 | struct DrawTextArgs args; | |||
1491 | struct Texture* tex; | |||
1492 | int textWidth, lineHeight; | |||
1493 | int width, height, hintX, y; | |||
1494 | struct Bitmap bmp; | |||
1495 | ||||
1496 | DrawTextArgs_Make(&args, &w->base.text, w->base.font, false0); | |||
1497 | textWidth = Drawer2D_TextWidth(&args); | |||
1498 | lineHeight = w->base.lineHeight; | |||
1499 | w->base.caretAccumulator = 0.0; | |||
1500 | ||||
1501 | String_InitArray(range, rangeBuffer)range.buffer = rangeBuffer; range.length = 0; range.capacity = sizeof(rangeBuffer);; | |||
1502 | desc = &w->desc; | |||
1503 | desc->VTABLE->GetRange(desc, &range); | |||
1504 | ||||
1505 | width = max(textWidth, w->minWidth)((textWidth) > (w->minWidth) ? (textWidth) : (w->minWidth )); w->base.width = width; | |||
1506 | height = max(lineHeight, w->minHeight)((lineHeight) > (w->minHeight) ? (lineHeight) : (w-> minHeight)); w->base.height = height; | |||
1507 | ||||
1508 | Bitmap_AllocateClearedPow2(&bmp, width, height); | |||
1509 | { | |||
1510 | /* Centre text vertically */ | |||
1511 | y = 0; | |||
1512 | if (lineHeight < height) { y = height / 2 - lineHeight / 2; } | |||
1513 | w->base.caretOffset = 2 + y; | |||
1514 | ||||
1515 | Drawer2D_Clear(&bmp, backCol, 0, 0, width, height); | |||
1516 | Drawer2D_DrawText(&bmp, &args, w->base.padding, y); | |||
1517 | ||||
1518 | args.text = range; | |||
1519 | hintX = width - Drawer2D_TextWidth(&args); | |||
1520 | /* Draw hint text right-aligned if it won't overlap input text */ | |||
1521 | if (textWidth + 3 < hintX) { | |||
1522 | Drawer2D_DrawText(&bmp, &args, hintX, y); | |||
1523 | } | |||
1524 | } | |||
1525 | ||||
1526 | tex = &w->base.inputTex; | |||
1527 | Drawer2D_MakeTexture(tex, &bmp, width, height); | |||
1528 | Mem_Free(bmp.scan0); | |||
1529 | ||||
1530 | Widget_Layout(&w->base)(&w->base)->VTABLE->Reposition(&w->base); | |||
1531 | tex->X = w->base.x; tex->Y = w->base.y; | |||
1532 | } | |||
1533 | ||||
1534 | static cc_bool TextInputWidget_AllowedChar(void* widget, char c) { | |||
1535 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1536 | struct MenuInputDesc* desc; | |||
1537 | int maxChars; | |||
1538 | cc_bool valid; | |||
1539 | ||||
1540 | if (c == '&') return false0; | |||
1541 | desc = &((struct TextInputWidget*)w)->desc; | |||
1542 | ||||
1543 | if (!desc->VTABLE->IsValidChar(desc, c)) return false0; | |||
1544 | maxChars = w->GetMaxLines() * INPUTWIDGET_LEN64; | |||
1545 | if (w->text.length == maxChars) return false0; | |||
1546 | ||||
1547 | /* See if the new string is in valid format */ | |||
1548 | InputWidget_AppendChar(w, c); | |||
1549 | valid = desc->VTABLE->IsValidString(desc, &w->text); | |||
1550 | InputWidget_DeleteChar(w); | |||
1551 | return valid; | |||
1552 | } | |||
1553 | ||||
1554 | static int TextInputWidget_GetMaxLines(void) { return 1; } | |||
1555 | static const struct WidgetVTABLE TextInputWidget_VTABLE = { | |||
1556 | TextInputWidget_Render, InputWidget_Free, InputWidget_Reposition, | |||
1557 | InputWidget_KeyDown, Widget_InputUp, Widget_MouseScroll, | |||
1558 | InputWidget_PointerDown, Widget_PointerUp, Widget_PointerMove, | |||
1559 | TextInputWidget_BuildMesh, TextInputWidget_Render2 | |||
1560 | }; | |||
1561 | void TextInputWidget_Create(struct TextInputWidget* w, int width, const cc_string* text, struct MenuInputDesc* desc) { | |||
1562 | InputWidget_Reset(&w->base); | |||
1563 | w->base.VTABLE = &TextInputWidget_VTABLE; | |||
1564 | ||||
1565 | w->minWidth = Display_ScaleX(width); | |||
1566 | w->minHeight = Display_ScaleY(30); | |||
1567 | w->desc = *desc; | |||
1568 | ||||
1569 | w->base.convertPercents = false0; | |||
1570 | w->base.padding = 3; | |||
1571 | w->base.showCaret = true1; | |||
1572 | ||||
1573 | w->base.GetMaxLines = TextInputWidget_GetMaxLines; | |||
1574 | w->base.RemakeTexture = TextInputWidget_RemakeTexture; | |||
1575 | w->base.OnPressedEnter = InputWidget_OnPressedEnter; | |||
1576 | w->base.AllowedChar = TextInputWidget_AllowedChar; | |||
1577 | ||||
1578 | String_InitArray(w->base.text, w->_textBuffer)w->base.text.buffer = w->_textBuffer; w->base.text.length = 0; w->base.text.capacity = sizeof(w->_textBuffer);; | |||
1579 | String_Copy(&w->base.text, text); | |||
1580 | } | |||
1581 | ||||
1582 | void TextInputWidget_SetFont(struct TextInputWidget* w, struct FontDesc* font) { | |||
1583 | w->base.font = font; | |||
1584 | w->base.lineHeight = Drawer2D_FontHeight(font, false0); | |||
1585 | InputWidget_UpdateText(&w->base); | |||
1586 | } | |||
1587 | ||||
1588 | ||||
1589 | /*########################################################################################################################* | |||
1590 | *-----------------------------------------------------ChatInputWidget-----------------------------------------------------* | |||
1591 | *#########################################################################################################################*/ | |||
1592 | static const cc_string chatInputPrefix = String_FromConst("> "){ "> ", (sizeof("> ") - 1), (sizeof("> ") - 1)}; | |||
1593 | ||||
1594 | static void ChatInputWidget_RemakeTexture(void* widget) { | |||
1595 | cc_string line; char lineBuffer[STRING_SIZE64 + 2]; | |||
1596 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1597 | struct DrawTextArgs args; | |||
1598 | int width = 0, height = 0; | |||
1599 | struct Bitmap bmp; | |||
1600 | char lastCol; | |||
1601 | int i, x, y; | |||
1602 | ||||
1603 | for (i = 0; i < w->GetMaxLines(); i++) { | |||
1604 | if (!w->lines[i].length) break; | |||
1605 | height += w->lineHeight; | |||
1606 | width = max(width, w->lineWidths[i])((width) > (w->lineWidths[i]) ? (width) : (w->lineWidths [i])); | |||
1607 | } | |||
1608 | ||||
1609 | if (!width) width = w->prefixWidth; | |||
1610 | if (!height) height = w->lineHeight; | |||
1611 | Bitmap_AllocateClearedPow2(&bmp, width, height); | |||
1612 | ||||
1613 | DrawTextArgs_Make(&args, &chatInputPrefix, w->font, true1); | |||
1614 | Drawer2D_DrawText(&bmp, &args, 0, 0); | |||
1615 | ||||
1616 | String_InitArray(line, lineBuffer)line.buffer = lineBuffer; line.length = 0; line.capacity = sizeof (lineBuffer);; | |||
1617 | for (i = 0, y = 0; i < Array_Elems(w->lines)(sizeof(w->lines) / sizeof(w->lines[0])); i++) { | |||
1618 | if (!w->lines[i].length) break; | |||
1619 | line.length = 0; | |||
1620 | ||||
1621 | /* Colour code continues in next line */ | |||
1622 | lastCol = InputWidget_GetLastCol(w, 0, i); | |||
1623 | if (!Drawer2D_IsWhiteCol(lastCol)) { | |||
1624 | String_Append(&line, '&'); String_Append(&line, lastCol); | |||
1625 | } | |||
1626 | /* Convert % to & for colour codes */ | |||
1627 | InputWidget_FormatLine(w, i, &line); | |||
1628 | args.text = line; | |||
1629 | ||||
1630 | x = i == 0 ? w->prefixWidth : 0; | |||
1631 | Drawer2D_DrawText(&bmp, &args, x, y); | |||
1632 | y += w->lineHeight; | |||
1633 | } | |||
1634 | ||||
1635 | Drawer2D_MakeTexture(&w->inputTex, &bmp, width, height); | |||
1636 | Mem_Free(bmp.scan0); | |||
1637 | w->caretAccumulator = 0; | |||
1638 | ||||
1639 | w->width = width; | |||
1640 | w->height = height; | |||
1641 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
1642 | w->inputTex.X = w->x + w->padding; | |||
1643 | w->inputTex.Y = w->y; | |||
1644 | } | |||
1645 | ||||
1646 | static void ChatInputWidget_Render(void* widget, double delta) { | |||
1647 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1648 | PackedCol backCol = PackedCol_Make(0, 0, 0, 127)(((cc_uint8)(0) << 0) | ((cc_uint8)(0) << 8) | (( cc_uint8)(0) << 16) | ((cc_uint8)(127) << 24)); | |||
1649 | int x = w->x, y = w->y; | |||
1650 | cc_bool caretAtEnd; | |||
1651 | int i, width; | |||
1652 | ||||
1653 | Gfx_SetTexturing(false0); | |||
1654 | for (i = 0; i < INPUTWIDGET_MAX_LINES3; i++) { | |||
1655 | if (i > 0 && !w->lines[i].length) break; | |||
1656 | ||||
1657 | caretAtEnd = (w->caretY == i) && (w->caretX == INPUTWIDGET_LEN64 || w->caretPos == -1); | |||
1658 | width = w->lineWidths[i] + (caretAtEnd ? w->caretTex.Width : 0); | |||
1659 | /* Cover whole window width to match original classic behaviour */ | |||
1660 | if (Gui.ClassicChat) { width = max(width, WindowInfo.Width - x * 4)((width) > (WindowInfo.Width - x * 4) ? (width) : (WindowInfo .Width - x * 4)); } | |||
1661 | ||||
1662 | Gfx_Draw2DFlat(x, y, width + w->padding * 2, w->lineHeight, backCol); | |||
1663 | y += w->lineHeight; | |||
1664 | } | |||
1665 | ||||
1666 | Gfx_SetTexturing(true1); | |||
1667 | Texture_Render(&w->inputTex); | |||
1668 | InputWidget_RenderCaret(w, delta); | |||
1669 | } | |||
1670 | ||||
1671 | static void ChatInputWidget_OnPressedEnter(void* widget) { | |||
1672 | struct ChatInputWidget* w = (struct ChatInputWidget*)widget; | |||
1673 | /* Don't want trailing spaces in output message */ | |||
1674 | cc_string text = w->base.text; | |||
1675 | String_UNSAFE_TrimEnd(&text); | |||
1676 | if (text.length) { Chat_Send(&text, true1); } | |||
1677 | ||||
1678 | w->origStr.length = 0; | |||
1679 | w->typingLogPos = Chat_InputLog.count; /* Index of newest entry + 1. */ | |||
1680 | ||||
1681 | Chat_AddOf(&String_Empty, MSG_TYPE_CLIENTSTATUS_2); | |||
1682 | InputWidget_OnPressedEnter(widget); | |||
1683 | } | |||
1684 | ||||
1685 | static void ChatInputWidget_UpKey(struct InputWidget* w) { | |||
1686 | struct ChatInputWidget* W = (struct ChatInputWidget*)w; | |||
1687 | cc_string prevInput; | |||
1688 | int pos; | |||
1689 | ||||
1690 | if (Key_IsActionPressed()(Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])) { | |||
1691 | pos = w->caretPos == -1 ? w->text.length : w->caretPos; | |||
1692 | if (pos < INPUTWIDGET_LEN64) return; | |||
1693 | ||||
1694 | w->caretPos = pos - INPUTWIDGET_LEN64; | |||
1695 | InputWidget_UpdateCaret(w); | |||
1696 | return; | |||
1697 | } | |||
1698 | ||||
1699 | if (W->typingLogPos == Chat_InputLog.count) { | |||
1700 | String_Copy(&W->origStr, &w->text); | |||
1701 | } | |||
1702 | ||||
1703 | if (!Chat_InputLog.count) return; | |||
1704 | W->typingLogPos--; | |||
1705 | w->text.length = 0; | |||
1706 | ||||
1707 | if (W->typingLogPos < 0) W->typingLogPos = 0; | |||
1708 | prevInput = StringsBuffer_UNSAFE_Get(&Chat_InputLog, W->typingLogPos); | |||
1709 | String_AppendString(&w->text, &prevInput); | |||
1710 | ||||
1711 | w->caretPos = -1; | |||
1712 | InputWidget_UpdateText(w); | |||
1713 | } | |||
1714 | ||||
1715 | static void ChatInputWidget_DownKey(struct InputWidget* w) { | |||
1716 | struct ChatInputWidget* W = (struct ChatInputWidget*)w; | |||
1717 | cc_string prevInput; | |||
1718 | ||||
1719 | if (Key_IsActionPressed()(Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])) { | |||
1720 | if (w->caretPos == -1) return; | |||
1721 | ||||
1722 | w->caretPos += INPUTWIDGET_LEN64; | |||
1723 | if (w->caretPos >= w->text.length) { w->caretPos = -1; } | |||
1724 | InputWidget_UpdateCaret(w); | |||
1725 | return; | |||
1726 | } | |||
1727 | ||||
1728 | if (!Chat_InputLog.count) return; | |||
1729 | W->typingLogPos++; | |||
1730 | w->text.length = 0; | |||
1731 | ||||
1732 | if (W->typingLogPos >= Chat_InputLog.count) { | |||
1733 | W->typingLogPos = Chat_InputLog.count; | |||
1734 | String_AppendString(&w->text, &W->origStr); | |||
1735 | } else { | |||
1736 | prevInput = StringsBuffer_UNSAFE_Get(&Chat_InputLog, W->typingLogPos); | |||
1737 | String_AppendString(&w->text, &prevInput); | |||
1738 | } | |||
1739 | ||||
1740 | w->caretPos = -1; | |||
1741 | InputWidget_UpdateText(w); | |||
1742 | } | |||
1743 | ||||
1744 | static cc_bool ChatInputWidget_IsNameChar(char c) { | |||
1745 | return c == '_' || c == '.' || (c >= '0' && c <= '9') | |||
1746 | || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); | |||
1747 | } | |||
1748 | ||||
1749 | static void ChatInputWidget_TabKey(struct InputWidget* w) { | |||
1750 | cc_string str; char strBuffer[STRING_SIZE64]; | |||
1751 | EntityID matches[TABLIST_MAX_NAMES256]; | |||
1752 | cc_string part, name; | |||
1753 | int beg, end, len; | |||
1754 | int i, j, numMatches; | |||
1755 | char* buffer; | |||
1756 | ||||
1757 | end = w->caretPos == -1 ? w->text.length - 1 : w->caretPos; | |||
1758 | beg = end; | |||
1759 | buffer = w->text.buffer; | |||
1760 | ||||
1761 | /* e.g. if player typed "hi Nam", backtrack to "N" */ | |||
1762 | while (beg >= 0 && ChatInputWidget_IsNameChar(buffer[beg])) beg--; | |||
1763 | beg++; | |||
1764 | if (end < 0 || beg > end) return; | |||
1765 | ||||
1766 | part = String_UNSAFE_Substring(&w->text, beg, (end + 1) - beg); | |||
1767 | Chat_AddOf(&String_Empty, MSG_TYPE_CLIENTSTATUS_2); | |||
1768 | numMatches = 0; | |||
1769 | ||||
1770 | for (i = 0; i < TABLIST_MAX_NAMES256; i++) { | |||
1771 | if (!TabList.NameOffsets[i]) continue; | |||
1772 | ||||
1773 | name = TabList_UNSAFE_GetPlayer(i)StringsBuffer_UNSAFE_Get(&TabList._buffer, TabList.NameOffsets [i] - 3);; | |||
1774 | if (!String_CaselessContains(&name, &part)) continue; | |||
1775 | matches[numMatches++] = (EntityID)i; | |||
1776 | } | |||
1777 | ||||
1778 | if (numMatches == 1) { | |||
1779 | if (w->caretPos == -1) end++; | |||
1780 | len = end - beg; | |||
1781 | ||||
1782 | /* Following on from above example, delete 'N','a','m' */ | |||
1783 | /* Then insert the e.g. matching 'nAME1' player name */ | |||
1784 | for (j = 0; j < len; j++) { | |||
1785 | String_DeleteAt(&w->text, beg); | |||
1786 | } | |||
1787 | ||||
1788 | if (w->caretPos != -1) w->caretPos -= len; | |||
1789 | name = TabList_UNSAFE_GetPlayer(matches[0])StringsBuffer_UNSAFE_Get(&TabList._buffer, TabList.NameOffsets [matches[0]] - 3);; | |||
1790 | InputWidget_AppendText(w, &name); | |||
1791 | } else if (numMatches > 1) { | |||
1792 | String_InitArray(str, strBuffer)str.buffer = strBuffer; str.length = 0; str.capacity = sizeof (strBuffer);; | |||
1793 | String_Format1(&str, "&e%i matching names: ", &numMatches); | |||
1794 | ||||
1795 | for (i = 0; i < numMatches; i++) { | |||
1796 | name = TabList_UNSAFE_GetPlayer(matches[i])StringsBuffer_UNSAFE_Get(&TabList._buffer, TabList.NameOffsets [matches[i]] - 3);; | |||
1797 | if ((str.length + name.length + 1) > STRING_SIZE64) break; | |||
1798 | ||||
1799 | String_AppendString(&str, &name); | |||
1800 | String_Append(&str, ' '); | |||
1801 | } | |||
1802 | Chat_AddOf(&str, MSG_TYPE_CLIENTSTATUS_2); | |||
1803 | } | |||
1804 | } | |||
1805 | ||||
1806 | static int ChatInputWidget_KeyDown(void* widget, int key) { | |||
1807 | struct InputWidget* w = (struct InputWidget*)widget; | |||
1808 | if (key == KEY_TAB) { ChatInputWidget_TabKey(w); return true1; } | |||
1809 | if (key == KEY_UP) { ChatInputWidget_UpKey(w); return true1; } | |||
1810 | if (key == KEY_DOWN) { ChatInputWidget_DownKey(w); return true1; } | |||
1811 | return InputWidget_KeyDown(w, key); | |||
1812 | } | |||
1813 | ||||
1814 | static int ChatInputWidget_GetMaxLines(void) { | |||
1815 | return !Game_ClassicMode && Server.SupportsPartialMessages ? INPUTWIDGET_MAX_LINES3 : 1; | |||
1816 | } | |||
1817 | ||||
1818 | static const struct WidgetVTABLE ChatInputWidget_VTABLE = { | |||
1819 | ChatInputWidget_Render, InputWidget_Free, InputWidget_Reposition, | |||
1820 | ChatInputWidget_KeyDown, Widget_InputUp, Widget_MouseScroll, | |||
1821 | InputWidget_PointerDown, Widget_PointerUp, Widget_PointerMove | |||
1822 | }; | |||
1823 | void ChatInputWidget_Create(struct ChatInputWidget* w) { | |||
1824 | InputWidget_Reset(&w->base); | |||
1825 | w->typingLogPos = Chat_InputLog.count; /* Index of newest entry + 1. */ | |||
1826 | w->base.VTABLE = &ChatInputWidget_VTABLE; | |||
1827 | ||||
1828 | w->base.convertPercents = !Game_ClassicMode; | |||
1829 | w->base.showCaret = true1; | |||
1830 | w->base.padding = 5; | |||
1831 | w->base.GetMaxLines = ChatInputWidget_GetMaxLines; | |||
1832 | w->base.RemakeTexture = ChatInputWidget_RemakeTexture; | |||
1833 | w->base.OnPressedEnter = ChatInputWidget_OnPressedEnter; | |||
1834 | w->base.AllowedChar = InputWidget_AllowedChar; | |||
1835 | ||||
1836 | String_InitArray(w->base.text, w->_textBuffer)w->base.text.buffer = w->_textBuffer; w->base.text.length = 0; w->base.text.capacity = sizeof(w->_textBuffer);; | |||
1837 | String_InitArray(w->origStr, w->_origBuffer)w->origStr.buffer = w->_origBuffer; w->origStr.length = 0; w->origStr.capacity = sizeof(w->_origBuffer);; | |||
1838 | } | |||
1839 | ||||
1840 | void ChatInputWidget_SetFont(struct ChatInputWidget* w, struct FontDesc* font) { | |||
1841 | struct DrawTextArgs args; | |||
1842 | DrawTextArgs_Make(&args, &chatInputPrefix, font, true1); | |||
1843 | ||||
1844 | w->base.font = font; | |||
1845 | w->base.prefixWidth = Drawer2D_TextWidth(&args); | |||
1846 | w->base.lineHeight = Drawer2D_TextHeight(&args); | |||
1847 | Gfx_DeleteTexture(&w->base.caretTex.ID); | |||
1848 | } | |||
1849 | ||||
1850 | ||||
1851 | /*########################################################################################################################* | |||
1852 | *-----------------------------------------------------TextGroupWidget-----------------------------------------------------* | |||
1853 | *#########################################################################################################################*/ | |||
1854 | void TextGroupWidget_ShiftUp(struct TextGroupWidget* w) { | |||
1855 | int last, i; | |||
1856 | Gfx_DeleteTexture(&w->textures[0].ID); | |||
1857 | last = w->lines - 1; | |||
1858 | ||||
1859 | for (i = 0; i < last; i++) { | |||
1860 | w->textures[i] = w->textures[i + 1]; | |||
1861 | } | |||
1862 | w->textures[last].ID = 0; /* Gfx_DeleteTexture() called by TextGroupWidget_Redraw otherwise */ | |||
1863 | TextGroupWidget_Redraw(w, last); | |||
1864 | } | |||
1865 | ||||
1866 | void TextGroupWidget_ShiftDown(struct TextGroupWidget* w) { | |||
1867 | int last, i; | |||
1868 | last = w->lines - 1; | |||
1869 | Gfx_DeleteTexture(&w->textures[last].ID); | |||
1870 | ||||
1871 | for (i = last; i > 0; i--) { | |||
1872 | w->textures[i] = w->textures[i - 1]; | |||
1873 | } | |||
1874 | w->textures[0].ID = 0; /* Gfx_DeleteTexture() called by TextGroupWidget_Redraw otherwise */ | |||
1875 | TextGroupWidget_Redraw(w, 0); | |||
1876 | } | |||
1877 | ||||
1878 | int TextGroupWidget_UsedHeight(struct TextGroupWidget* w) { | |||
1879 | struct Texture* textures = w->textures; | |||
1880 | int i, height = 0; | |||
1881 | ||||
1882 | for (i = 0; i < w->lines; i++) { | |||
1883 | if (textures[i].ID) break; | |||
1884 | } | |||
1885 | for (; i < w->lines; i++) { | |||
1886 | height += textures[i].Height; | |||
1887 | } | |||
1888 | return height; | |||
1889 | } | |||
1890 | ||||
1891 | static void TextGroupWidget_Reposition(void* widget) { | |||
1892 | struct TextGroupWidget* w = (struct TextGroupWidget*)widget; | |||
1893 | struct Texture* textures = w->textures; | |||
1894 | int i, y, width = 0, height = 0; | |||
1895 | ||||
1896 | /* Work out how big the text group is now */ | |||
1897 | for (i = 0; i < w->lines; i++) { | |||
1898 | width = max(width, textures[i].Width)((width) > (textures[i].Width) ? (width) : (textures[i].Width )); | |||
1899 | height += textures[i].Height; | |||
1900 | } | |||
1901 | ||||
1902 | w->width = width; w->height = height; | |||
1903 | Widget_CalcPosition(w); | |||
1904 | ||||
1905 | for (i = 0, y = w->y; i < w->lines; i++) { | |||
1906 | textures[i].X = Gui_CalcPos(w->horAnchor, w->xOffset, textures[i].Width, WindowInfo.Width); | |||
1907 | textures[i].Y = y; | |||
1908 | y += textures[i].Height; | |||
1909 | } | |||
1910 | } | |||
1911 | ||||
1912 | struct Portion { short Beg, Len, LineBeg, LineLen; }; | |||
1913 | #define TEXTGROUPWIDGET_HTTP_LEN7 7 /* length of http:// */ | |||
1914 | #define TEXTGROUPWIDGET_URL0x8000 0x8000 | |||
1915 | #define TEXTGROUPWIDGET_PACKED_LEN0x7FFF 0x7FFF | |||
1916 | ||||
1917 | static int TextGroupWidget_NextUrl(char* chars, int charsLen, int i) { | |||
1918 | int start, left; | |||
1919 | ||||
1920 | for (; i < charsLen; i++) { | |||
1921 | if (!(chars[i] == 'h' || chars[i] == '&')) continue; | |||
1922 | left = charsLen - i; | |||
1923 | if (left < TEXTGROUPWIDGET_HTTP_LEN7) return charsLen; | |||
1924 | ||||
1925 | /* colour codes at start of URL */ | |||
1926 | start = i; | |||
1927 | while (left >= 2 && chars[i] == '&') { left -= 2; i += 2; } | |||
1928 | if (left < TEXTGROUPWIDGET_HTTP_LEN7) continue; | |||
1929 | ||||
1930 | /* Starts with "http" */ | |||
1931 | if (chars[i] != 'h' || chars[i + 1] != 't' || chars[i + 2] != 't' || chars[i + 3] != 'p') continue; | |||
1932 | left -= 4; i += 4; | |||
1933 | ||||
1934 | /* And then with "s://" or "://" */ | |||
1935 | if (chars[i] == 's') { left--; i++; } | |||
1936 | if (left >= 3 && chars[i] == ':' && chars[i + 1] == '/' && chars[i + 2] == '/') return start; | |||
1937 | } | |||
1938 | return charsLen; | |||
1939 | } | |||
1940 | ||||
1941 | static int TextGroupWidget_UrlEnd(char* chars, int charsLen, int* begs, int begsLen, int i) { | |||
1942 | int start = i, j; | |||
1943 | int next, left; | |||
1944 | cc_bool isBeg; | |||
1945 | ||||
1946 | for (; i < charsLen && chars[i] != ' '; i++) { | |||
1947 | /* Is this character the start of a line */ | |||
1948 | isBeg = false0; | |||
1949 | for (j = 0; j < begsLen; j++) { | |||
1950 | if (i == begs[j]) { isBeg = true1; break; } | |||
1951 | } | |||
1952 | ||||
1953 | /* Definitely not a multilined URL */ | |||
1954 | if (!isBeg || i == start) continue; | |||
1955 | if (chars[i] != '>') break; | |||
1956 | ||||
1957 | /* Does this line start with "> ", making it a multiline */ | |||
1958 | next = i + 1; left = charsLen - next; | |||
1959 | while (left >= 2 && chars[next] == '&') { left -= 2; next += 2; } | |||
1960 | if (left == 0 || chars[next] != ' ') break; | |||
1961 | ||||
1962 | i = next; | |||
1963 | } | |||
1964 | return i; | |||
1965 | } | |||
1966 | ||||
1967 | static void TextGroupWidget_Output(struct Portion bit, int lineBeg, int lineEnd, struct Portion** portions) { | |||
1968 | struct Portion* cur; | |||
1969 | int overBy, underBy; | |||
1970 | if (bit.Beg >= lineEnd || !bit.Len) return; | |||
1971 | ||||
1972 | bit.LineBeg = bit.Beg; | |||
1973 | bit.LineLen = bit.Len & TEXTGROUPWIDGET_PACKED_LEN0x7FFF; | |||
1974 | ||||
1975 | /* Adjust this portion to be within this line */ | |||
1976 | if (bit.Beg >= lineBeg) { | |||
1977 | } else if (bit.Beg + bit.LineLen > lineBeg) { | |||
1978 | /* Adjust start of portion to be within this line */ | |||
1979 | underBy = lineBeg - bit.Beg; | |||
1980 | bit.LineBeg += underBy; bit.LineLen -= underBy; | |||
1981 | } else { return; } | |||
1982 | ||||
1983 | /* Limit length of portion to be within this line */ | |||
1984 | overBy = (bit.LineBeg + bit.LineLen) - lineEnd; | |||
1985 | if (overBy > 0) bit.LineLen -= overBy; | |||
1986 | ||||
1987 | bit.LineBeg -= lineBeg; | |||
1988 | if (!bit.LineLen) return; | |||
1989 | ||||
1990 | cur = *portions; *cur++ = bit; *portions = cur; | |||
1991 | } | |||
1992 | ||||
1993 | static int TextGroupWidget_Reduce(struct TextGroupWidget* w, char* chars, int target, struct Portion* portions) { | |||
1994 | struct Portion* start = portions; | |||
1995 | int begs[TEXTGROUPWIDGET_MAX_LINES30]; | |||
1996 | int ends[TEXTGROUPWIDGET_MAX_LINES30]; | |||
1997 | struct Portion bit; | |||
1998 | cc_string line; | |||
1999 | int nextStart, i, total = 0, end; | |||
2000 | ||||
2001 | for (i = 0; i < w->lines; i++) { | |||
2002 | line = TextGroupWidget_UNSAFE_Get(w, i); | |||
2003 | begs[i] = -1; ends[i] = -1; | |||
2004 | if (!line.length) continue; | |||
2005 | ||||
2006 | begs[i] = total; | |||
2007 | Mem_Copy(&chars[total], line.buffer, line.length); | |||
2008 | total += line.length; ends[i] = total; | |||
2009 | } | |||
2010 | ||||
2011 | end = 0; | |||
2012 | for (;;) { | |||
2013 | nextStart = TextGroupWidget_NextUrl(chars, total, end); | |||
2014 | ||||
2015 | /* add normal portion between urls */ | |||
2016 | bit.Beg = end; | |||
2017 | bit.Len = nextStart - end; | |||
2018 | TextGroupWidget_Output(bit, begs[target], ends[target], &portions); | |||
| ||||
2019 | ||||
2020 | if (nextStart == total) break; | |||
2021 | end = TextGroupWidget_UrlEnd(chars, total, begs, w->lines, nextStart); | |||
2022 | ||||
2023 | /* add this url portion */ | |||
2024 | bit.Beg = nextStart; | |||
2025 | bit.Len = (end - nextStart) | TEXTGROUPWIDGET_URL0x8000; | |||
2026 | TextGroupWidget_Output(bit, begs[target], ends[target], &portions); | |||
2027 | } | |||
2028 | return (int)(portions - start); | |||
2029 | } | |||
2030 | ||||
2031 | static void TextGroupWidget_FormatUrl(cc_string* text, const cc_string* url) { | |||
2032 | char* dst; | |||
2033 | int i; | |||
2034 | String_AppendColorless(text, url); | |||
2035 | ||||
2036 | /* Delete "> " multiline chars from URLs */ | |||
2037 | dst = text->buffer; | |||
2038 | for (i = text->length - 2; i >= 0; i--) { | |||
2039 | if (dst[i] != '>' || dst[i + 1] != ' ') continue; | |||
2040 | ||||
2041 | String_DeleteAt(text, i + 1); | |||
2042 | String_DeleteAt(text, i); | |||
2043 | } | |||
2044 | } | |||
2045 | ||||
2046 | static cc_bool TextGroupWidget_GetUrl(struct TextGroupWidget* w, cc_string* text, int index, int mouseX) { | |||
2047 | char chars[TEXTGROUPWIDGET_MAX_LINES30 * TEXTGROUPWIDGET_LEN(64 + (64 / 2))]; | |||
2048 | struct Portion portions[2 * (TEXTGROUPWIDGET_LEN(64 + (64 / 2)) / TEXTGROUPWIDGET_HTTP_LEN7)]; | |||
2049 | struct Portion bit; | |||
2050 | struct DrawTextArgs args = { 0 }; | |||
2051 | cc_string line, url; | |||
2052 | int portionsCount; | |||
2053 | int i, x, width; | |||
2054 | ||||
2055 | mouseX -= w->textures[index].X; | |||
2056 | args.useShadow = true1; | |||
2057 | line = TextGroupWidget_UNSAFE_Get(w, index); | |||
2058 | ||||
2059 | if (Game_ClassicMode) return false0; | |||
2060 | portionsCount = TextGroupWidget_Reduce(w, chars, index, portions); | |||
2061 | ||||
2062 | for (i = 0, x = 0; i < portionsCount; i++) { | |||
2063 | bit = portions[i]; | |||
2064 | args.text = String_UNSAFE_Substring(&line, bit.LineBeg, bit.LineLen); | |||
2065 | args.font = w->font; | |||
2066 | ||||
2067 | width = Drawer2D_TextWidth(&args); | |||
2068 | if ((bit.Len & TEXTGROUPWIDGET_URL0x8000) && mouseX >= x && mouseX < x + width) { | |||
2069 | bit.Len &= TEXTGROUPWIDGET_PACKED_LEN0x7FFF; | |||
2070 | url = String_Init(&chars[bit.Beg], bit.Len, bit.Len); | |||
2071 | ||||
2072 | TextGroupWidget_FormatUrl(text, &url); | |||
2073 | return true1; | |||
2074 | } | |||
2075 | x += width; | |||
2076 | } | |||
2077 | return false0; | |||
2078 | } | |||
2079 | ||||
2080 | int TextGroupWidget_GetSelected(struct TextGroupWidget* w, cc_string* text, int x, int y) { | |||
2081 | struct Texture tex; | |||
2082 | cc_string line; | |||
2083 | int i; | |||
2084 | ||||
2085 | for (i = 0; i < w->lines; i++) { | |||
2086 | if (!w->textures[i].ID) continue; | |||
2087 | tex = w->textures[i]; | |||
2088 | if (!Gui_Contains(tex.X, tex.Y, tex.Width, tex.Height, x, y)) continue; | |||
2089 | ||||
2090 | if (!TextGroupWidget_GetUrl(w, text, i, x)) { | |||
2091 | line = TextGroupWidget_UNSAFE_Get(w, i); | |||
2092 | String_AppendString(text, &line); | |||
2093 | } | |||
2094 | return i; | |||
2095 | } | |||
2096 | return -1; | |||
2097 | } | |||
2098 | ||||
2099 | static cc_bool TextGroupWidget_MightHaveUrls(struct TextGroupWidget* w) { | |||
2100 | cc_string line; | |||
2101 | int i; | |||
2102 | ||||
2103 | for (i = 0; i < w->lines; i++) { | |||
2104 | line = TextGroupWidget_UNSAFE_Get(w, i); | |||
2105 | if (String_IndexOf(&line, '/')String_IndexOfAt(&line, 0, '/') >= 0) return true1; | |||
2106 | } | |||
2107 | return false0; | |||
2108 | } | |||
2109 | ||||
2110 | static void TextGroupWidget_DrawAdvanced(struct TextGroupWidget* w, struct Texture* tex, struct DrawTextArgs* args, int index, const cc_string* text) { | |||
2111 | char chars[TEXTGROUPWIDGET_MAX_LINES30 * TEXTGROUPWIDGET_LEN(64 + (64 / 2))]; | |||
2112 | struct Portion portions[2 * (TEXTGROUPWIDGET_LEN(64 + (64 / 2)) / TEXTGROUPWIDGET_HTTP_LEN7)]; | |||
2113 | struct Portion bit; | |||
2114 | int width, height; | |||
2115 | int partWidths[Array_Elems(portions)(sizeof(portions) / sizeof(portions[0]))]; | |||
2116 | struct Bitmap bmp; | |||
2117 | int portionsCount; | |||
2118 | int i, x, ul; | |||
2119 | ||||
2120 | width = 0; | |||
2121 | height = Drawer2D_TextHeight(args); | |||
2122 | portionsCount = TextGroupWidget_Reduce(w, chars, index, portions); | |||
2123 | ||||
2124 | for (i = 0; i < portionsCount; i++) { | |||
2125 | bit = portions[i]; | |||
2126 | args->text = String_UNSAFE_Substring(text, bit.LineBeg, bit.LineLen); | |||
2127 | ||||
2128 | partWidths[i] = Drawer2D_TextWidth(args); | |||
2129 | width += partWidths[i]; | |||
2130 | } | |||
2131 | ||||
2132 | Bitmap_AllocateClearedPow2(&bmp, width, height); | |||
2133 | { | |||
2134 | x = 0; | |||
2135 | for (i = 0; i < portionsCount; i++) { | |||
2136 | bit = portions[i]; | |||
2137 | ul = (bit.Len & TEXTGROUPWIDGET_URL0x8000); | |||
2138 | args->text = String_UNSAFE_Substring(text, bit.LineBeg, bit.LineLen); | |||
2139 | ||||
2140 | if (ul) args->font->flags |= FONT_FLAGS_UNDERLINE; | |||
2141 | Drawer2D_DrawText(&bmp, args, x, 0); | |||
2142 | if (ul) args->font->flags &= ~FONT_FLAGS_UNDERLINE; | |||
2143 | ||||
2144 | x += partWidths[i]; | |||
2145 | } | |||
2146 | Drawer2D_MakeTexture(tex, &bmp, width, height); | |||
2147 | } | |||
2148 | Mem_Free(bmp.scan0); | |||
2149 | } | |||
2150 | ||||
2151 | void TextGroupWidget_RedrawAll(struct TextGroupWidget* w) { | |||
2152 | int i; | |||
2153 | for (i = 0; i < w->lines; i++) { TextGroupWidget_Redraw(w, i); } | |||
2154 | } | |||
2155 | ||||
2156 | void TextGroupWidget_Redraw(struct TextGroupWidget* w, int index) { | |||
2157 | cc_string text; | |||
2158 | struct DrawTextArgs args; | |||
2159 | struct Texture tex = { 0 }; | |||
2160 | Gfx_DeleteTexture(&w->textures[index].ID); | |||
2161 | ||||
2162 | text = TextGroupWidget_UNSAFE_Get(w, index); | |||
2163 | if (!Drawer2D_IsEmptyText(&text)) { | |||
2164 | DrawTextArgs_Make(&args, &text, w->font, true1); | |||
2165 | ||||
2166 | if (w->underlineUrls && TextGroupWidget_MightHaveUrls(w)) { | |||
2167 | TextGroupWidget_DrawAdvanced(w, &tex, &args, index, &text); | |||
2168 | } else { | |||
2169 | Drawer2D_MakeTextTexture(&tex, &args); | |||
2170 | } | |||
2171 | Drawer2D_ReducePadding_Tex(&tex, w->font->size, 3); | |||
2172 | } else { | |||
2173 | tex.Height = w->collapsible[index] ? 0 : w->defaultHeight; | |||
2174 | } | |||
2175 | ||||
2176 | tex.X = Gui_CalcPos(w->horAnchor, w->xOffset, tex.Width, WindowInfo.Width); | |||
2177 | w->textures[index] = tex; | |||
2178 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
2179 | } | |||
2180 | ||||
2181 | void TextGroupWidget_RedrawAllWithCol(struct TextGroupWidget* group, char col) { | |||
2182 | cc_string line; | |||
2183 | int i, j; | |||
2184 | ||||
2185 | for (i = 0; i < group->lines; i++) { | |||
| ||||
2186 | line = TextGroupWidget_UNSAFE_Get(group, i); | |||
2187 | if (!line.length) continue; | |||
2188 | ||||
2189 | for (j = 0; j < line.length - 1; j++) { | |||
2190 | if (line.buffer[j] == '&' && line.buffer[j + 1] == col) { | |||
2191 | TextGroupWidget_Redraw(group, i); | |||
2192 | break; | |||
2193 | } | |||
2194 | } | |||
2195 | } | |||
2196 | } | |||
2197 | ||||
2198 | ||||
2199 | void TextGroupWidget_SetFont(struct TextGroupWidget* w, struct FontDesc* font) { | |||
2200 | int i, height; | |||
2201 | ||||
2202 | height = Drawer2D_FontHeight(font, true1); | |||
2203 | Drawer2D_ReducePadding_Height(&height, font->size, 3); | |||
2204 | w->defaultHeight = height; | |||
2205 | ||||
2206 | for (i = 0; i < w->lines; i++) { | |||
2207 | w->textures[i].Height = w->collapsible[i] ? 0 : height; | |||
2208 | } | |||
2209 | w->font = font; | |||
2210 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
2211 | } | |||
2212 | ||||
2213 | static void TextGroupWidget_Render(void* widget, double delta) { | |||
2214 | struct TextGroupWidget* w = (struct TextGroupWidget*)widget; | |||
2215 | struct Texture* textures = w->textures; | |||
2216 | int i; | |||
2217 | ||||
2218 | for (i = 0; i < w->lines; i++) { | |||
2219 | if (!textures[i].ID) continue; | |||
2220 | Texture_Render(&textures[i]); | |||
2221 | } | |||
2222 | } | |||
2223 | ||||
2224 | static void TextGroupWidget_Free(void* widget) { | |||
2225 | struct TextGroupWidget* w = (struct TextGroupWidget*)widget; | |||
2226 | int i; | |||
2227 | ||||
2228 | for (i = 0; i < w->lines; i++) { | |||
2229 | Gfx_DeleteTexture(&w->textures[i].ID); | |||
2230 | } | |||
2231 | } | |||
2232 | ||||
2233 | static const struct WidgetVTABLE TextGroupWidget_VTABLE = { | |||
2234 | TextGroupWidget_Render, TextGroupWidget_Free, TextGroupWidget_Reposition, | |||
2235 | Widget_InputDown, Widget_InputUp, Widget_MouseScroll, | |||
2236 | Widget_Pointer, Widget_PointerUp, Widget_PointerMove | |||
2237 | }; | |||
2238 | void TextGroupWidget_Create(struct TextGroupWidget* w, int lines, struct Texture* textures, TextGroupWidget_Get getLine) { | |||
2239 | Widget_Reset(w); | |||
2240 | w->VTABLE = &TextGroupWidget_VTABLE; | |||
2241 | w->lines = lines; | |||
2242 | w->textures = textures; | |||
2243 | w->GetLine = getLine; | |||
2244 | } | |||
2245 | ||||
2246 | ||||
2247 | /*########################################################################################################################* | |||
2248 | *---------------------------------------------------SpecialInputWidget----------------------------------------------------* | |||
2249 | *#########################################################################################################################*/ | |||
2250 | static void SpecialInputWidget_UpdateColString(struct SpecialInputWidget* w) { | |||
2251 | int i; | |||
2252 | String_InitArray(w->colString, w->_colBuffer)w->colString.buffer = w->_colBuffer; w->colString.length = 0; w->colString.capacity = sizeof(w->_colBuffer);; | |||
2253 | ||||
2254 | for (i = 0; i < DRAWER2D_MAX_COLS256; i++) { | |||
2255 | if (i >= 'A' && i <= 'F') continue; | |||
2256 | if (!BitmapCol_A(Drawer2D_Cols[i])((cc_uint8)(Drawer2D_Cols[i] >> 24))) continue; | |||
2257 | ||||
2258 | String_Append(&w->colString, '&'); String_Append(&w->colString, (char)i); | |||
2259 | String_Append(&w->colString, '%'); String_Append(&w->colString, (char)i); | |||
2260 | } | |||
2261 | } | |||
2262 | ||||
2263 | static cc_bool SpecialInputWidget_IntersectsTitle(struct SpecialInputWidget* w, int x, int y) { | |||
2264 | int i, width, titleX = 0; | |||
2265 | ||||
2266 | for (i = 0; i < Array_Elems(w->tabs)(sizeof(w->tabs) / sizeof(w->tabs[0])); i++) { | |||
2267 | width = w->tabs[i].titleWidth; | |||
2268 | if (Gui_Contains(titleX, 0, width, w->titleHeight, x, y)) { | |||
2269 | w->selectedIndex = i; | |||
2270 | return true1; | |||
2271 | } | |||
2272 | titleX += width; | |||
2273 | } | |||
2274 | return false0; | |||
2275 | } | |||
2276 | ||||
2277 | static void SpecialInputWidget_IntersectsBody(struct SpecialInputWidget* w, int x, int y) { | |||
2278 | struct SpecialInputTab e = w->tabs[w->selectedIndex]; | |||
2279 | cc_string str; | |||
2280 | int i; | |||
2281 | ||||
2282 | y -= w->titleHeight; | |||
2283 | x /= w->elementWidth; y /= w->elementHeight; | |||
2284 | ||||
2285 | i = (x + y * e.itemsPerRow) * e.charsPerItem; | |||
2286 | if (i >= e.contents.length) return; | |||
2287 | ||||
2288 | /* TODO: need to insert characters that don't affect w->caretPos index, adjust w->caretPos colour */ | |||
2289 | str = String_Init(&e.contents.buffer[i], e.charsPerItem, 0); | |||
2290 | ||||
2291 | /* TODO: Not be so hacky */ | |||
2292 | if (w->selectedIndex == 0) str.length = 2; | |||
2293 | InputWidget_AppendText(w->target, &str); | |||
2294 | } | |||
2295 | ||||
2296 | static void SpecialInputTab_Init(struct SpecialInputTab* tab, STRING_REF cc_string* title, int itemsPerRow, int charsPerItem, STRING_REF cc_string* contents) { | |||
2297 | tab->title = *title; | |||
2298 | tab->titleWidth = 0; | |||
2299 | tab->contents = *contents; | |||
2300 | tab->itemsPerRow = itemsPerRow; | |||
2301 | tab->charsPerItem = charsPerItem; | |||
2302 | } | |||
2303 | ||||
2304 | static void SpecialInputWidget_InitTabs(struct SpecialInputWidget* w) { | |||
2305 | static cc_string title_cols = String_FromConst("Colours"){ "Colours", (sizeof("Colours") - 1), (sizeof("Colours") - 1) }; | |||
2306 | static cc_string title_math = String_FromConst("Math"){ "Math", (sizeof("Math") - 1), (sizeof("Math") - 1)}; | |||
2307 | static cc_string tab_math = String_FromConst("\x9F\xAB\xAC\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xFB\xFC\xFD"){ "\x9F\xAB\xAC\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xFB\xFC\xFD" , (sizeof("\x9F\xAB\xAC\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xFB\xFC\xFD" ) - 1), (sizeof("\x9F\xAB\xAC\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xFB\xFC\xFD" ) - 1)}; | |||
2308 | static cc_string title_line = String_FromConst("Line/Box"){ "Line/Box", (sizeof("Line/Box") - 1), (sizeof("Line/Box") - 1)}; | |||
2309 | static cc_string tab_line = String_FromConst("\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFE"){ "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFE" , (sizeof("\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFE" ) - 1), (sizeof("\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFE" ) - 1)}; | |||
2310 | static cc_string title_letters = String_FromConst("Letters"){ "Letters", (sizeof("Letters") - 1), (sizeof("Letters") - 1) }; | |||
2311 | static cc_string tab_letters = String_FromConst("\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\xA0\xA1\xA2\xA3\xA4\xA5"){ "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\xA0\xA1\xA2\xA3\xA4\xA5" , (sizeof("\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\xA0\xA1\xA2\xA3\xA4\xA5" ) - 1), (sizeof("\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\xA0\xA1\xA2\xA3\xA4\xA5" ) - 1)}; | |||
2312 | static cc_string title_other = String_FromConst("Other"){ "Other", (sizeof("Other") - 1), (sizeof("Other") - 1)}; | |||
2313 | static cc_string tab_other = String_FromConst("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x9B\x9C\x9D\x9E\xA6\xA7\xA8\xA9\xAA\xAD\xAE\xAF\xF9\xFA"){ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x9B\x9C\x9D\x9E\xA6\xA7\xA8\xA9\xAA\xAD\xAE\xAF\xF9\xFA" , (sizeof("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x9B\x9C\x9D\x9E\xA6\xA7\xA8\xA9\xAA\xAD\xAE\xAF\xF9\xFA" ) - 1), (sizeof("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x9B\x9C\x9D\x9E\xA6\xA7\xA8\xA9\xAA\xAD\xAE\xAF\xF9\xFA" ) - 1)}; | |||
2314 | ||||
2315 | SpecialInputWidget_UpdateColString(w); | |||
2316 | SpecialInputTab_Init(&w->tabs[0], &title_cols, 10, 4, &w->colString); | |||
2317 | SpecialInputTab_Init(&w->tabs[1], &title_math, 16, 1, &tab_math); | |||
2318 | SpecialInputTab_Init(&w->tabs[2], &title_line, 17, 1, &tab_line); | |||
2319 | SpecialInputTab_Init(&w->tabs[3], &title_letters, 17, 1, &tab_letters); | |||
2320 | SpecialInputTab_Init(&w->tabs[4], &title_other, 16, 1, &tab_other); | |||
2321 | } | |||
2322 | ||||
2323 | #define SPECIAL_TITLE_SPACING10 10 | |||
2324 | #define SPECIAL_CONTENT_SPACING5 5 | |||
2325 | static int SpecialInputWidget_MeasureTitles(struct SpecialInputWidget* w) { | |||
2326 | struct DrawTextArgs args; | |||
2327 | int i, width = 0; | |||
2328 | ||||
2329 | DrawTextArgs_MakeEmpty(&args, w->font, false0); | |||
2330 | for (i = 0; i < Array_Elems(w->tabs)(sizeof(w->tabs) / sizeof(w->tabs[0])); i++) { | |||
2331 | args.text = w->tabs[i].title; | |||
2332 | ||||
2333 | w->tabs[i].titleWidth = Drawer2D_TextWidth(&args) + SPECIAL_TITLE_SPACING10; | |||
2334 | width += w->tabs[i].titleWidth; | |||
2335 | } | |||
2336 | ||||
2337 | w->titleHeight = Drawer2D_TextHeight(&args); | |||
2338 | return width; | |||
2339 | } | |||
2340 | ||||
2341 | static void SpecialInputWidget_DrawTitles(struct SpecialInputWidget* w, struct Bitmap* bmp) { | |||
2342 | BitmapCol col_selected = BitmapCol_Make(30, 30, 30, 200)(((cc_uint8)(30) << 16) | ((cc_uint8)(30) << 8) | ((cc_uint8)(30) << 0) | ((cc_uint8)(200) << 24)); | |||
2343 | BitmapCol col_inactive = BitmapCol_Make( 0, 0, 0, 127)(((cc_uint8)(0) << 16) | ((cc_uint8)(0) << 8) | ( (cc_uint8)(0) << 0) | ((cc_uint8)(127) << 24)); | |||
2344 | BitmapCol col; | |||
2345 | struct DrawTextArgs args; | |||
2346 | int i, width, x = 0; | |||
2347 | ||||
2348 | DrawTextArgs_MakeEmpty(&args, w->font, false0); | |||
2349 | for (i = 0; i < Array_Elems(w->tabs)(sizeof(w->tabs) / sizeof(w->tabs[0])); i++) { | |||
2350 | args.text = w->tabs[i].title; | |||
2351 | col = i == w->selectedIndex ? col_selected : col_inactive; | |||
2352 | width = w->tabs[i].titleWidth; | |||
2353 | ||||
2354 | Drawer2D_Clear(bmp, col, x, 0, width, w->titleHeight); | |||
2355 | Drawer2D_DrawText(bmp, &args, x + SPECIAL_TITLE_SPACING10 / 2, 0); | |||
2356 | x += width; | |||
2357 | } | |||
2358 | } | |||
2359 | ||||
2360 | static int SpecialInputWidget_MeasureContent(struct SpecialInputWidget* w, struct SpecialInputTab* tab) { | |||
2361 | struct DrawTextArgs args; | |||
2362 | int textWidth, textHeight; | |||
2363 | int i, maxWidth = 0; | |||
2364 | ||||
2365 | DrawTextArgs_MakeEmpty(&args, w->font, false0); | |||
2366 | args.text.length = tab->charsPerItem; | |||
2367 | textHeight = Drawer2D_TextHeight(&args); | |||
2368 | ||||
2369 | for (i = 0; i < tab->contents.length; i += tab->charsPerItem) { | |||
2370 | args.text.buffer = &tab->contents.buffer[i]; | |||
2371 | textWidth = Drawer2D_TextWidth(&args); | |||
2372 | maxWidth = max(maxWidth, textWidth)((maxWidth) > (textWidth) ? (maxWidth) : (textWidth)); | |||
2373 | } | |||
2374 | ||||
2375 | w->elementWidth = maxWidth + SPECIAL_CONTENT_SPACING5; | |||
2376 | w->elementHeight = textHeight + SPECIAL_CONTENT_SPACING5; | |||
2377 | return w->elementWidth * tab->itemsPerRow; | |||
2378 | } | |||
2379 | ||||
2380 | static int SpecialInputWidget_ContentHeight(struct SpecialInputWidget* w, struct SpecialInputTab* tab) { | |||
2381 | int rows = Math_CeilDiv(tab->contents.length / tab->charsPerItem, tab->itemsPerRow); | |||
2382 | return w->elementHeight * rows; | |||
2383 | } | |||
2384 | ||||
2385 | static void SpecialInputWidget_DrawContent(struct SpecialInputWidget* w, struct SpecialInputTab* tab, struct Bitmap* bmp, int yOffset) { | |||
2386 | struct DrawTextArgs args; | |||
2387 | int i, x, y, item; | |||
2388 | ||||
2389 | int wrap = tab->itemsPerRow; | |||
2390 | DrawTextArgs_MakeEmpty(&args, w->font, false0); | |||
2391 | args.text.length = tab->charsPerItem; | |||
2392 | ||||
2393 | for (i = 0; i < tab->contents.length; i += tab->charsPerItem) { | |||
2394 | args.text.buffer = &tab->contents.buffer[i]; | |||
2395 | item = i / tab->charsPerItem; | |||
2396 | ||||
2397 | x = (item % wrap) * w->elementWidth; | |||
2398 | y = (item / wrap) * w->elementHeight + yOffset; | |||
2399 | Drawer2D_DrawText(bmp, &args, x, y); | |||
2400 | } | |||
2401 | } | |||
2402 | ||||
2403 | static void SpecialInputWidget_Make(struct SpecialInputWidget* w, struct SpecialInputTab* tab) { | |||
2404 | BitmapCol col = BitmapCol_Make(30, 30, 30, 200)(((cc_uint8)(30) << 16) | ((cc_uint8)(30) << 8) | ((cc_uint8)(30) << 0) | ((cc_uint8)(200) << 24)); | |||
2405 | int titlesWidth, titlesHeight; | |||
2406 | int contentWidth, contentHeight; | |||
2407 | int width, height; | |||
2408 | struct Bitmap bmp; | |||
2409 | ||||
2410 | titlesWidth = SpecialInputWidget_MeasureTitles(w); | |||
2411 | titlesHeight = w->titleHeight; | |||
2412 | contentWidth = SpecialInputWidget_MeasureContent(w, tab); | |||
2413 | contentHeight = SpecialInputWidget_ContentHeight(w, tab); | |||
2414 | ||||
2415 | width = max(titlesWidth, contentWidth)((titlesWidth) > (contentWidth) ? (titlesWidth) : (contentWidth )); | |||
2416 | height = titlesHeight + contentHeight; | |||
2417 | Gfx_DeleteTexture(&w->tex.ID); | |||
2418 | ||||
2419 | Bitmap_AllocateClearedPow2(&bmp, width, height); | |||
2420 | { | |||
2421 | SpecialInputWidget_DrawTitles(w, &bmp); | |||
2422 | Drawer2D_Clear(&bmp, col, 0, titlesHeight, width, contentHeight); | |||
2423 | SpecialInputWidget_DrawContent(w, tab, &bmp, titlesHeight); | |||
2424 | } | |||
2425 | Drawer2D_MakeTexture(&w->tex, &bmp, width, height); | |||
2426 | Mem_Free(bmp.scan0); | |||
2427 | } | |||
2428 | ||||
2429 | void SpecialInputWidget_Redraw(struct SpecialInputWidget* w) { | |||
2430 | SpecialInputWidget_Make(w, &w->tabs[w->selectedIndex]); | |||
2431 | w->pendingRedraw = false0; | |||
2432 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
2433 | } | |||
2434 | ||||
2435 | static void SpecialInputWidget_Render(void* widget, double delta) { | |||
2436 | struct SpecialInputWidget* w = (struct SpecialInputWidget*)widget; | |||
2437 | Texture_Render(&w->tex); | |||
2438 | } | |||
2439 | ||||
2440 | static void SpecialInputWidget_Free(void* widget) { | |||
2441 | struct SpecialInputWidget* w = (struct SpecialInputWidget*)widget; | |||
2442 | Gfx_DeleteTexture(&w->tex.ID); | |||
2443 | } | |||
2444 | ||||
2445 | static void SpecialInputWidget_Reposition(void* widget) { | |||
2446 | struct SpecialInputWidget* w = (struct SpecialInputWidget*)widget; | |||
2447 | w->width = w->tex.Width; | |||
2448 | w->height = w->active ? w->tex.Height : 0; | |||
2449 | Widget_CalcPosition(w); | |||
2450 | w->tex.X = w->x; w->tex.Y = w->y; | |||
2451 | } | |||
2452 | ||||
2453 | static int SpecialInputWidget_PointerDown(void* widget, int id, int x, int y) { | |||
2454 | struct SpecialInputWidget* w = (struct SpecialInputWidget*)widget; | |||
2455 | x -= w->x; y -= w->y; | |||
2456 | ||||
2457 | if (SpecialInputWidget_IntersectsTitle(w, x, y)) { | |||
2458 | SpecialInputWidget_Redraw(w); | |||
2459 | } else { | |||
2460 | SpecialInputWidget_IntersectsBody(w, x, y); | |||
2461 | } | |||
2462 | return TOUCH_TYPE_GUI1; | |||
2463 | } | |||
2464 | ||||
2465 | void SpecialInputWidget_UpdateCols(struct SpecialInputWidget* w) { | |||
2466 | SpecialInputWidget_UpdateColString(w); | |||
2467 | w->tabs[0].contents = w->colString; | |||
2468 | if (w->selectedIndex != 0) return; | |||
2469 | ||||
2470 | /* defer updating colours tab until visible */ | |||
2471 | if (!w->active) { w->pendingRedraw = true1; return; } | |||
2472 | SpecialInputWidget_Redraw(w); | |||
2473 | } | |||
2474 | ||||
2475 | void SpecialInputWidget_SetActive(struct SpecialInputWidget* w, cc_bool active) { | |||
2476 | w->active = active; | |||
2477 | if (active && w->pendingRedraw) SpecialInputWidget_Redraw(w); | |||
2478 | Widget_Layout(w)(w)->VTABLE->Reposition(w); | |||
2479 | } | |||
2480 | ||||
2481 | static const struct WidgetVTABLE SpecialInputWidget_VTABLE = { | |||
2482 | SpecialInputWidget_Render, SpecialInputWidget_Free, SpecialInputWidget_Reposition, | |||
2483 | Widget_InputDown, Widget_InputUp, Widget_MouseScroll, | |||
2484 | SpecialInputWidget_PointerDown, Widget_PointerUp, Widget_PointerMove | |||
2485 | }; | |||
2486 | void SpecialInputWidget_Create(struct SpecialInputWidget* w, struct FontDesc* font, struct InputWidget* target) { | |||
2487 | Widget_Reset(w); | |||
2488 | w->VTABLE = &SpecialInputWidget_VTABLE; | |||
2489 | w->verAnchor = ANCHOR_MAX; | |||
2490 | w->font = font; | |||
2491 | w->target = target; | |||
2492 | SpecialInputWidget_InitTabs(w); | |||
2493 | } | |||
2494 | ||||
2495 | ||||
2496 | /*########################################################################################################################* | |||
2497 | *----------------------------------------------------ThumbstickWidget-----------------------------------------------------* | |||
2498 | *#########################################################################################################################*/ | |||
2499 | #ifdef CC_BUILD_TOUCH | |||
2500 | #define DIR_YMAX (1 << 0) | |||
2501 | #define DIR_YMIN (1 << 1) | |||
2502 | #define DIR_XMAX (1 << 2) | |||
2503 | #define DIR_XMIN (1 << 3) | |||
2504 | ||||
2505 | static void ThumbstickWidget_Rotate(void* widget, struct VertexTextured** vertices, int offset) { | |||
2506 | struct ThumbstickWidget* w = (struct ThumbstickWidget*)widget; | |||
2507 | struct VertexTextured* ptr; | |||
2508 | int i, x, y; | |||
2509 | ||||
2510 | ptr = *vertices - 4; | |||
2511 | for (i = 0; i < 4; i++) { | |||
2512 | int x = ptr[i].X - w->x; | |||
2513 | int y = ptr[i].Y - w->y; | |||
2514 | ptr[i].X = -y + w->x + offset; | |||
2515 | ptr[i].Y = x + w->y; | |||
2516 | } | |||
2517 | } | |||
2518 | ||||
2519 | static void ThumbstickWidget_BuildGroup(void* widget, struct Texture* tex, struct VertexTextured** vertices) { | |||
2520 | struct ThumbstickWidget* w = (struct ThumbstickWidget*)widget; | |||
2521 | float tmp; | |||
2522 | tex->Y = w->y + w->height / 2; | |||
2523 | Gfx_Make2DQuad(tex, PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )), vertices); | |||
2524 | ||||
2525 | tex->Y = w->y; | |||
2526 | tmp = tex->uv.V1; tex->uv.V1 = tex->uv.V2; tex->uv.V2 = tmp; | |||
2527 | Gfx_Make2DQuad(tex, PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )), vertices); | |||
2528 | ||||
2529 | Gfx_Make2DQuad(tex, PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )), vertices); | |||
2530 | ThumbstickWidget_Rotate(widget, vertices, w->width); | |||
2531 | ||||
2532 | tmp = tex->uv.V1; tex->uv.V1 = tex->uv.V2; tex->uv.V2 = tmp; | |||
2533 | Gfx_Make2DQuad(tex, PACKEDCOL_WHITE(((cc_uint8)(255) << 0) | ((cc_uint8)(255) << 8) | ((cc_uint8)(255) << 16) | ((cc_uint8)(255) << 24 )), vertices); | |||
2534 | ThumbstickWidget_Rotate(widget, vertices, w->width / 2); | |||
2535 | } | |||
2536 | ||||
2537 | static void ThumbstickWidget_BuildMesh(void* widget, struct VertexTextured** vertices) { | |||
2538 | struct ThumbstickWidget* w = (struct ThumbstickWidget*)widget; | |||
2539 | struct Texture tex; | |||
2540 | ||||
2541 | tex.X = w->x; | |||
2542 | tex.Width = w->width; tex.Height = w->height / 2; | |||
2543 | tex.uv.U1 = 0.0f; tex.uv.U2 = 1.0f; | |||
2544 | ||||
2545 | tex.uv.V1 = 0.0f; tex.uv.V2 = 0.5f; | |||
2546 | ThumbstickWidget_BuildGroup(widget, &tex, vertices); | |||
2547 | tex.uv.V1 = 0.5f; tex.uv.V2 = 1.0f; | |||
2548 | ThumbstickWidget_BuildGroup(widget, &tex, vertices); | |||
2549 | } | |||
2550 | ||||
2551 | static int ThumbstickWidget_CalcDirs(struct ThumbstickWidget* w) { | |||
2552 | int i, dx, dy, dirs = 0; | |||
2553 | double angle; | |||
2554 | ||||
2555 | for (i = 0; i < INPUT_MAX_POINTERS1; i++) { | |||
2556 | if (!(w->active & (1 << i))) continue; | |||
2557 | ||||
2558 | dx = Pointers[i].x - (w->x + w->width / 2); | |||
2559 | dy = Pointers[i].y - (w->y + w->height / 2); | |||
2560 | angle = Math_Atan2(dx, dy) * MATH_RAD2DEG(180.0f / 3.1415926535897931f); | |||
2561 | ||||
2562 | /* 4 quadrants diagonally, but slightly expanded for overlap*/ | |||
2563 | if (angle >= 30 && angle <= 150) dirs |= DIR_YMAX; | |||
2564 | if (angle >= -60 && angle <= 60) dirs |= DIR_XMAX; | |||
2565 | if (angle >= -150 && angle <= -30) dirs |= DIR_YMIN; | |||
2566 | if (angle < -120 || angle > 120) dirs |= DIR_XMIN; | |||
2567 | } | |||
2568 | return dirs; | |||
2569 | } | |||
2570 | ||||
2571 | static int ThumbstickWidget_Render2(void* widget, int offset) { | |||
2572 | struct ThumbstickWidget* w = (struct ThumbstickWidget*)widget; | |||
2573 | int i, base, flags = ThumbstickWidget_CalcDirs(w); | |||
2574 | ||||
2575 | if (Gui.TouchTex) { | |||
2576 | Gfx_BindTexture(Gui.TouchTex); | |||
2577 | for (i = 0; i < 4; i++) { | |||
2578 | base = (flags & (1 << i)) ? 0 : THUMBSTICKWIDGET_PER; | |||
2579 | Gfx_DrawVb_IndexedTris_Range(4, offset + base + (i * 4)); | |||
2580 | } | |||
2581 | } | |||
2582 | return offset + THUMBSTICKWIDGET_MAX; | |||
2583 | } | |||
2584 | ||||
2585 | static void ThumbstickWidget_Reposition(void* widget) { | |||
2586 | struct ThumbstickWidget* w = (struct ThumbstickWidget*)widget; | |||
2587 | w->width = Display_ScaleX(128 * w->scale); | |||
2588 | w->height = Display_ScaleY(128 * w->scale); | |||
2589 | Widget_CalcPosition(w); | |||
2590 | } | |||
2591 | ||||
2592 | static const struct WidgetVTABLE ThumbstickWidget_VTABLE = { | |||
2593 | NULL((void*)0), Screen_NullFunc, ThumbstickWidget_Reposition, | |||
2594 | Widget_InputDown, Widget_InputUp, Widget_MouseScroll, | |||
2595 | Widget_Pointer, Widget_PointerUp, Widget_PointerMove, | |||
2596 | ThumbstickWidget_BuildMesh, ThumbstickWidget_Render2 | |||
2597 | }; | |||
2598 | void ThumbstickWidget_Init(struct ThumbstickWidget* w) { | |||
2599 | Widget_Reset(w); | |||
2600 | w->VTABLE = &ThumbstickWidget_VTABLE; | |||
2601 | w->scale = 1; | |||
2602 | } | |||
2603 | ||||
2604 | void ThumbstickWidget_GetMovement(struct ThumbstickWidget* w, float* xMoving, float* zMoving) { | |||
2605 | int dirs = ThumbstickWidget_CalcDirs(w); | |||
2606 | if (dirs & DIR_XMIN) *xMoving -= 1; | |||
2607 | if (dirs & DIR_XMAX) *xMoving += 1; | |||
2608 | if (dirs & DIR_YMIN) *zMoving -= 1; | |||
2609 | if (dirs & DIR_YMAX) *zMoving += 1; | |||
2610 | } | |||
2611 | #endif |
1 | #ifndef CC_WIDGETS_H |
2 | #define CC_WIDGETS_H |
3 | #include "Gui.h" |
4 | #include "BlockID.h" |
5 | #include "Constants.h" |
6 | #include "Entity.h" |
7 | /* Contains all 2D widget implementations. |
8 | Copyright 2014-2021 ClassiCube | Licensed under BSD-3 |
9 | */ |
10 | struct FontDesc; |
11 | |
12 | /* A text label. */ |
13 | struct TextWidget { |
14 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
15 | struct Texture tex; |
16 | PackedCol col; |
17 | }; |
18 | #define TEXTWIDGET_MAX4 4 |
19 | |
20 | /* Initialises a text widget. */ |
21 | CC_NOINLINE__attribute__((noinline)) void TextWidget_Init(struct TextWidget* w); |
22 | /* Draws the given text into a texture, then updates the position and size of this widget. */ |
23 | CC_NOINLINE__attribute__((noinline)) void TextWidget_Set(struct TextWidget* w, const cc_string* text, struct FontDesc* font); |
24 | /* Shorthand for TextWidget_Set using String_FromReadonly */ |
25 | CC_NOINLINE__attribute__((noinline)) void TextWidget_SetConst(struct TextWidget* w, const char* text, struct FontDesc* font); |
26 | |
27 | |
28 | typedef void (*Button_Get)(cc_string* raw); |
29 | typedef void (*Button_Set)(const cc_string* raw); |
30 | /* A labelled button that can be clicked on. */ |
31 | struct ButtonWidget { |
32 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
33 | struct Texture tex; |
34 | PackedCol col; |
35 | int minWidth, minHeight; |
36 | const char* optName; |
37 | Button_Get GetValue; |
38 | Button_Set SetValue; |
39 | }; |
40 | #define BUTTONWIDGET_MAX12 12 |
41 | |
42 | /* Initialises a button widget. */ |
43 | CC_NOINLINE__attribute__((noinline)) void ButtonWidget_Make(struct ButtonWidget* w, int minWidth, Widget_LeftClick onClick, |
44 | cc_uint8 horAnchor, cc_uint8 verAnchor, int xOffset, int yOffset); |
45 | /* Initialises a button widget. */ |
46 | CC_NOINLINE__attribute__((noinline)) void ButtonWidget_Init(struct ButtonWidget* w, int minWidth, Widget_LeftClick onClick); |
47 | /* Draws the given text into a texture, then updates the position and size of this widget. */ |
48 | CC_NOINLINE__attribute__((noinline)) void ButtonWidget_Set(struct ButtonWidget* w, const cc_string* text, struct FontDesc* font); |
49 | /* Shorthand for ButtonWidget_Set using String_FromReadonly */ |
50 | CC_NOINLINE__attribute__((noinline)) void ButtonWidget_SetConst(struct ButtonWidget* w, const char* text, struct FontDesc* font); |
51 | |
52 | /* Clickable and draggable scrollbar. */ |
53 | struct ScrollbarWidget { |
54 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
55 | int topRow, rowsTotal, rowsVisible; |
56 | float scrollingAcc; |
57 | int dragOffset; |
58 | int draggingId, padding; |
59 | int borderX, borderY; |
60 | int nubsWidth, offsets[3]; |
61 | }; |
62 | /* Resets state of the given scrollbar widget to default. */ |
63 | CC_NOINLINE__attribute__((noinline)) void ScrollbarWidget_Create(struct ScrollbarWidget* w); |
64 | |
65 | /* A row of blocks with a background. */ |
66 | struct HotbarWidget { |
67 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
68 | struct Texture selTex, backTex; |
69 | float slotWidth, selWidth; |
70 | float slotXOffset, elemSize; |
71 | float scrollAcc, scale; |
72 | cc_bool altHandled; |
73 | struct Texture ellipsisTex; |
74 | }; |
75 | /* Resets state of the given hotbar widget to default. */ |
76 | CC_NOINLINE__attribute__((noinline)) void HotbarWidget_Create(struct HotbarWidget* w); |
77 | CC_NOINLINE__attribute__((noinline)) void HotbarWidget_SetFont(struct HotbarWidget* w, struct FontDesc* font); |
78 | |
79 | /* A table of blocks. */ |
80 | struct TableWidget { |
81 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
82 | int blocksCount, blocksPerRow; |
83 | int rowsTotal, rowsVisible; |
84 | int lastCreatedIndex; |
85 | struct FontDesc* font; |
86 | int selectedIndex, cellSizeX, cellSizeY; |
87 | float selBlockExpand; |
88 | GfxResourceID vb; |
89 | cc_bool pendingClose; |
90 | float scale; |
91 | |
92 | BlockID blocks[BLOCK_COUNT]; |
93 | struct ScrollbarWidget scroll; |
94 | struct Texture descTex; |
95 | int lastX, lastY, paddingX; |
96 | int paddingTopY, paddingMaxY; |
97 | }; |
98 | |
99 | CC_NOINLINE__attribute__((noinline)) void TableWidget_Create(struct TableWidget* w); |
100 | /* Sets the selected block in the table to the given block. */ |
101 | /* Also adjusts scrollbar and moves cursor to be over the given block. */ |
102 | CC_NOINLINE__attribute__((noinline)) void TableWidget_SetBlockTo(struct TableWidget* w, BlockID block); |
103 | CC_NOINLINE__attribute__((noinline)) void TableWidget_RecreateBlocks(struct TableWidget* w); |
104 | CC_NOINLINE__attribute__((noinline)) void TableWidget_OnInventoryChanged(struct TableWidget* w); |
105 | CC_NOINLINE__attribute__((noinline)) void TableWidget_MakeDescTex(struct TableWidget* w, BlockID block); |
106 | CC_NOINLINE__attribute__((noinline)) void TableWidget_Recreate(struct TableWidget* w); |
107 | |
108 | |
109 | #define INPUTWIDGET_MAX_LINES3 3 |
110 | #define INPUTWIDGET_LEN64 STRING_SIZE64 |
111 | struct InputWidget { |
112 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
113 | struct FontDesc* font; |
114 | int (*GetMaxLines)(void); |
115 | void (*RemakeTexture)(void* elem); /* Remakes the raw texture containing all the chat lines. Also updates dimensions. */ |
116 | void (*OnPressedEnter)(void* elem); /* Invoked when the user presses enter. */ |
117 | cc_bool (*AllowedChar)(void* elem, char c); |
118 | void (*OnTextChanged)(void* elem); /* Callback invoked whenever text changes. */ |
119 | |
120 | cc_string text; /* The actual raw text */ |
121 | cc_string lines[INPUTWIDGET_MAX_LINES3]; /* text of each line after word wrapping */ |
122 | int lineWidths[INPUTWIDGET_MAX_LINES3]; /* Width of each line in pixels */ |
123 | int lineHeight; /* Height of a line in pixels */ |
124 | struct Texture inputTex; |
125 | int prefixWidth; |
126 | cc_bool convertPercents; |
127 | |
128 | cc_uint8 padding; |
129 | cc_bool showCaret; |
130 | int caretWidth; |
131 | int caretX, caretY; /* Coordinates of caret in lines */ |
132 | int caretPos; /* Position of caret, -1 for at end of string */ |
133 | int caretOffset; |
134 | PackedCol caretCol; |
135 | struct Texture caretTex; |
136 | double caretAccumulator; |
137 | }; |
138 | |
139 | /* Removes all characters and then deletes the input texture. */ |
140 | CC_NOINLINE__attribute__((noinline)) void InputWidget_Clear(struct InputWidget* w); |
141 | /* Tries appending all characters from the given string, then update the input texture. */ |
142 | CC_NOINLINE__attribute__((noinline)) void InputWidget_AppendText(struct InputWidget* w, const cc_string* text); |
143 | /* Tries appending the given character, then updates the input texture. */ |
144 | CC_NOINLINE__attribute__((noinline)) void InputWidget_Append(struct InputWidget* w, char c); |
145 | /* Redraws text and recalculates associated state. */ |
146 | /* Also calls Window_SetKeyboardText with the text in the input widget. */ |
147 | /* This way native text input state stays synchronised with the input widget. */ |
148 | /* (e.g. may only accept numerical input, so 'c' gets stripped from str) */ |
149 | CC_NOINLINE__attribute__((noinline)) void InputWidget_UpdateText(struct InputWidget* w); |
150 | /* Shorthand for InputWidget_Clear followed by InputWidget_AppendText, */ |
151 | /* then calls Window_SetKeyboardText with the text in the input widget. */ |
152 | /* This way native text input state stays synchronised with the input widget. */ |
153 | /* (e.g. may only accept numerical input, so 'c' gets stripped from str) */ |
154 | CC_NOINLINE__attribute__((noinline)) void InputWidget_SetText(struct InputWidget* w, const cc_string* str); |
155 | |
156 | |
157 | struct MenuInputDesc; |
158 | struct MenuInputVTABLE { |
159 | /* Returns a description of the range of valid values (e.g. "0 - 100") */ |
160 | void (*GetRange)(struct MenuInputDesc* d, cc_string* range); |
161 | /* Whether the given character is acceptable for this input */ |
162 | cc_bool (*IsValidChar)(struct MenuInputDesc* d, char c); |
163 | /* Whether the characters of the given string are acceptable for this input */ |
164 | /* e.g. for an integer, '-' is only valid for the first character */ |
165 | cc_bool (*IsValidString)(struct MenuInputDesc* d, const cc_string* s); |
166 | /* Whether the characters of the given string produce a valid value */ |
167 | cc_bool (*IsValidValue)(struct MenuInputDesc* d, const cc_string* s); |
168 | /* Gets the default value for this input. */ |
169 | void (*GetDefault)(struct MenuInputDesc* d, cc_string* value); |
170 | }; |
171 | |
172 | struct MenuInputDesc { |
173 | const struct MenuInputVTABLE* VTABLE; |
174 | union { |
175 | struct { const char* const* Names; int Count; } e; |
176 | struct { int Min, Max, Default; } i; |
177 | struct { float Min, Max, Default; } f; |
178 | struct { PackedCol Default; } h; |
179 | } meta; |
180 | }; |
181 | |
182 | extern const struct MenuInputVTABLE HexInput_VTABLE; |
183 | extern const struct MenuInputVTABLE IntInput_VTABLE; |
184 | extern const struct MenuInputVTABLE SeedInput_VTABLE; |
185 | extern const struct MenuInputVTABLE FloatInput_VTABLE; |
186 | extern const struct MenuInputVTABLE PathInput_VTABLE; |
187 | extern const struct MenuInputVTABLE StringInput_VTABLE; |
188 | |
189 | #define MenuInput_Hex(v, def)v.VTABLE = &HexInput_VTABLE; v.meta.h.Default = def; v.VTABLE = &HexInput_VTABLE; v.meta.h.Default = def; |
190 | #define MenuInput_Int(v, lo, hi, def)v.VTABLE = &IntInput_VTABLE; v.meta.i.Min = lo; v.meta.i. Max = hi; v.meta.i.Default = def; v.VTABLE = &IntInput_VTABLE; v.meta.i.Min = lo; v.meta.i.Max = hi; v.meta.i.Default = def; |
191 | #define MenuInput_Seed(v)v.VTABLE = &SeedInput_VTABLE; v.meta.i.Min = ((cc_int32)- 2147483647L - (cc_int32)1L); v.meta.i.Max = ((cc_int32)2147483647L ); v.VTABLE = &SeedInput_VTABLE; v.meta.i.Min = Int32_MinValue((cc_int32)-2147483647L - (cc_int32)1L); v.meta.i.Max = Int32_MaxValue((cc_int32)2147483647L); |
192 | #define MenuInput_Float(v, lo, hi, def)v.VTABLE = &FloatInput_VTABLE; v.meta.f.Min = lo; v.meta. f.Max = hi; v.meta.f.Default = def; v.VTABLE = &FloatInput_VTABLE; v.meta.f.Min = lo; v.meta.f.Max = hi; v.meta.f.Default = def; |
193 | #define MenuInput_Path(v)v.VTABLE = &PathInput_VTABLE; v.VTABLE = &PathInput_VTABLE; |
194 | #define MenuInput_Enum(v, names, count)v.VTABLE = ((void*)0); v.meta.e.Names = names; v.meta.e.Count = count; v.VTABLE = NULL((void*)0); v.meta.e.Names = names; v.meta.e.Count = count; |
195 | #define MenuInput_String(v)v.VTABLE = &StringInput_VTABLE; v.VTABLE = &StringInput_VTABLE; |
196 | |
197 | struct TextInputWidget { |
198 | struct InputWidget base; |
199 | int minWidth, minHeight; |
200 | struct MenuInputDesc desc; |
201 | char _textBuffer[INPUTWIDGET_LEN64]; |
202 | }; |
203 | #define MENUINPUTWIDGET_MAX8 8 |
204 | |
205 | CC_NOINLINE__attribute__((noinline)) void TextInputWidget_Create(struct TextInputWidget* w, int width, const cc_string* text, struct MenuInputDesc* d); |
206 | /* Sets the font used, then redraws the input widget. */ |
207 | CC_NOINLINE__attribute__((noinline)) void TextInputWidget_SetFont(struct TextInputWidget* w, struct FontDesc* font); |
208 | |
209 | |
210 | struct ChatInputWidget { |
211 | struct InputWidget base; |
212 | int typingLogPos; |
213 | cc_string origStr; |
214 | char _textBuffer[INPUTWIDGET_MAX_LINES3 * INPUTWIDGET_LEN64]; |
215 | char _origBuffer[INPUTWIDGET_MAX_LINES3 * INPUTWIDGET_LEN64]; |
216 | }; |
217 | |
218 | CC_NOINLINE__attribute__((noinline)) void ChatInputWidget_Create(struct ChatInputWidget* w); |
219 | CC_NOINLINE__attribute__((noinline)) void ChatInputWidget_SetFont(struct ChatInputWidget* w, struct FontDesc* font); |
220 | |
221 | |
222 | /* Retrieves the text for the i'th line in the group */ |
223 | typedef cc_string (*TextGroupWidget_Get)(int i); |
224 | #define TEXTGROUPWIDGET_MAX_LINES30 30 |
225 | #define TEXTGROUPWIDGET_LEN(64 + (64 / 2)) (STRING_SIZE64 + (STRING_SIZE64 / 2)) |
226 | |
227 | /* A group of text labels. */ |
228 | struct TextGroupWidget { |
229 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
230 | int lines, defaultHeight; |
231 | struct FontDesc* font; |
232 | /* Whether a line has zero height when that line has no text in it. */ |
233 | cc_bool collapsible[TEXTGROUPWIDGET_MAX_LINES30]; |
234 | cc_bool underlineUrls; |
235 | struct Texture* textures; |
236 | TextGroupWidget_Get GetLine; |
237 | }; |
238 | |
239 | CC_NOINLINE__attribute__((noinline)) void TextGroupWidget_Create(struct TextGroupWidget* w, int lines, struct Texture* textures, TextGroupWidget_Get getLine); |
240 | CC_NOINLINE__attribute__((noinline)) void TextGroupWidget_SetFont(struct TextGroupWidget* w, struct FontDesc* font); |
241 | /* Deletes first line, then moves all other lines upwards, then redraws last line. */ |
242 | /* NOTE: GetLine must also adjust the lines it returns for this to behave properly. */ |
243 | CC_NOINLINE__attribute__((noinline)) void TextGroupWidget_ShiftUp(struct TextGroupWidget* w); |
244 | /* Deletes last line, then moves all other lines downwards, then redraws first line. */ |
245 | /* NOTE: GetLine must also adjust the lines it returns for this to behave properly. */ |
246 | CC_NOINLINE__attribute__((noinline)) void TextGroupWidget_ShiftDown(struct TextGroupWidget* w); |
247 | /* Returns height of lines, except for the first 0 or more empty lines. */ |
248 | CC_NOINLINE__attribute__((noinline)) int TextGroupWidget_UsedHeight(struct TextGroupWidget* w); |
249 | /* Returns either the URL or the line underneath the given coordinates. */ |
250 | CC_NOINLINE__attribute__((noinline)) int TextGroupWidget_GetSelected(struct TextGroupWidget* w, cc_string* text, int mouseX, int mouseY); |
251 | /* Redraws the given line, updating the texture and Y position of other lines. */ |
252 | CC_NOINLINE__attribute__((noinline)) void TextGroupWidget_Redraw(struct TextGroupWidget* w, int index); |
253 | /* Calls TextGroupWidget_Redraw for all lines */ |
254 | CC_NOINLINE__attribute__((noinline)) void TextGroupWidget_RedrawAll(struct TextGroupWidget* w); |
255 | /* Calls TextGroupWidget_Redraw for all lines which have the given colour code. */ |
256 | /* Typically only called in response to the ChatEvents.ColCodeChanged event. */ |
257 | CC_NOINLINE__attribute__((noinline)) void TextGroupWidget_RedrawAllWithCol(struct TextGroupWidget* w, char col); |
258 | /* Gets the text for the i'th line. */ |
259 | static cc_string TextGroupWidget_UNSAFE_Get(struct TextGroupWidget* w, int i) { return w->GetLine(i); } |
260 | |
261 | |
262 | typedef void (*SpecialInputAppendFunc)(void* userData, char c); |
263 | struct SpecialInputTab { |
264 | int itemsPerRow, charsPerItem, titleWidth; |
265 | cc_string title, contents; |
266 | }; |
267 | |
268 | struct SpecialInputWidget { |
269 | Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick; |
270 | int elementWidth, elementHeight; |
271 | int selectedIndex; |
272 | cc_bool pendingRedraw; |
273 | struct InputWidget* target; |
274 | struct Texture tex; |
275 | struct FontDesc* font; |
276 | int titleHeight; |
277 | struct SpecialInputTab tabs[5]; |
278 | cc_string colString; |
279 | char _colBuffer[DRAWER2D_MAX_COLS256 * 4]; |
280 | }; |
281 | |
282 | CC_NOINLINE__attribute__((noinline)) void SpecialInputWidget_Create(struct SpecialInputWidget* w, struct FontDesc* font, struct InputWidget* target); |
283 | CC_NOINLINE__attribute__((noinline)) void SpecialInputWidget_Redraw(struct SpecialInputWidget* w); |
284 | CC_NOINLINE__attribute__((noinline)) void SpecialInputWidget_UpdateCols(struct SpecialInputWidget* w); |
285 | CC_NOINLINE__attribute__((noinline)) void SpecialInputWidget_SetActive(struct SpecialInputWidget* w, cc_bool active); |
286 | |
287 | #ifdef CC_BUILD_TOUCH |
288 | struct ThumbstickWidget { Widget_Bodyconst struct WidgetVTABLE* VTABLE; int x, y, width, height; cc_bool active; cc_bool disabled; cc_uint8 horAnchor, verAnchor; int xOffset, yOffset; Widget_LeftClick MenuClick;; float scale; }; |
289 | #define THUMBSTICKWIDGET_PER (4 * 4) |
290 | #define THUMBSTICKWIDGET_MAX (THUMBSTICKWIDGET_PER * 2) |
291 | |
292 | void ThumbstickWidget_Init(struct ThumbstickWidget* w); |
293 | void ThumbstickWidget_GetMovement(struct ThumbstickWidget* w, float* xMoving, float* zMoving); |
294 | #endif |
295 | #endif |