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