From 91db2b64715f5c14651c4c1e976819078bba4da0 Mon Sep 17 00:00:00 2001 From: Jacob Guin Date: Wed, 21 Dec 2022 21:48:40 -0500 Subject: [PATCH] Finally! After a long time, I have a basic library for handling OpenGL with no errors to my knowledge. --- GraphicsManager/Enums/ObjectAnchor.cs | 10 + GraphicsManager/GraphicsManager.csproj | 20 ++ GraphicsManager/Interfaces/IParent.cs | 18 ++ GraphicsManager/Interfaces/IRenderObject.cs | 23 ++ GraphicsManager/Interfaces/ITextureObject.cs | 13 ++ GraphicsManager/Objects/Core/Font.cs | 59 +++++ GraphicsManager/Objects/Core/Shader.cs | 129 +++++++++++ GraphicsManager/Objects/Core/Texture.cs | 106 +++++++++ GraphicsManager/Objects/Label.cs | 214 ++++++++++++++++++ GraphicsManager/Objects/Rectangle.cs | 212 +++++++++++++++++ GraphicsManager/Objects/Textbox.cs | 121 ++++++++++ GraphicsManager/Objects/UserControl.cs | 183 +++++++++++++++ .../Resources/Fonts/TektonPro-Regular.otf | Bin 0 -> 79500 bytes GraphicsManager/Resources/Shaders/Label.frag | 16 ++ GraphicsManager/Resources/Shaders/Label.vert | 15 ++ .../Resources/Shaders/Rectangle.frag | 9 + .../Resources/Shaders/Rectangle.vert | 10 + .../Resources/Shaders/RectangleTexture.frag | 8 + .../Resources/Shaders/RectangleTexture.vert | 9 + .../Resources/Textures/Textbox.png | Bin 0 -> 267 bytes GraphicsManager/Structs/Character.cs | 12 + GraphicsManager/Tools.cs | 31 +++ GraphicsManager/Window.cs | 165 ++++++++++++++ 23 files changed, 1383 insertions(+) create mode 100644 GraphicsManager/Enums/ObjectAnchor.cs create mode 100644 GraphicsManager/GraphicsManager.csproj create mode 100644 GraphicsManager/Interfaces/IParent.cs create mode 100644 GraphicsManager/Interfaces/IRenderObject.cs create mode 100644 GraphicsManager/Interfaces/ITextureObject.cs create mode 100644 GraphicsManager/Objects/Core/Font.cs create mode 100644 GraphicsManager/Objects/Core/Shader.cs create mode 100644 GraphicsManager/Objects/Core/Texture.cs create mode 100644 GraphicsManager/Objects/Label.cs create mode 100644 GraphicsManager/Objects/Rectangle.cs create mode 100644 GraphicsManager/Objects/Textbox.cs create mode 100644 GraphicsManager/Objects/UserControl.cs create mode 100644 GraphicsManager/Resources/Fonts/TektonPro-Regular.otf create mode 100644 GraphicsManager/Resources/Shaders/Label.frag create mode 100644 GraphicsManager/Resources/Shaders/Label.vert create mode 100644 GraphicsManager/Resources/Shaders/Rectangle.frag create mode 100644 GraphicsManager/Resources/Shaders/Rectangle.vert create mode 100644 GraphicsManager/Resources/Shaders/RectangleTexture.frag create mode 100644 GraphicsManager/Resources/Shaders/RectangleTexture.vert create mode 100644 GraphicsManager/Resources/Textures/Textbox.png create mode 100644 GraphicsManager/Structs/Character.cs create mode 100644 GraphicsManager/Tools.cs create mode 100644 GraphicsManager/Window.cs diff --git a/GraphicsManager/Enums/ObjectAnchor.cs b/GraphicsManager/Enums/ObjectAnchor.cs new file mode 100644 index 0000000..f693894 --- /dev/null +++ b/GraphicsManager/Enums/ObjectAnchor.cs @@ -0,0 +1,10 @@ +namespace GraphicsManager.Enums; + +public enum ObjectAnchor +{ + Left = 0b_0001, + Top = 0b_0010, + Right = 0b_0100, + Bottom = 0b_1000, + All = Left | Top | Right | Bottom, +} diff --git a/GraphicsManager/GraphicsManager.csproj b/GraphicsManager/GraphicsManager.csproj new file mode 100644 index 0000000..fd26c9e --- /dev/null +++ b/GraphicsManager/GraphicsManager.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/GraphicsManager/Interfaces/IParent.cs b/GraphicsManager/Interfaces/IParent.cs new file mode 100644 index 0000000..6ac1561 --- /dev/null +++ b/GraphicsManager/Interfaces/IParent.cs @@ -0,0 +1,18 @@ +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; + +namespace GraphicsManager.Interfaces; + +public interface IParent +{ + public Vector2i Size { get; } + public void Resize(ResizeEventArgs e); + public float[] RctToFloat(int x, int y, int Width, int Height, bool hastexture = false, float z = 0.0f); + public float IntToFloat(int p, bool Invert = false); + public float FloatToInt(float p, bool Invert = false); + public event Action MouseDown; + public event Action KeyDown; + public Vector2 MousePosition { get; } + + public Vector2i Position { get; } +} diff --git a/GraphicsManager/Interfaces/IRenderObject.cs b/GraphicsManager/Interfaces/IRenderObject.cs new file mode 100644 index 0000000..310e380 --- /dev/null +++ b/GraphicsManager/Interfaces/IRenderObject.cs @@ -0,0 +1,23 @@ +using GraphicsManager.Enums; +using OpenTK.Mathematics; + +namespace GraphicsManager.Interfaces; + +public interface IRenderObject +{ + public ObjectAnchor Anchor { get; set; } + public bool Loaded { get; } + public void LoadToParent(IParent Parent, Window Window); + public void Draw(); + public void Clean(); + public Vector2i Size { get; set; } + public Vector2i Location { get; set; } + public Vector2 SizeAsFloat { get; } + public Vector2 LocationAsFloat { get; } + public Vector2i Distance { get; } + public IParent? Parent { get; } + public Window? Window { get; } + public bool Visible { get; set; } + + public event Func? Clicked; +} diff --git a/GraphicsManager/Interfaces/ITextureObject.cs b/GraphicsManager/Interfaces/ITextureObject.cs new file mode 100644 index 0000000..4fb3848 --- /dev/null +++ b/GraphicsManager/Interfaces/ITextureObject.cs @@ -0,0 +1,13 @@ +using GraphicsManager.Objects.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GraphicsManager.Interfaces; + +public interface ITextureObject : IRenderObject +{ + public Texture? Texture { get; } +} diff --git a/GraphicsManager/Objects/Core/Font.cs b/GraphicsManager/Objects/Core/Font.cs new file mode 100644 index 0000000..35918a7 --- /dev/null +++ b/GraphicsManager/Objects/Core/Font.cs @@ -0,0 +1,59 @@ +using System.Reflection; + +namespace GraphicsManager.Objects.Core; + +public class Font +{ + public void SetEmbeddedFont(string Font, Assembly? Assembly = null) + { + _ = Font ?? throw new ArgumentNullException(nameof(Font)); + this.Assembly = Assembly; + this.Embeded = true; + this.Name = Font; + } + + public static Font MakeEmbeddedFont(string Font, Assembly? Assembly = null) + { + _ = Font ?? throw new ArgumentNullException(nameof(Font)); + return new Font() + { + Assembly = Assembly, + Embeded = true, + Name = Font, + }; + } + + public static Font MakeFontFromFile(string Font) + { + _ = Font ?? throw new ArgumentNullException(nameof(Font)); + return new Font() + { + Assembly = null, + Embeded = false, + Name = Font, + }; + } + + public void SetFontFile(string Font) + { + _ = Font ?? throw new ArgumentNullException(nameof(Font)); + this.Assembly = null; + this.Embeded = false; + this.Name = Font; + } + + public byte[] GetData() + { + if (Embeded) + { + string Base = "GraphicsManager.Resources.Fonts."; + if (Assembly is not null) Base = string.Empty; + return (Assembly is null ? Tools.GetResourceBytes(Base + Name) : Tools.GetResourceBytes(Assembly!, $"{Base}{Name}")); + } + return File.ReadAllBytes(Name); + } + + public string Name { get; private set; } = "shal be default"; + public bool Embeded { get; private set; } + public Assembly? Assembly { get; private set; } +} diff --git a/GraphicsManager/Objects/Core/Shader.cs b/GraphicsManager/Objects/Core/Shader.cs new file mode 100644 index 0000000..8d79551 --- /dev/null +++ b/GraphicsManager/Objects/Core/Shader.cs @@ -0,0 +1,129 @@ +using OpenTK.Graphics.OpenGL4; +using System.Reflection; + +namespace GraphicsManager.Objects.Core; + +public class Shader : IDisposable +{ + public int Handle { get; } + private readonly int VertexShader; + private readonly int FragmentShader; + private bool disposedValue = false; + + public Shader(string VertexShaderSource, string FragmentShaderSource, bool VertextBuiltIn = false, bool FragmentShaderBuiltIn = false, Assembly? Assembly = null) + { + VertexShader = GL.CreateShader(ShaderType.VertexShader); + string Base = "GraphicsManager.Resources.Shaders."; + if (Assembly is not null) Base = string.Empty; + string vss = (VertextBuiltIn ? Tools.GetResourceString((Assembly == null ? typeof(Tools).Assembly : Assembly), $"{Base}{VertexShaderSource}") : File.ReadAllText(VertexShaderSource))!; + string fss = (FragmentShaderBuiltIn ? Tools.GetResourceString((Assembly ?? typeof(Tools).Assembly), $"{Base}{FragmentShaderSource}") : File.ReadAllText(FragmentShaderSource))!; + GL.ShaderSource(VertexShader, vss); + + FragmentShader = GL.CreateShader(ShaderType.FragmentShader); + GL.ShaderSource(FragmentShader, fss); + + GL.CompileShader(VertexShader); + + string infoLogVert = GL.GetShaderInfoLog(VertexShader); + if (infoLogVert != string.Empty) + Console.WriteLine(infoLogVert); + + GL.CompileShader(FragmentShader); + + string infoLogFrag = GL.GetShaderInfoLog(FragmentShader); + + if (infoLogFrag != string.Empty) + Console.WriteLine(infoLogFrag); + + Handle = GL.CreateProgram(); + + GL.AttachShader(Handle, VertexShader); + GL.AttachShader(Handle, FragmentShader); + + GL.LinkProgram(Handle); + + GL.DetachShader(Handle, VertexShader); + GL.DetachShader(Handle, FragmentShader); + GL.DeleteShader(FragmentShader); + GL.DeleteShader(VertexShader); + } + + public Shader(string ShaderSource, bool Embeded = false, Assembly? Assembly = null) + { + VertexShader = GL.CreateShader(ShaderType.VertexShader); + string Base = "GraphicsManager.Resources.Shaders."; + if (Assembly is not null) Base = string.Empty; + GL.ShaderSource(VertexShader, (Embeded ? Tools.GetResourceString((Assembly ?? typeof(Tools).Assembly), $"{Base}{ShaderSource}.vert") : File.ReadAllText($"{ShaderSource}.vert"))); + + FragmentShader = GL.CreateShader(ShaderType.FragmentShader); + GL.ShaderSource(FragmentShader, (Embeded ? Tools.GetResourceString((Assembly ?? typeof(Tools).Assembly), $"{Base}{ShaderSource}.frag") : File.ReadAllText($"{ShaderSource}.frag"))); + + GL.CompileShader(VertexShader); + + string infoLogVert = GL.GetShaderInfoLog(VertexShader); + if (infoLogVert != string.Empty) + Console.WriteLine(infoLogVert); + + GL.CompileShader(FragmentShader); + + string infoLogFrag = GL.GetShaderInfoLog(FragmentShader); + + if (infoLogFrag != string.Empty) + Console.WriteLine(infoLogFrag); + + Handle = GL.CreateProgram(); + + GL.AttachShader(Handle, VertexShader); + GL.AttachShader(Handle, FragmentShader); + + GL.LinkProgram(Handle); + + GL.DetachShader(Handle, VertexShader); + GL.DetachShader(Handle, FragmentShader); + GL.DeleteShader(FragmentShader); + GL.DeleteShader(VertexShader); + } + + public void Use() + { + GL.UseProgram(Handle); + } + + public int GetAttribLocation(string attribName) + { + return GL.GetAttribLocation(Handle, attribName); + } + + public void SetInt(string name, int value) + { + int location = GL.GetUniformLocation(Handle, name); + + GL.Uniform1(location, value); + } + + public Shader Clone() + { + return (Shader)MemberwiseClone(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + GL.DeleteProgram(Handle); + + disposedValue = true; + } + } + + ~Shader() + { + GL.DeleteProgram(Handle); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } +} diff --git a/GraphicsManager/Objects/Core/Texture.cs b/GraphicsManager/Objects/Core/Texture.cs new file mode 100644 index 0000000..d0e95a4 --- /dev/null +++ b/GraphicsManager/Objects/Core/Texture.cs @@ -0,0 +1,106 @@ +using GraphicsManager.Structs; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; +using SharpFont; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Image = SixLabors.ImageSharp.Image; + +namespace GraphicsManager.Objects.Core; + +public class Texture +{ + public static readonly Shader TextureShader = new("RectangleTexture", true); + + public int handel; + public Texture(byte[] File) + { + Image image = Image.Load(File); + image.Mutate(x => x.Flip(FlipMode.Vertical)); + + var pixels = new List(4 * image.Width * image.Height); + + for (int y = 0; y < image.Height; y++) + { + var row = image.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + pixels.Add(row[x].R); + pixels.Add(row[x].G); + pixels.Add(row[x].B); + pixels.Add(row[x].A); + } + } + + handel = GL.GenTexture(); + GL.BindTexture(TextureTarget.Texture2D, handel); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, pixels.ToArray()); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + } + + internal Texture(Label l, char charter, uint PixelHeight, Face face) + { + if (!Label._characters.ContainsKey(l)) Label._characters.Add(l, new Dictionary()); + face.SetPixelSizes(0, PixelHeight); + + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1); + + GL.ActiveTexture(TextureUnit.Texture0); + face.SelectCharmap(Encoding.Unicode); + ushort temp = ((ushort)charter); + + try + { + face.LoadChar(temp, LoadFlags.Render, LoadTarget.Normal); + GlyphSlot glyph = face.Glyph; + FTBitmap bitmap = glyph.Bitmap; + + handel = GL.GenTexture(); + GL.BindTexture(TextureTarget.Texture2D, handel); + GL.TexImage2D(TextureTarget.Texture2D, 0, + PixelInternalFormat.R8, bitmap.Width, bitmap.Rows, 0, + PixelFormat.Red, PixelType.UnsignedByte, bitmap.Buffer); + + + Character cha = new() + { + Size = new Vector2(bitmap.Width, bitmap.Rows), + Bearing = new Vector2(glyph.BitmapLeft, glyph.BitmapTop), + Advance = (int)glyph.Advance.X.Value, + Texture = this, + }; + + Label._characters[l].Add(temp, cha); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + public void LoadText() + { + GL.TextureParameter(handel, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TextureParameter(handel, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + GL.TextureParameter(handel, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge); + GL.TextureParameter(handel, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge); + } + + public void Load(int loc) + { + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); + GL.EnableVertexAttribArray(loc); + GL.VertexAttribPointer(loc, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); + } + + public void Use(TextureUnit unit = TextureUnit.Texture0) + { + GL.ActiveTexture(unit); + GL.BindTexture(TextureTarget.Texture2D, handel); + } +} diff --git a/GraphicsManager/Objects/Label.cs b/GraphicsManager/Objects/Label.cs new file mode 100644 index 0000000..12e6dc8 --- /dev/null +++ b/GraphicsManager/Objects/Label.cs @@ -0,0 +1,214 @@ +using GraphicsManager.Enums; +using GraphicsManager.Interfaces; +using GraphicsManager.Objects.Core; +using GraphicsManager.Structs; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; +using SharpFont; +using System.Reflection; +using Encoding = SharpFont.Encoding; + +namespace GraphicsManager.Objects; + +public class Label : IRenderObject +{ + public static readonly Shader DefaultTextShader = new("Label", true); + public IParent? Parent { get; private set; } + public ObjectAnchor Anchor { get; set; } = ObjectAnchor.Left | ObjectAnchor.Top; + private Vector2 laf = new(), saf = new(); + + public Vector2 LocationAsFloat { get { return laf; } } + public Vector2 SizeAsFloat { get { return saf; } } + public bool Visible { get; set; } = true; + + public static readonly Dictionary> _characters = new(); + private string text = string.Empty; + public int VAO { get; set; } + public int VBO { get; set; } + public Vector2 DIR { get; set; } = new Vector2(1f, 0f); + public string Text + { + get => text; + set + { + if (Loaded) + { + Library lib = new(); + Face face = new(lib, Font.GetData(), 0); + + face.SetPixelSizes(0, PixelHeight); + + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1); + + face.SelectCharmap(Encoding.Unicode); + if (!_characters.ContainsKey(this)) _characters.Add(this, new Dictionary()); + foreach (char character in value) + { + if (_characters[this].ContainsKey(character) == false) + { + var f = new Texture(this, character, PixelHeight, face); + f.LoadText(); + } + } + } + text = value; + } + } + public uint PixelHeight { get; set; } = 20; + public float Scale { get; set; } = 1.2f; + public Shader Shader { get; } = DefaultTextShader; + public Font Font { get; set; } = Font.MakeEmbeddedFont("TektonPro-Regular.otf"); + + public Vector4 Color { get; set; } = new Vector4(1, 1, 1, 1); + public Vector2i Distance { get; private set; } + private Vector2i loc_ = new(); + private Vector2i locc_ = new(); + public Vector2i Location + { + get + { + return loc_; + } + set + { + + loc_ = value; + if (Window is null || Parent is null) return; + locc_ = new(value.X + Parent.Position.X, value.Y + Parent.Position.Y); + } + } + public Vector2i Size { get; set; } + + public void Clean() + { + + } + + public void Draw() + { + if (Visible & loadd) + { + Shader.Use(); + GL.Enable(EnableCap.Blend); + GL.Uniform4(2, Color); + Matrix4 projectionM = Matrix4.CreateOrthographicOffCenter(0, Window!.Size.X, Window!.Size.Y, 0, -1.0f, 1.0f); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + GL.BlendFunc(0, BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + GL.UniformMatrix4(1, false, ref projectionM); + + GL.BindVertexArray(VAO); + + float angle_rad = (float)Math.Atan2(DIR.Y, DIR.X); + Matrix4 rotateM = Matrix4.CreateRotationZ(angle_rad); + Matrix4 transOriginM = Matrix4.CreateTranslation(new Vector3(locc_.X, locc_.Y, 0f)); + + float char_x = 0.0f; + + Library lib = new(); + + Face face = new(lib, Font.GetData(), 0); + + face.SetPixelSizes(0, PixelHeight); + + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1); + + GL.ActiveTexture(TextureUnit.Texture0); + face.SelectCharmap(Encoding.Unicode); + + + float hhh = 0f; + foreach (char c in Text) + { + if (!_characters[this].ContainsKey(c)) break; + Character ch = _characters[this][c]; + + if (c == '\n') + { + hhh += PixelHeight; + char_x = 0f; + } + else + { + float w = ch.Size.X * Scale; + float h = ch.Size.Y * Scale; + float xrel = char_x + ch.Bearing.X * Scale; + float yrel = (ch.Size.Y - ch.Bearing.Y) * Scale; + yrel += hhh; + char_x += (ch.Advance >> 6) * Scale; + + Matrix4 scaleM = Matrix4.CreateScale(new Vector3(w, h, 1.0f)); + Matrix4 transRelM = Matrix4.CreateTranslation(new Vector3(xrel, yrel, 0.0f)); + + Matrix4 modelM = scaleM * transRelM * rotateM * transOriginM; + GL.UniformMatrix4(0, false, ref modelM); + + ch.Texture.Use(); + + GL.DrawArrays(PrimitiveType.Triangles, 0, 6); + } + } + + GL.Disable(EnableCap.Blend); + } + } + public Window? Window { get; private set; } + + public void LoadToParent(IParent window, Window win) + { + Parent = window; + Window = win; + //X = window.FloatToInt(X, window.Size.X); + //Y = window.FloatToInt(Y, window.Size.Y, true); + Library lib = new(); + + Face face = new(lib, Font.GetData(), 0); + face.SetPixelSizes(0, PixelHeight); + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1); + face.SelectCharmap(Encoding.Unicode); + + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4); + + float[] vquad = + { + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 1.0f, 1.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f + }; + + VBO = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ArrayBuffer, VBO); + GL.BufferData(BufferTarget.ArrayBuffer, 4 * 6 * 4, vquad, BufferUsageHint.StaticDraw); + + VAO = GL.GenVertexArray(); + if (!_characters.ContainsKey(this)) _characters.Add(this, new Dictionary()); + foreach (char character in Text) + { + if (_characters[this].ContainsKey(character) == false) + { + var f = new Texture(this, character, PixelHeight, face); + f.LoadText(); + } + } + GL.BindVertexArray(VAO); + GL.EnableVertexAttribArray(0); + GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 4 * 4, 0); + GL.EnableVertexAttribArray(1); + GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 4 * 4, 2 * 4); + + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + GL.BindVertexArray(0); + Text = text; + loadd = true; + Location = Location; + Distance = new(window.Size.X - Size.X - Location.X, window.Size.Y - Size.Y - Location.Y); + } + + private bool loadd = false; + + public event Func? Clicked; + + public bool Loaded => loadd; +} diff --git a/GraphicsManager/Objects/Rectangle.cs b/GraphicsManager/Objects/Rectangle.cs new file mode 100644 index 0000000..feafdaa --- /dev/null +++ b/GraphicsManager/Objects/Rectangle.cs @@ -0,0 +1,212 @@ +using GraphicsManager.Enums; +using GraphicsManager.Interfaces; +using GraphicsManager.Objects.Core; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; + +namespace GraphicsManager.Objects; + +public class Rectangle : ITextureObject +{ + public static readonly Shader DefaultShader = new("Rectangle", true); + + public ObjectAnchor Anchor { get; set; } = ObjectAnchor.Left | ObjectAnchor.Top; + + public Texture? Texture { get; private set; } + + public Rectangle(Texture? texture = null) + { + Texture = texture; + if (Points_ is null) + { + bool tex = (Texture is null); + Points_ = new float[(tex ? 12 : 20)]; + if (!tex) + { + Points[3] = 1.0f; + Points[4] = 1.0f; + Points[8] = 1.0f; + Points[19] = 1.0f; + } + } + } + + public Vector4 Color { get; set; } + + public bool Visible { get; set; } = true; + + public void Draw() + { + if (Visible) + { + if (Texture is not null) Texture.Use(); + Shader.Use(); + GL.Uniform4(0, Color); + if (Texture is not null) + { + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + GL.BlendFunc(0, BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + } + GL.BindVertexArray(ArrayObject); + GL.DrawElements(PrimitiveType.Triangles, Indexs.Length, DrawElementsType.UnsignedInt, 0); + if (Texture is not null) GL.Disable(EnableCap.Blend); + } + } + + public void Clean() + { + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + GL.DeleteBuffer(BufferObject); + } + + public void LoadToParent(IParent Parent, Window Window) + { + this.Parent = Parent; + this.Window = Window; + int pos = Points.Length - 3; + if (Texture is not null) pos -= 2; + pos = 4; + if (Texture is not null) pos += 2; + + BufferObject = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject); + ArrayObject = GL.GenVertexArray(); + GL.BindVertexArray(ArrayObject); + int add = 3; + if (Texture is not null) add = 5; + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, add * sizeof(float), 0); + if (Texture is not null) + { + Shader = Texture.TextureShader; + Texture.Load(Shader.GetAttribLocation("aTexCoord")); + } + GL.EnableVertexAttribArray(0); + GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject); + GL.BufferData(BufferTarget.ArrayBuffer, Points.Length * sizeof(float), Points, Hint); + GL.BindVertexArray(ArrayObject); + ElementBufferObject = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, ElementBufferObject); + GL.BufferData(BufferTarget.ElementArrayBuffer, Indexs.Length * sizeof(uint), Indexs, Hint); + load = true; + loadd = true; + + Window.MouseDown += Window_MouseDown; + Location = Location; + Distance = new(Parent.Size.X - Size.X - Location.X, Parent.Size.Y - Size.Y - Location.Y); + } + + public IParent? Parent { get; private set; } + public Window? Window { get; private set; } + + private void Window_MouseDown(OpenTK.Windowing.Common.MouseButtonEventArgs e) + { + if (Clicked is not null && e.Button == OpenTK.Windowing.GraphicsLibraryFramework.MouseButton.Button1 && Location.X <= Parent?.MousePosition.X && Size.X + Location.X >= Parent?.MousePosition.X && Location.Y + Size.Y >= Parent?.MousePosition.Y && Location.Y <= Parent?.MousePosition.Y) Clicked.Invoke(this); + } + + ~Rectangle() + { + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + GL.DeleteBuffer(BufferObject); + } + + public Shader Shader { get; set; } = DefaultShader; + public int ElementBufferObject { get; private set; } + public int BufferObject { get; private set; } + public int ArrayObject { get; private set; } + private float[] Points_; + private Vector2i size_ = new(), loc_ = new(); + bool load = false; + + public float[] Points + { + get + { + return Points_; + } + set + { + Points_ = value; + try + { + if (load) + { + int add = 3; + if (Texture is not null) add = 5; + GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject); + GL.BindVertexArray(ArrayObject); + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, add * sizeof(float), 0); + GL.EnableVertexAttribArray(0); + GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject); + GL.BufferData(BufferTarget.ArrayBuffer, Points_.Length * sizeof(float), Points_, Hint); + GL.BindVertexArray(ArrayObject); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, ElementBufferObject); + GL.BufferData(BufferTarget.ElementArrayBuffer, Indexs.Length * sizeof(uint), Indexs, Hint); + } + } + catch (AccessViolationException v) + { + Console.WriteLine(v.Message); + } + } + } + + public uint[] Indexs { get; set; } = new uint[6] { 0, 1, 3, 1, 2, 3 }; + public BufferUsageHint Hint { get; set; } = BufferUsageHint.StaticDraw; + + private bool loadd = false; + + public event Func? Clicked; + + public bool Loaded => loadd; + public Vector2i Distance { get; private set; } + + public Vector2i Size + { + get + { + return size_; + } + set + { + size_ = value; + if (Window is null || Parent is null) return; + float[] temp = Points; + saf = new Vector2(Window.IntToFloat(value.X + loc_.X + Parent.Position.X, false), Window.IntToFloat(value.Y + loc_.Y + Parent.Position.Y, true)); + temp[0] = saf.X; + temp[(Texture is null ? 3 : 5)] = saf.X; + temp[(Texture is null ? 4 : 6)] = saf.Y; + temp[(Texture is null ? 7 : 11)] = saf.Y; + Points = temp; + } + } + + public Vector2i Location + { + get + { + return loc_; + } + set + { + loc_ = value; + if (Window is null || Parent is null) return; + float[] temp = Points; + laf = new Vector2(Window.IntToFloat(value.X + Parent.Position.X, false), Window.IntToFloat(value.Y + Parent.Position.Y, true)); + temp[(Texture is null ? 6 : 10)] = laf.X; + temp[(Texture is null ? 9 : 15)] = laf.X; + temp[1] = laf.Y; + temp[(Texture is null ? 10 : 16)] = laf.Y; + saf = new Vector2(Window.IntToFloat(Size.X + value.X + Parent.Position.X, false), Window.IntToFloat(Size.Y + value.Y + Parent.Position.Y, true)); + temp[0] = saf.X; + temp[(Texture is null ? 3 : 5)] = saf.X; + temp[(Texture is null ? 4 : 6)] = saf.Y; + temp[(Texture is null ? 7 : 11)] = saf.Y; + Points = temp; + } + } + private Vector2 laf = new(), saf = new(); + + public Vector2 LocationAsFloat { get { return laf; } } + public Vector2 SizeAsFloat { get { return saf; } } +} diff --git a/GraphicsManager/Objects/Textbox.cs b/GraphicsManager/Objects/Textbox.cs new file mode 100644 index 0000000..fa56553 --- /dev/null +++ b/GraphicsManager/Objects/Textbox.cs @@ -0,0 +1,121 @@ +using GraphicsManager.Enums; +using GraphicsManager.Interfaces; +using GraphicsManager.Objects.Core; +using OpenTK.Mathematics; +using OpenTK.Windowing.GraphicsLibraryFramework; + +namespace GraphicsManager.Objects; + +public class Textbox : ITextureObject +{ + public event Func? Clicked; + public Rectangle Bounds; + public Label Label; + public IParent Parent { get; private set; } + public Textbox(Texture texture = null!) + { + Bounds = new Rectangle(texture ?? new Texture(Tools.GetResourceBytes("GraphicsManager.Resources.Textures.Textbox.png"))); + Label = new Label() + { + Text = nameof(Textbox) + }; + } + public Vector2i Size { get { return Bounds.Size; } set { Bounds.Size = value; } } + public Vector2i Location { get { return Bounds.Location; } set { Bounds.Location = value; } } + public Vector2i Distance { get { return Bounds.Distance; }} + public Texture Texture => Bounds.Texture!; + + public string Text { get => Label.Text; set => Label.Text = value; } + public Font Font { get => Label.Font; set => Label.Font = value; } + public Vector4 BackgroundColor { get => Bounds.Color; set => Bounds.Color = value; } + public float[] Points + { + get + { + return Bounds.Points; + } + set + { + if (Label is not null && Parent is not null) + { + Label.Location = new Vector2i((int)Parent.FloatToInt(value[10] + ((-1 - Parent.IntToFloat(10)) * -1)), (int)Parent.FloatToInt(value[6] + (-1 - Parent.IntToFloat(5)) * -1, true)); + } + Bounds.Points = value; + } + } + + public ObjectAnchor Anchor { get; set; } = ObjectAnchor.Left | ObjectAnchor.Top; + + private bool _loaded = false; + public bool Loaded => _loaded; + + public bool Visible { get; set; } = true; + + public void Clean() + { + Bounds.Clean(); + Label.Clean(); + } + + public void Draw() + { + Bounds.Draw(); + Label.Draw(); + } + public Window? Window { get; private set; } + public void LoadToParent(IParent Parent, Window Window) + { + this.Parent = Parent; + this.Window = Window; + this.Parent.MouseDown += Parrent_MouseDown; + this.Parent.KeyDown += Parrent_KeyDown; + Bounds.LoadToParent(Parent, Window); + Points = Points; + Label.LoadToParent(Parent, Window); + _loaded = true; + } + + private void Parrent_KeyDown(OpenTK.Windowing.Common.KeyboardKeyEventArgs obj) + { + if (use) + { + if (obj.Key == Keys.CapsLock || obj.Key == Keys.Menu || obj.Key == Keys.LeftSuper || obj.Key == Keys.RightSuper || obj.Key == Keys.End || obj.Key == Keys.Home || obj.Key == Keys.PageDown || obj.Key == Keys.PageUp || obj.Key == Keys.Insert || obj.Key == Keys.Up || obj.Key == Keys.Down || obj.Key == Keys.Left || obj.Key == Keys.Right) return; + if (obj.Key == Keys.Backspace) + { + if (!(Text.Length > 0)) return; + Text = Text.Remove(Text.Length - 1, 1); + } + else if (obj.Key == Keys.Delete) + { + if (!(Text.Length > 0)) return; + Text = Text.Remove(Text.Length - 1, 1); + } + else if (obj.Shift) + { + if (obj.Key == Keys.Enter || obj.Key == Keys.KeyPadEnter) Text += '\n'; + else if (obj.Key == Keys.LeftShift || obj.Key == Keys.KeyPadEnter || obj.Key == Keys.Enter || obj.Key == Keys.End || obj.Key == Keys.Down) return; + else Text += ((char)obj.Key).ToString().ToUpper(); + } + else if (obj.Command || obj.Alt || obj.Control) { } + else + { + Text += ((char)obj.Key).ToString().ToLower(); + } + } + } + + bool use = false; + + private void Parrent_MouseDown(OpenTK.Windowing.Common.MouseButtonEventArgs e) + { + if (e.Button == MouseButton.Button1 && Location.X <= Parent?.MousePosition.X && Size.X + Location.X >= Parent?.MousePosition.X && Location.Y + Size.Y >= Parent?.MousePosition.Y && Location.Y <= Parent?.MousePosition.Y) + { + use = true; + if (Clicked is not null) Clicked.Invoke(this); + } + else use = false; + } + + public Vector2 LocationAsFloat { get { return Bounds.LocationAsFloat; } } + public Vector2 SizeAsFloat { get { return Bounds.SizeAsFloat; } } +} diff --git a/GraphicsManager/Objects/UserControl.cs b/GraphicsManager/Objects/UserControl.cs new file mode 100644 index 0000000..8dc8604 --- /dev/null +++ b/GraphicsManager/Objects/UserControl.cs @@ -0,0 +1,183 @@ +using GraphicsManager.Enums; +using GraphicsManager.Interfaces; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; + +namespace GraphicsManager.Objects; + +public class UserControl : IRenderObject, IParent +{ + private Rectangle _bounds; + + public UserControl() + { + _bounds = new Rectangle(); + _bounds.Clicked += _bounds_Clicked; + } + + private Task _bounds_Clicked(IRenderObject arg) + { + _ = Clicked?.Invoke(arg)!; + return Task.CompletedTask; + } + + public ICollection Controls { get; } = new List(); + public ObjectAnchor Anchor { get => _bounds.Anchor; set => _bounds.Anchor = value; } + public Vector4 Color { get => _bounds.Color; set => _bounds.Color = value; } + public bool Visible { get => _bounds.Visible; set => _bounds.Visible = value; } + public Vector2i Size { get => _bounds.Size; set => _bounds.Size = value; } + public Vector2 SizeAsFloat { get => _bounds.SizeAsFloat; } + public Vector2i Location { get => _bounds.Location; set => _bounds.Location = value; } + public Vector2i Position => Location; + public Vector2 LocationAsFloat { get => _bounds.LocationAsFloat; } + public Vector2i Distance { get => _bounds.Distance; } + public event Func? Clicked; + public event Action MouseDown; + public event Action KeyDown; + + public IParent? Parent { get; private set; } + public Window? Window { get; private set; } + public bool Loaded { get; private set; } = false; + + public Vector2 MousePosition => throw new NotImplementedException(); + + public void LoadToParent(IParent Parent, Window Window) + { + this.Parent = Parent; + this.Window = Window; + _bounds.LoadToParent(Parent, Window); + foreach (IRenderObject obj in Controls) + { + obj.LoadToParent(this, Window); + } + Loaded = true; + } + + public void Draw() + { + if (Loaded) + { + _bounds.Draw(); + IEnumerable needload = Controls.Where(a => a.Loaded == false); + + if (needload.Any()) + { + foreach (IRenderObject Control in needload) + { + Control.LoadToParent(this, Window!); + } + } + foreach (IRenderObject Control in Controls) + { + Control.Draw(); + } + } + } + + public void Clean() + { + foreach (IRenderObject Control in Controls) + { + Control.Clean(); + } + _bounds.Clean(); + } + + public void Resize(ResizeEventArgs e) + { + if (e.Width == 0 && e.Height == 0) return; + foreach (IRenderObject Control in Controls) + { + if (Control.Loaded) + { + bool top = (Control.Anchor & ObjectAnchor.Top) == ObjectAnchor.Top; + bool left = (Control.Anchor & ObjectAnchor.Left) == ObjectAnchor.Left; + bool right = (Control.Anchor & ObjectAnchor.Right) == ObjectAnchor.Right; + bool bottom = (Control.Anchor & ObjectAnchor.Bottom) == ObjectAnchor.Bottom; + if (!top && !bottom) { Control.Anchor |= ObjectAnchor.Top; top = true; } + if (!left && !right) { Control.Anchor |= ObjectAnchor.Left; left = true; } + int lx = (left ? Control.Location.X : Size.X - Control.Distance.X - Control.Size.X); + int ly = (top ? Control.Location.Y : Size.Y - Control.Distance.Y - Control.Size.Y); + int sy = (bottom ? Size.Y - Control.Distance.Y - ly : Control.Size.Y); + int sx = (right ? Size.X - Control.Distance.X - lx : Control.Size.X); + Control.Size = new(sx, sy); + Control.Location = new(lx, ly); + } + } + } + + #region Cool Math Things + public float[] RctToFloat(int x, int y, int Width, int Height, bool hastexture = false, float z = 0.0f) + { + if (hastexture) + { + return new float[20] { + IntToFloat(x + Width), IntToFloat(y, true), z, 1.0f, 1.0f,// top r + IntToFloat(x + Width), IntToFloat(y + Height, true), z, 1.0f, 0.0f,//b r + IntToFloat(x), IntToFloat(y + Height, true), z, 0.0f, 0.0f,//bot l + IntToFloat(x), IntToFloat(y, true), z, 0.0f, 1.0f// top l + }; + } + else + { + return new float[12] { + IntToFloat(x + Width), IntToFloat(y, true), z,// top r + IntToFloat(x + Width), IntToFloat(y + Height, true), z, //b r + IntToFloat(x), IntToFloat(y + Height, true), z, //bot l + IntToFloat(x), IntToFloat(y, true), z,// top l + }; + } + } + + public float IntToFloat(int p, bool Invert = false) + { + int Size = (Invert ? this.Size.Y : this.Size.X); + double half = Math.Round((double)Size / (double)2, 1); + double Per = Math.Round((double)1 / half, 15); + if (p == half) return 0.0f; + if (Invert) + { + if (p > half) return (float)(((double)(p - half) * Per) * -1); + else return (float)(1 - (p * Per)); + } + else + { + if (p > half) return (float)((double)(p - half) * Per); + else return (float)((1 - (p * Per)) * -1); + } + } + + public float FloatToInt(float p, bool Invert = false) + { + int Size = (Invert ? this.Size.Y : this.Size.X); + double half = Math.Round((double)Size / (double)2, 15); + if (p == 0) return (int)half; + if (Invert) + { + if (p < 0) + { + p *= -1; + p++; + return (float)(half * p); + } + else + { + return (float)(half - (p * half)); + } + } + else + { + if (p < 0) + { + p *= -1; + p++; + return (float)(Size - (half * p)); + } + else + { + return (float)(p * half + half); + } + } + } + #endregion +} diff --git a/GraphicsManager/Resources/Fonts/TektonPro-Regular.otf b/GraphicsManager/Resources/Fonts/TektonPro-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..1c860f76f05e4c6003cc31dd3fef9a73ee9ffae4 GIT binary patch literal 79500 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 literal 0 HcmV?d00001 diff --git a/GraphicsManager/Resources/Shaders/Label.frag b/GraphicsManager/Resources/Shaders/Label.frag new file mode 100644 index 0000000..b348052 --- /dev/null +++ b/GraphicsManager/Resources/Shaders/Label.frag @@ -0,0 +1,16 @@ +#version 460 + +in vec2 vUV; + +layout (binding=0) uniform sampler2D u_texture; + +layout (location = 2) uniform vec4 textColor; + +out vec4 fragColor; + +void main() +{ + vec2 uv = vUV.xy; + float text = texture(u_texture, uv).r; + fragColor = vec4(textColor.rgba*text); +} \ No newline at end of file diff --git a/GraphicsManager/Resources/Shaders/Label.vert b/GraphicsManager/Resources/Shaders/Label.vert new file mode 100644 index 0000000..d2ab3c8 --- /dev/null +++ b/GraphicsManager/Resources/Shaders/Label.vert @@ -0,0 +1,15 @@ +#version 460 + +layout (location = 0) in vec2 in_pos; +layout (location = 1) in vec2 in_uv; + +out vec2 vUV; + +layout (location = 0) uniform mat4 model; +layout (location = 1) uniform mat4 projection; + +void main() +{ + vUV = in_uv.xy; + gl_Position = projection * model * vec4(in_pos.xy, 0.0, 1.0); +} \ No newline at end of file diff --git a/GraphicsManager/Resources/Shaders/Rectangle.frag b/GraphicsManager/Resources/Shaders/Rectangle.frag new file mode 100644 index 0000000..c96630f --- /dev/null +++ b/GraphicsManager/Resources/Shaders/Rectangle.frag @@ -0,0 +1,9 @@ +#version 330 core +out vec4 FragColor; + +in vec4 vertexColor; + +void main() +{ + FragColor = vertexColor; +} \ No newline at end of file diff --git a/GraphicsManager/Resources/Shaders/Rectangle.vert b/GraphicsManager/Resources/Shaders/Rectangle.vert new file mode 100644 index 0000000..280766f --- /dev/null +++ b/GraphicsManager/Resources/Shaders/Rectangle.vert @@ -0,0 +1,10 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +uniform vec4 objColor; +out vec4 vertexColor; + +void main() +{ + gl_Position = vec4(aPos, 1.0); + vertexColor = objColor; +} \ No newline at end of file diff --git a/GraphicsManager/Resources/Shaders/RectangleTexture.frag b/GraphicsManager/Resources/Shaders/RectangleTexture.frag new file mode 100644 index 0000000..981cfab --- /dev/null +++ b/GraphicsManager/Resources/Shaders/RectangleTexture.frag @@ -0,0 +1,8 @@ +#version 330 +out vec4 outputColor; +in vec2 texCoord; +uniform sampler2D texture1; +void main(void) +{ + outputColor = texture(texture1, texCoord); +} \ No newline at end of file diff --git a/GraphicsManager/Resources/Shaders/RectangleTexture.vert b/GraphicsManager/Resources/Shaders/RectangleTexture.vert new file mode 100644 index 0000000..013d06b --- /dev/null +++ b/GraphicsManager/Resources/Shaders/RectangleTexture.vert @@ -0,0 +1,9 @@ +#version 330 +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec2 aTexCoord; +out vec2 texCoord; +void main(void) +{ + texCoord = aTexCoord; + gl_Position = vec4(aPosition, 1.0); +} \ No newline at end of file diff --git a/GraphicsManager/Resources/Textures/Textbox.png b/GraphicsManager/Resources/Textures/Textbox.png new file mode 100644 index 0000000000000000000000000000000000000000..995ac5d2eceb8002438a9493e548f0a3001c8c89 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^jzFx!!3HF+oeN(Bq!^2X+?^QKos)S9jszMyoGAa!aaEGhtwAt>MS^JR3&+pyU9)PodrNIxaxp)kJa+k3djSr$ UGymRYfxOD#>FVdQ&MBb@0NjIFl>h($ literal 0 HcmV?d00001 diff --git a/GraphicsManager/Structs/Character.cs b/GraphicsManager/Structs/Character.cs new file mode 100644 index 0000000..2325118 --- /dev/null +++ b/GraphicsManager/Structs/Character.cs @@ -0,0 +1,12 @@ +using GraphicsManager.Objects.Core; +using OpenTK.Mathematics; + +namespace GraphicsManager.Structs; + +public struct Character +{ + public Texture Texture { get; set; } + public Vector2 Size { get; set; } + public Vector2 Bearing { get; set; } + public int Advance { get; set; } +} diff --git a/GraphicsManager/Tools.cs b/GraphicsManager/Tools.cs new file mode 100644 index 0000000..ce3dad6 --- /dev/null +++ b/GraphicsManager/Tools.cs @@ -0,0 +1,31 @@ +using System.Reflection; + +namespace GraphicsManager; + +public class Tools +{ + public static byte[] GetResourceBytes(Assembly Assembly, string Resource) + { + Stream str = Assembly.GetManifestResourceStream(Resource)!; + MemoryStream ms = new(); + str.CopyTo(ms); + str.Dispose(); + byte[] result = ms.ToArray(); + ms.Dispose(); + return result; + } + + public static string GetResourceString(Assembly Assembly, string Resource) + { + Stream str = Assembly.GetManifestResourceStream(Resource)!; + StreamReader sr = new(str); + string result = sr.ReadToEnd(); + sr.Dispose(); + str.Dispose(); + return result; + } + + public static byte[] GetResourceBytes(string Resource) => GetResourceBytes(typeof(Tools).Assembly, Resource); + + public static string GetResourceString(string Resource) => GetResourceString(typeof(Tools).Assembly, Resource); +} diff --git a/GraphicsManager/Window.cs b/GraphicsManager/Window.cs new file mode 100644 index 0000000..01b7e56 --- /dev/null +++ b/GraphicsManager/Window.cs @@ -0,0 +1,165 @@ +using GraphicsManager.Enums; +using GraphicsManager.Interfaces; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; + +namespace GraphicsManager; + +public class Window : NativeWindow , IParent +{ + public Window(NativeWindowSettings nativeWindowSettings) : base(nativeWindowSettings) + { + } + + public Window() : base(new NativeWindowSettings()) + { + + } + public Vector2i Position { get; } = new Vector2i(0, 0); + public Color4 BackgroundColor { get; set; } = new Color4(0, 0, 0, 255); + + public ICollection Controls { get; } = new List(); +#region Cool Math Things + public float[] RctToFloat(int x, int y, int Width, int Height, bool hastexture = false, float z = 0.0f) + { + if (hastexture) + { + return new float[20] { + IntToFloat(x + Width), IntToFloat(y, true), z, 1.0f, 1.0f,// top r + IntToFloat(x + Width), IntToFloat(y + Height, true), z, 1.0f, 0.0f,//b r + IntToFloat(x), IntToFloat(y + Height, true), z, 0.0f, 0.0f,//bot l + IntToFloat(x), IntToFloat(y, true), z, 0.0f, 1.0f// top l + }; + } + else + { + return new float[12] { + IntToFloat(x + Width), IntToFloat(y, true), z,// top r + IntToFloat(x + Width), IntToFloat(y + Height, true), z, //b r + IntToFloat(x), IntToFloat(y + Height, true), z, //bot l + IntToFloat(x), IntToFloat(y, true), z,// top l + }; + } + } + + public float IntToFloat(int p, bool Invert = false) + { + int Size = (Invert ? this.Size.Y : this.Size.X); + double half = Math.Round((double)Size / (double)2, 1); + double Per = Math.Round((double)1 / half, 15); + if (p == half) return 0.0f; + if (Invert) + { + if (p > half) return (float)(((double)(p - half) * Per) * -1); + else return (float)(1 - (p * Per)); + } + else + { + if (p > half) return (float)((double)(p - half) * Per); + else return (float)((1 - (p * Per)) * -1); + } + } + + public float FloatToInt(float p, bool Invert = false) + { + int Size = (Invert ? this.Size.Y : this.Size.X); + double half = Math.Round((double)Size / (double)2, 15); + if (p == 0) return (int)half; + if (Invert) + { + if (p < 0) + { + p *= -1; + p++; + return (float)(half * p); + } + else + { + return (float)(half - (p * half)); + } + } + else + { + if (p < 0) + { + p *= -1; + p++; + return (float)(Size - (half * p)); + } + else + { + return (float)(p * half + half); + } + } + } +#endregion + + public void Resize(ResizeEventArgs e) + { + if (e.Width == 0 && e.Height == 0) return; + base.OnResize(e); + GL.Viewport(0, 0, e.Width, e.Height); + foreach (IRenderObject Control in Controls) + { + if (Control.Loaded) + { + bool top = (Control.Anchor & ObjectAnchor.Top) == ObjectAnchor.Top; + bool left = (Control.Anchor & ObjectAnchor.Left) == ObjectAnchor.Left; + bool right = (Control.Anchor & ObjectAnchor.Right) == ObjectAnchor.Right; + bool bottom = (Control.Anchor & ObjectAnchor.Bottom) == ObjectAnchor.Bottom; + if (!top && !bottom) { Control.Anchor |= ObjectAnchor.Top; top = true; } + if (!left && !right) { Control.Anchor |= ObjectAnchor.Left; left = true; } + int lx = (left ? Control.Location.X : Size.X - Control.Distance.X - Control.Size.X); + int ly = (top ? Control.Location.Y : Size.Y - Control.Distance.Y - Control.Size.Y); + int sy = (bottom ? Size.Y - Control.Distance.Y - ly : Control.Size.Y); + int sx = (right ? Size.X - Control.Distance.X - lx : Control.Size.X); + Control.Size = new(sx, sy); + Control.Location = new(lx, ly); + if (Control is IParent) + { + IParent parent = (IParent)Control; + parent.Resize(e); + } + } + } + } + + protected override void OnResize(ResizeEventArgs e) + { + Resize(e); + } + + public void StartRender() + { + Context.MakeCurrent(); + while (Exists && IsVisible && !IsExiting) + { + DrawFrame(); + } + } + + public void DrawFrame() + { + ProcessEvents(); + GL.ClearColor(BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, (BackgroundColor.A * -1) + 1); + IEnumerable needload = Controls.Where(a => a.Loaded == false); + + if (needload.Any()) + { + foreach (IRenderObject obj in needload) + { + obj.LoadToParent(this, this); + } + } + + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + foreach (IRenderObject obj in Controls) + { + if (obj.Loaded) obj.Draw(); + } + Context.SwapBuffers(); + Thread.Sleep(8); + } +} \ No newline at end of file