1using System.Drawing;
2using System.Numerics;
3using Fjord.Input;
4using Fjord.Scenes;
5using static SDL2.SDL;
6using static SDL2.SDL_gfx;
7using static SDL2.SDL_ttf;
8
9namespace Fjord.Graphics;
10
11public enum Flip
12{
13 None,
14 Horizontal,
15 Vertical,
16 Both
17}
18
19public enum Center
20{
21 TopLeft,
22 TopMiddle,
23 TopRight,
24 MiddleLeft,
25 Middle,
26 MiddleRight,
27 BottomLeft,
28 BottomMiddle,
29 BottomRight
30}
31
32public static class Draw
33{
34 internal static string? CurrentSceneID = null;
35
36 internal static List<IDrawInstruction> drawBuffer = new();
37
38 internal static Dictionary<string, IntPtr> textureCache = new();
39
40 public static void Box(Vector4 rect, Vector4 color, int depth=0) {
41 new Rectangle(rect)
42 {
43 color = color,
44 depth = depth,
45 fill = true
46 }.Render();
47 }
48
49 public static void Rectangle(Vector4 rect, Vector4 color, int depth=0) {
50 new Rectangle(rect) {
51 color = color,
52 depth = depth,
53 fill = false
54 }.Render();
55 }
56
57 public static void RoundedBox(Vector4 rect, Vector4 color, float radius, int depth=0) {
58 new Rectangle(rect)
59 {
60 color = color,
61 depth = depth,
62 fill = true,
63 borderRadius = radius
64 }.Render();
65 }
66
67 public static void RoundedRectangle(Vector4 rect, Vector4 color, float radius, int depth=0) {
68 new Rectangle(rect) {
69 color = color,
70 depth = depth,
71 fill = false,
72 borderRadius = radius
73 }.Render();
74 }
75
76 public static void FillCircle(Vector2 position, float radius, Vector4 color, int depth=0) {
77 new Circle(position, radius) {
78 color = color,
79 depth = depth,
80 fill = true
81 }.Render();
82 }
83
84 public static void Circle(Vector2 position, float radius, Vector4 color, int depth=0) {
85 new Circle(position, radius) {
86 color = color,
87 depth = depth,
88 fill = false
89 }.Render();
90 }
91
92 public static void TextureExt(Vector2 position, IntPtr texture, float angle, int depth=0)
93 {
94 new Texture(texture)
95 {
96 position = position,
97 depth = depth,
98 angle = angle
99 }.Render();
100 }
101
102 public static void Texture(Vector2 position, IntPtr texture, int depth=0)
103 {
104 new Texture(texture)
105 {
106 position = position,
107 depth = depth,
108 angle = 0
109 }.Render();
110 }
111
112 public static void Geometry(List<SDL_Vertex> verts, int depth=0)
113 {
114 new Geometry()
115 {
116 verticies = verts,
117 depth = depth
118 }.Render();
119 }
120
121 public static void Text(Vector2 position, string font, string text, int size, Vector4 color, int depth = 0)
122 {
123 new Text(font, text)
124 {
125 position = position,
126 size = size,
127 color = color,
128 depth = depth
129 }.Render();
130 }
131
132 public static void Line(Vector2 point1, Vector2 point2, Vector4 color, int depth = 0)
133 {
134 new Line(point1, point2)
135 {
136 color = color,
137 depth = depth
138 }.Render();
139 }
140
141 internal static void RectangleDirect(Rectangle rect) {
142 SDL_Color col = new SDL_Color() {
143 r = (byte)rect.color.X,
144 g = (byte)rect.color.Y,
145 b = (byte)rect.color.Z,
146 a = (byte)rect.color.W
147 };
148 SDL_Rect SDLRect = new () {
149 x = (int)rect.rect.X,
150 y = (int)rect.rect.Y,
151 w = (int)rect.rect.Z,
152 h = (int)rect.rect.W
153 };
154
155 Helpers.SDL_SetRenderDrawColor(Game.SDLRenderer, col);
156 if(rect.borderRadius != null)
157 {
158 if(rect.fill) {
159 if(rect.borderRadius > Math.Min(rect.rect.Z, rect.rect.W) / 2) {
160 rect.borderRadius = Math.Min(rect.rect.Z, rect.rect.W) / 2;
161 }
162
163 SDL_Rect rect1 = new () {
164 x = (int)rect.rect.X,
165 y = (int)(rect.rect.Y + rect.borderRadius),
166 w = (int)rect.rect.Z,
167 h = (int)(rect.rect.W - rect.borderRadius * 2)
168 };
169 SDL_RenderFillRect(Game.SDLRenderer, ref rect1);
170
171 SDL_Rect rect2 = new () {
172 x = (int)(rect.rect.X + rect.borderRadius),
173 y = (int)rect.rect.Y,
174 w = (int)(rect.rect.Z - rect.borderRadius * 2),
175 h = (int)rect.rect.W
176 };
177 SDL_RenderFillRect(Game.SDLRenderer, ref rect2);
178
179 //Top Left
180 CircleDirect(
181 new Circle(new(rect.rect.X + (float)rect.borderRadius, rect.rect.Y + (float)rect.borderRadius), (float)rect.borderRadius)
182 .Fill(true)
183 .Color(rect.color)
184 );
185
186 //Top Right
187 CircleDirect(
188 new Circle(new(rect.rect.X + rect.rect.Z - (float)rect.borderRadius - 1, rect.rect.Y + (float)rect.borderRadius), (float)rect.borderRadius)
189 .Fill(true)
190 .Color(rect.color)
191 );
192
193 //Bottom Left
194 CircleDirect(
195 new Circle(new(rect.rect.X + (float)rect.borderRadius + 1, rect.rect.Y + rect.rect.W - (float)rect.borderRadius), (float)rect.borderRadius)
196 .Fill(true)
197 .Color(rect.color)
198 );
199
200 // Bottom Right
201 CircleDirect(
202 new Circle(new(rect.rect.X + rect.rect.Z - (float)rect.borderRadius - 1, rect.rect.Y + rect.rect.W - (float)rect.borderRadius - 1), (float)rect.borderRadius)
203 .Fill(true)
204 .Color(rect.color)
205 );
206 } else
207 roundedRectangleRGBA(Game.SDLRenderer, (short)rect.rect.X, (short)rect.rect.Y, (short)(rect.rect.X + rect.rect.Z), (short)(rect.rect.Y + rect.rect.W), (short)rect.borderRadius, col.r, col.g, col.b, col.a);
208 } else {
209 if(rect.fill)
210 SDL_RenderFillRect(Game.SDLRenderer, ref SDLRect);
211 else
212 SDL_RenderDrawRect(Game.SDLRenderer, ref SDLRect);
213 }
214 }
215
216 internal static void CircleDirect(Circle circle) {
217 if(circle.fill) {
218 Helpers.SDL_SetRenderDrawColor(Game.SDLRenderer, Helpers.V4ToColor(circle.color));
219 int x = (int)circle.radius;
220 int y = 0;
221 int err = 0;
222
223 int x0 = (int)circle.position.X;
224 int y0 = (int)circle.position.Y;
225
226 while (x >= y)
227 {
228 SDL_RenderDrawLine(Game.SDLRenderer, x0 + x, y0 + y, x0 - x, y0 + y);
229 SDL_RenderDrawLine(Game.SDLRenderer, x0 - y, y0 + x, x0 - y, y0 - x);
230
231 SDL_RenderDrawLine(Game.SDLRenderer, x0 + x, y0 - y, x0 - x, y0 - y);
232 SDL_RenderDrawLine(Game.SDLRenderer, x0 + y, y0 - x, x0 + y, y0 + x);
233
234 if (err <= 0)
235 {
236 y += 1;
237 err += 2*y + 1;
238 }
239
240 if (err > 0)
241 {
242 x -= 1;
243 err -= 2*x + 1;
244 }
245 }
246 } else
247 if(aacircleRGBA(Game.SDLRenderer, (short)circle.position.X, (short)circle.position.Y, (short)circle.radius, (byte)circle.color.X, (byte)circle.color.Y, (byte)circle.color.Z, (byte)circle.color.W) == -1) {
248 Debug.Log(LogLevel.Error, "Failed to draw aacircleRGBA");
249 }
250 SDL_SetRenderDrawColor(Game.SDLRenderer, 0, 0, 0, 255);
251 }
252
253 internal static void TextureDirect(Texture texture)
254 {
255 SDL_Rect rect = new()
256 {
257 x = (int)texture.position.X,
258 y = (int)texture.position.Y,
259 w = (int)(texture.textureSize.X * texture.sizeMultiplier.X),
260 h = (int)(texture.textureSize.Y * texture.sizeMultiplier.Y)
261 };
262
263 SDL_RendererFlip tmpFlip = texture.flip == Flip.Horizontal ? SDL_RendererFlip.SDL_FLIP_HORIZONTAL : texture.flip == Flip.Vertical ? SDL_RendererFlip.SDL_FLIP_VERTICAL : texture.flip == Flip.Both ? SDL_RendererFlip.SDL_FLIP_HORIZONTAL | SDL_RendererFlip.SDL_FLIP_VERTICAL : SDL_RendererFlip.SDL_FLIP_NONE;
264
265 Vector2 center = new();
266 Vector2 textureSize = texture.textureSize;
267
268 if(!texture.isCustomCenter)
269 {
270 switch(texture.center)
271 {
272 case Graphics.Center.TopLeft: {
273 center = new(0, 0);
274 } break;
275 case Graphics.Center.TopMiddle: {
276 center = new(rect.w / 2, 0);
277 } break;
278 case Graphics.Center.TopRight: {
279 center = new(rect.h, 0);
280 } break;
281
282 case Graphics.Center.MiddleLeft: {
283 center = new(0, rect.h / 2);
284 } break;
285 case Graphics.Center.Middle: {
286 center = new(rect.w / 2, rect.h / 2);
287 } break;
288 case Graphics.Center.MiddleRight: {
289 center = new(rect.w, rect.h / 2);
290 } break;
291
292 case Graphics.Center.BottomLeft: {
293 center = new(0, rect.h);
294 } break;
295 case Graphics.Center.BottomMiddle: {
296 center = new(rect.w / 2, rect.h);
297 } break;
298 case Graphics.Center.BottomRight: {
299 center = new(rect.w, rect.h);
300 } break;
301 }
302 } else
303 {
304 center = new(texture.customCenter.X, texture.customCenter.Y);
305 }
306
307 SDL_Point sdlcenter = new()
308 {
309 x = (int)center.X,
310 y = (int)center.Y
311 };
312 rect.x -= sdlcenter.x;
313 rect.y -= sdlcenter.y;
314
315 SDL_SetTextureAlphaMod(texture.SDLTexture, (byte)texture.alpha);
316
317 if(texture.srcTextureOffset is not null)
318 {
319 SDL_Rect srcRect = new()
320 {
321 x = (int)texture.srcTextureOffset.Value.X,
322 y = (int)texture.srcTextureOffset.Value.Y,
323 w = (int)texture.srcTextureOffset.Value.Z,
324 h = (int)texture.srcTextureOffset.Value.W
325 };
326 SDL_RenderCopyEx(Game.SDLRenderer, texture.SDLTexture, ref srcRect, ref rect, texture.angle, ref sdlcenter, tmpFlip);
327 } else {
328 SDL_RenderCopyEx(Game.SDLRenderer, texture.SDLTexture, IntPtr.Zero, ref rect, texture.angle, ref sdlcenter, tmpFlip);
329 }
330
331 SDL_SetTextureAlphaMod(texture.SDLTexture, 255);
332
333 if(texture.destroy)
334 {
335 SDL_DestroyTexture(texture.SDLTexture);
336 SDL_FreeSurface(texture.SDLSurface);
337 texture = null;
338 }
339 }
340
341 internal static void GeometryDirect(Geometry geometry)
342 {
343 SDL_RenderGeometry(Game.SDLRenderer, IntPtr.Zero, geometry.verticies.ToArray(), geometry.verticies.Count, IntPtr.Zero, 0);
344 }
345
346 internal static void TextDirect(Text text)
347 {
348 if(!Font.Fonts.ContainsKey(text.font + text.size.ToString()))
349 {
350 Font.Fonts.Add(text.font + text.size.ToString(), TTF_OpenFont(text.font, text.size));
351 }
352
353 SDL_Color col = Helpers.V4ToColor(text.color);
354 string CacheKey = text.font + text.value + text.size + col.r + col.g + col.b + col.a;
355 if (!Font.FontCache.ContainsKey(CacheKey))
356 {
357 IntPtr surface = TTF_RenderText_Blended(Font.Fonts[text.font + text.size.ToString()], text.value, col);
358 IntPtr texture = SDL_CreateTextureFromSurface(Game.SDLRenderer, surface);
359 Font.FontCache.Add(CacheKey, texture);
360 SDL_FreeSurface(surface);
361 }
362
363 SDL_QueryTexture(Font.FontCache[CacheKey], out uint format, out int access, out int textureW, out int textureH);
364
365 SDL_Rect rect = new()
366 {
367 x = (int)text.position.X,
368 y = (int)text.position.Y,
369 w = (int)(textureW),
370 h = (int)(textureH)
371
372 };
373
374 SDL_RenderCopy(Game.SDLRenderer, Font.FontCache[CacheKey], IntPtr.Zero, ref rect);
375 }
376
377 internal static void LineDirect(Line line)
378 {
379 SDL_SetRenderDrawColor(Game.SDLRenderer, (byte)line.color.X, (byte)line.color.Y, (byte)line.color.Z, (byte)line.color.W);
380 SDL_RenderDrawLine(Game.SDLRenderer, (int)line.point1.X, (int)line.point1.Y, (int)line.point2.X, (int)line.point2.Y);
381 }
382
383 internal static void DrawDrawBuffer(List<IDrawInstruction> drawBufferLocal, string? sceneId) {
384
385 drawBufferLocal.Sort((a, b) => a.depth.CompareTo(b.depth));
386 foreach(IDrawInstruction drawInsObj in drawBufferLocal) {
387 if(drawInsObj is Rectangle rect) {
388 if(sceneId != null)
389 {
390 rect.rect.X -= SceneHandler.Get(sceneId).Camera.Offset.X;
391 rect.rect.Y -= SceneHandler.Get(sceneId).Camera.Offset.Y;
392 }
393 Draw.RectangleDirect(rect);
394 } else if(drawInsObj is Circle circle) {
395 if(circle.hoverAnimation != null)
396 {
397 if(circle.hoverAnimation.xDriver != null)
398 circle.position.X *= (circle.hoverAnimation.xDriver(circle.hoverAnimation.progress) + 1);
399 if(circle.hoverAnimation.yDriver != null)
400 circle.position.Y *= (circle.hoverAnimation.yDriver(circle.hoverAnimation.progress) + 1);
401 if(circle.hoverAnimation.radiusDriver != null)
402 circle.radius *= (circle.hoverAnimation.radiusDriver(circle.hoverAnimation.progress) + 1);
403 if(circle.hoverAnimation.colorDriver != null)
404 circle.color = Helpers.Lerp(circle.color, circle.hoverAnimation.colorGoal, circle.hoverAnimation.progress);
405 // drawInsClone.color *= (drawIns.hoverAnimation.colorDriver(drawIns.hoverAnimation.progress) + 1);
406 if(sceneId == null ? Helpers.PointDistance(GlobalMouse.Position, circle.position) < circle.radius : Helpers.PointDistance(SceneHandler.Scenes[sceneId].Mouse.Position, circle.position) < circle.radius) {
407 if(circle.hoverAnimation.progress < 1)
408 circle.hoverAnimation.progress += circle.hoverAnimation.speed * (float)Game.DeltaTime;
409 } else if(circle.hoverAnimation.progress > 0) {
410 circle.hoverAnimation.progress -= circle.hoverAnimation.speed * (float)Game.DeltaTime;
411 if(circle.hoverAnimation.progress < 0)
412 circle.hoverAnimation.progress = 0;
413 }
414
415 }
416 if(sceneId != null)
417 circle.position -= SceneHandler.Get(sceneId).Camera.Offset;
418 Draw.CircleDirect(circle);
419 } else if(drawInsObj is Texture texture)
420 {
421 if(sceneId != null)
422 texture.position -= SceneHandler.Get(sceneId).Camera.Offset;
423 Draw.TextureDirect(texture);
424 } else if(drawInsObj is Geometry geo)
425 {
426 if(sceneId != null)
427 {
428 for(var i = 0; i < geo.verticies.Count; i++)
429 {
430 geo.verticies[i] = new SDL_Vertex() {
431 position = new SDL_FPoint() {
432 x = geo.verticies[i].position.x - SceneHandler.Get(sceneId).Camera.Offset.X,
433 y = geo.verticies[i].position.y - SceneHandler.Get(sceneId).Camera.Offset.Y,
434 },
435 color = geo.verticies[i].color,
436 tex_coord = geo.verticies[i].tex_coord
437 };
438 }
439 }
440
441 Draw.GeometryDirect(geo);
442 } else if(drawInsObj is Text text)
443 {
444 if(sceneId != null)
445 text.position -= SceneHandler.Get(sceneId).Camera.Offset;
446 Draw.TextDirect(text);
447 } else if(drawInsObj is Line line)
448 {
449 if(sceneId != null)
450 {
451 line.point1 -= SceneHandler.Get(sceneId).Camera.Offset;
452 line.point2 -= SceneHandler.Get(sceneId).Camera.Offset;
453 }
454
455 Draw.LineDirect(line);
456 }
457 }
458 }
459}