From 70ea6d05557afebccfb296854f10c8d6217ec1af Mon Sep 17 00:00:00 2001 From: Booklordofthedings Date: Thu, 6 Jun 2024 15:31:32 +0200 Subject: [PATCH] checkbox, radio button, and parts of the toolbar work now --- README.md | 24 ++- assets/32.png~ | Bin 559 -> 0 bytes assets/horizontal_patch.png | Bin 0 -> 207 bytes docs/Button.md | 0 src/App.bf | 177 +++++++++++++------- src/{Controls => Components}/Button.bf | 20 +-- src/Components/Checkbox.bf | 49 ++++++ src/Components/Component.bf | 103 ++++++++++++ src/{Controls => Components}/Container.bf | 51 +++--- src/{Controls => Components}/IconButton.bf | 2 +- src/Components/Label.bf | 25 +++ src/{Controls => Components}/LargeButton.bf | 6 +- src/{Controls => Components}/NButton.bf | 6 +- src/Components/Placeholder.bf | 13 ++ src/Components/RadioButton.bf | 62 +++++++ src/{Controls => Components}/Screen.bf | 2 +- src/Components/Toolbar.bf | 142 ++++++++++++++++ src/Components/ToolbarPopup.bf | 57 +++++++ src/Controls/Checkbox.bf | 21 --- src/Controls/GuiObject.bf | 109 ------------ src/Controls/Label.bf | 28 ---- src/Controls/Placeholder.bf | 12 -- src/Extensions.bf | 27 ++- src/Generators/ButtonGenerator.bf | 2 +- src/Generators/CheckboxGenerator.bf | 48 ++++++ src/Generators/IconButtonGenerator.bf | 2 +- src/Generators/LargeButtonGenerator.bf | 2 +- src/Generators/NButtonGenerator.bf | 2 +- src/Generators/RadioButtonGenerator.bf | 48 ++++++ src/PlatformLayer.bf | 21 +++ src/Textures.bf | 2 + src/Theme.bf | 7 + 32 files changed, 788 insertions(+), 282 deletions(-) delete mode 100644 assets/32.png~ create mode 100644 assets/horizontal_patch.png create mode 100644 docs/Button.md rename src/{Controls => Components}/Button.bf (55%) create mode 100644 src/Components/Checkbox.bf create mode 100644 src/Components/Component.bf rename src/{Controls => Components}/Container.bf (73%) rename src/{Controls => Components}/IconButton.bf (94%) create mode 100644 src/Components/Label.bf rename src/{Controls => Components}/LargeButton.bf (61%) rename src/{Controls => Components}/NButton.bf (76%) create mode 100644 src/Components/Placeholder.bf create mode 100644 src/Components/RadioButton.bf rename src/{Controls => Components}/Screen.bf (91%) create mode 100644 src/Components/Toolbar.bf create mode 100644 src/Components/ToolbarPopup.bf delete mode 100644 src/Controls/Checkbox.bf delete mode 100644 src/Controls/GuiObject.bf delete mode 100644 src/Controls/Label.bf delete mode 100644 src/Controls/Placeholder.bf create mode 100644 src/Generators/CheckboxGenerator.bf create mode 100644 src/Generators/RadioButtonGenerator.bf create mode 100644 src/PlatformLayer.bf diff --git a/README.md b/README.md index d6981bc..b6aa62c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,23 @@ -# TheaterGui +# TheaterGui *A simple ui library* +*Tg* is inspired by Winforms, so most components require their own new classes, generated by the "New File" functionality of BeefIDE -Simple library to create graphical user interfaces \ No newline at end of file +### Components +- Container + - Screen +- Button + +### Layouting +*Tg* uses a layouting system thats partially inspired by HTML, though its significantly less powerfull but easier to implement and to grasp. +All components are put next to their immidiate left neighbor. +If an object would overflow outside of the view width, it will instead be forced down onto the next row. +Calling *EndRow()* manually will also force components into the next row. +The vertical position of the next row is determined by the height of the largest component in the row above, so that they never overlap. +If the screen height is larger than the view it will scroll. +Containers are a component, that can be used for more complicated layouts. +A container grows to fits its children but is still treated like a normal components. +So if part of your layout needs to be vertical you would put it inside of a container and then add the container normally +Every component also has a Margin property, to make things look better. + +Whenever a component changes its size, the app will need to reorder its layout to fit everything. +*App.ForceReorder()* Is used for that. Most components will call it themselves if their size changes +If you want to change alot of sizes at once consider turning of *App.AllowReorder* and then manually calling it after you are done to increase the total speed. \ No newline at end of file diff --git a/assets/32.png~ b/assets/32.png~ deleted file mode 100644 index cf80bc89f052836ac49858f64bdec13c5bf0159e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 559 zcmV+~0?_@5P)>-~c0FC;$r1OATxpw{B=D58iL6hXlk zUj*?%&?;(LC=@|NAN&EwM6!@DTh>;Y%sHy%-pTFNtI6bLp6 z=HOx0h4KIU=eS6+Ce~tBfuP}Fj-Db(@QBl7m7!;yJxxQ8tj*DZiQK=P#VUYsZLS8k zQudrRvS?cDrRy9DqgH)n-L}{poH}ZOtE*bCnKyAt-f;v@(o)t25lng$I3XNC)_@W| zuVmg6!`^0yg4)m$*5>+^VQ+AhIzr?ECG21?!r;9+LK8i^kZDsWzrmrv5s+2D*S#Xb zc4bsB@wB9S-4l(6!-1uOOJUXp?R3NG`vrhenC%01@s?W#`-~&J3Uo@chcbO2!A(i_ z274Yy6cjMs>m9~mkWkV-V9u{`cAyFD5_^PpS>ppnp$fRH=&XS&oU0^(9fu0K@zx5n z1D_SKCmLSyo$#U}_6FOQ3P}R3daA-#?lYk}q~8+CZqdPVxDvr|pjOp)!3C}7=r$Zi x@Z{$QM)JGi5R%9#evI>Mn;ys`i3xc(+&{=zbju$YdN}|9002ovPDHLkV1l`k{T~1T diff --git a/assets/horizontal_patch.png b/assets/horizontal_patch.png new file mode 100644 index 0000000000000000000000000000000000000000..edfcf5ab6af4fa5b9fe4190c084f6ae3ba8b6653 GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3HE3&8=$zQk(@Ik;M!Q{D~mUxWayUCQz`^ z)5S3)%*4ayW7*uqZm%F{f!6?Eb0I!1Rd4MEJkleMYaitcBNC)XV@nhQZU-&t;ucLK6TR Cn@3Xs literal 0 HcmV?d00001 diff --git a/docs/Button.md b/docs/Button.md new file mode 100644 index 0000000..e69de29 diff --git a/src/App.bf b/src/App.bf index e47a6b9..679ecbf 100644 --- a/src/App.bf +++ b/src/App.bf @@ -1,14 +1,75 @@ namespace TheaterGui; -using TheaterGui.Controls; +using TheaterGui.Components; using System; +using System.Collections; using RaylibBeef; class App { + + //All loaded textures + private static List Popups = new .() ~ DeleteContainerAndItems!(_); + public static Textures Textures ~ delete _; + public static Component Selected; //Currently selected component + public static bool AllowReorder = true; + + private static Screen _CurrentScreen = null; + public static Screen CurrentScreen + { + get => _CurrentScreen; + set + { + if(_CurrentScreen != null) + delete _CurrentScreen; + _CurrentScreen = value; + } + }; + + ///Window width + public static int32 Width + { + get => Raylib.GetRenderWidth(); + set => Raylib.SetWindowSize(value, Raylib.GetRenderHeight()); + } + + ///Window height + public static int32 Height + { + get => Raylib.GetRenderHeight(); + set => Raylib.SetWindowSize(Raylib.GetRenderWidth(), value); + } + + ///Mouse position + public static Vector2 Mouse + { + get => Raylib.GetMousePosition(); + } + + //Mouse wheel offset + public static float Wheel + { + get; + set; + } + + public static void ForceReorder() + { + if(AllowReorder && CurrentScreen != null) + CurrentScreen.Reorder(Width); + } + + public static void OpenPopup(Component popup) + { + Popups.Add(popup); + } + + + ///Initialize the TheaterGui app public static void Initialize(StringView pAppTitle = "TheaterApp") { + //Default raylib settings Raylib.SetConfigFlags((.)(ConfigFlags.FLAG_WINDOW_RESIZABLE | ConfigFlags.FLAG_WINDOW_HIGHDPI)); Raylib.InitWindow(1280, 720, scope String(pAppTitle)); Raylib.SetExitKey(0); @@ -22,7 +83,10 @@ class App //Load textures Textures = new .(); - Theme.Font = Raylib.LoadFontFromMemory(".ttf", (.)&Theme.Din, Theme.Din.Count, 16, null, 256); + Theme.Font = Raylib.LoadFontFromMemory(".ttf", (.)&Theme.Din, Theme.Din.Count, Theme.FontSize, null, 256); + Theme.FontSmall = Raylib.LoadFontFromMemory(".ttf", (.)&Theme.Din, Theme.Din.Count, Theme.FontSizeSmall, null, 256); + Theme.FontLarge = Raylib.LoadFontFromMemory(".ttf", (.)&Theme.Din, Theme.Din.Count, Theme.FontSizeLarge, null, 256); + } ///Deinitialize the TheaterGui app @@ -61,38 +125,64 @@ class App if(Raylib.IsMouseButtonPressed(0)) { - CurrentScreen.OnClick(Raylib.GetMouseX(), (.)(Raylib.GetMouseY()+App.Wheel)); + if(Popups.Count > 0) + { + var top = Popups[Popups.Count-1]; + if(Rectangle(top.X, top.Y, top.Width, top.Height).Overlaps(Raylib.GetMouseX(), Raylib.GetMouseY())) + top.OnClick(Raylib.GetMouseX(), Raylib.GetMouseY()); + else + { + Popups.Remove(top); + delete top; + + CurrentScreen.OnClick(Raylib.GetMouseX(), (.)(Raylib.GetMouseY()+App.Wheel)); + } + } + else + CurrentScreen.OnClick(Raylib.GetMouseX(), (.)(Raylib.GetMouseY()+App.Wheel)); } else { - var hov = CurrentScreen.OnHover(Raylib.GetMouseX(), (.)(Raylib.GetMouseY()+App.Wheel)); - if(hov != null) + if(Popups.Count > 0) { - if(hov.Enabled) - Raylib.SetMouseCursor((.)MouseCursor.MOUSE_CURSOR_POINTING_HAND); - //TODO: If we go offscreen with the mouse it doesnt work anymore - if(oldPos != Mouse) - movTime = Raylib.GetTime(); - if(Raylib.GetTime() - movTime >= 0.5) - hoverText.Append(hov.Description); - oldPos = Mouse; + var top = Popups[Popups.Count-1]; + if(Rectangle(top.X, top.Y, top.Width, top.Height).Overlaps(Raylib.GetMouseX(), Raylib.GetMouseY())) + { + + } } else - Raylib.SetMouseCursor((.)MouseCursor.MOUSE_CURSOR_DEFAULT); + { + var hov = CurrentScreen.OnHover(Raylib.GetMouseX(), (.)(Raylib.GetMouseY()+App.Wheel)); + if(hov != null) + { + if(hov.Enabled) + Raylib.SetMouseCursor((.)MouseCursor.MOUSE_CURSOR_POINTING_HAND); + //TODO: If we go offscreen with the mouse it doesnt work anymore + if(oldPos != Mouse) + movTime = Raylib.GetTime(); + if(Raylib.GetTime() - movTime >= 0.5) + hoverText.Append(hov.Description); + oldPos = Mouse; + } + else + Raylib.SetMouseCursor((.)MouseCursor.MOUSE_CURSOR_DEFAULT); + } } //Drawing logic Raylib.BeginDrawing(); Raylib.ClearBackground(Theme.Background); CurrentScreen.Render(); + for(var i in Popups) + i.Render(); if(hoverText != String.Empty) - { + { //TODO: When you move outside of the window, the hover fucks up int32 yOffset = 0; int32 xOffset = 0; - //TODO: Curently just goes offscreen when it doesnt work - var measure = Raylib.MeasureTextEx(Theme.Font, hoverText, Theme.FontSize, 0); + var measure = Raylib.MeasureTextEx(Theme.FontSmall, hoverText, Theme.FontSizeSmall, 0); if(Raylib.GetMouseX() < Width/2) xOffset = 15; else @@ -104,7 +194,7 @@ class App Raylib.DrawRectangle((.)Mouse.x-3+xOffset+3, (.)Mouse.y-3+yOffset+3, (.)measure.x+6, (.)measure.y+3, .(0, 0, 0, 70)); Raylib.DrawRectangle((.)Mouse.x-3+xOffset, (.)Mouse.y-3+yOffset, (.)measure.x+6, (.)measure.y+3, .(218, 211, 176, 255)); Raylib.DrawRectangleLines((.)Mouse.x-3+xOffset, (.)Mouse.y-3+yOffset, (.)measure.x+6, (.)measure.y+3, Raylib.BLACK); - Raylib.DrawTextEx(Theme.Font, hoverText, Raymath.Vector2Add(Mouse, .(xOffset,yOffset)), Theme.FontSize, 0, Raylib.BLACK); + Raylib.DrawTextEx(Theme.FontSmall, hoverText, Raymath.Vector2Add(Mouse, .(xOffset,yOffset)), Theme.FontSizeSmall, 0, Raylib.BLACK); } Raylib.EndDrawing(); @@ -114,55 +204,14 @@ class App } } - // - ///Platform - // - - //All loaded textures - public static Textures Textures ~ delete _; - - private static Screen _CurrentScreen = null; - public static Screen CurrentScreen + ///Checks wether any popup catches the hover, returns wether it has been caught + public bool HandleHoverPopups() { - get => _CurrentScreen; - set + //Topmost first + for(var i in Popups.Reversed) { - if(_CurrentScreen != null) - delete _CurrentScreen; - _CurrentScreen = value; + } - }; - ///Window width - public static int32 Width - { - get => Raylib.GetRenderWidth(); - set => Raylib.SetWindowSize(value, Raylib.GetRenderHeight()); - } - ///Window height - public static int32 Height - { - get => Raylib.GetRenderHeight(); - set => Raylib.SetWindowSize(Raylib.GetRenderWidth(), value); - } - ///Mouse position - public static Vector2 Mouse - { - get => Raylib.GetMousePosition(); - } - //Mouse wheel offset - public static float Wheel - { - get; - set; - } - public static GuiObject Selected - { - get; - set; } - public static void ForceReorder() - { - CurrentScreen.Reorder(Width); - } } \ No newline at end of file diff --git a/src/Controls/Button.bf b/src/Components/Button.bf similarity index 55% rename from src/Controls/Button.bf rename to src/Components/Button.bf index d43a7c5..0609176 100644 --- a/src/Controls/Button.bf +++ b/src/Components/Button.bf @@ -1,22 +1,22 @@ -namespace TheaterGui.Controls; +namespace TheaterGui.Components; using System; using RaylibBeef; -abstract class Button : GuiObject +abstract class Button : Component { - public sprite Sprite; - - public this(StringView pName, StringView pComponentName = "Button") : base(pComponentName, pName) + public this(StringView pLabel, StringView pComponentName = "Button") : base(pComponentName, pLabel) { Sprite = App.Textures.GetAsset("button"); - Label.Append(pName); Width = Sprite.Width; Height = Sprite.Height; } + public abstract void ClickAction(); //What happends when its clicked + + protected bool _IsHovered = false; - public override GuiObject OnHover(int32 x, int32 y) + public override Component OnHover(int32 x, int32 y) { _IsHovered = true; return this; @@ -39,8 +39,8 @@ abstract class Button : GuiObject public override void Render() { Sprite.Render(X,Y, Tint); - var measure = Raylib.MeasureTextEx(Theme.Font, Label, Theme.FontSize, 0); - Raylib.DrawTextEx(Theme.Font, Label, .(X+Width/2-measure.x/2,Y+Height/2-measure.y/2), Theme.FontSize, 0, Theme.Text); + var measure = Raylib.MeasureTextEx(Theme.Font, Label.ToScopeCStr!(), Theme.FontSize, 0); + Raylib.DrawTextEx(Theme.Font, Label.ToScopeCStr!(), .(X+Width/2-measure.x/2,Y+Height/2-measure.y/2), Theme.FontSize, 0, Theme.Text); if(!Enabled) { Sprite.Render(X,Y, Theme.DisabledTint); @@ -52,6 +52,4 @@ abstract class Button : GuiObject } _IsHovered = false; } - - public abstract void ClickAction(); } \ No newline at end of file diff --git a/src/Components/Checkbox.bf b/src/Components/Checkbox.bf new file mode 100644 index 0000000..83ede84 --- /dev/null +++ b/src/Components/Checkbox.bf @@ -0,0 +1,49 @@ +namespace TheaterGui.Components; + +using System; +using RaylibBeef; + +abstract class Checkbox : Component +{ + public sprite SpriteChecked; + + public bool Checked = false; + + public abstract void OnCheck(bool checkValue); + + public this(StringView pName) : base("Checkbox", pName) + { + Sprite = App.Textures.GetAsset("checkbox"); + SpriteChecked = App.Textures.GetAsset("checkbox_checked"); + var measure = Raylib.MeasureTextEx(Theme.Font, Label.Ptr, Theme.FontSize, 0); + Width = Sprite.Width + (.)measure.x + 2; + Height = measure.y > Sprite.Height ? (.)measure.y : Sprite.Height; + } + + protected bool _IsHovered = false; + public override Component OnHover(int32 x, int32 y) + { + _IsHovered = true; + return this; + } + + public override bool OnClick(int32 x, int32 y) + { + Checked = !Checked; + OnCheck(Checked); + return true; + } + + public override void Render() + { + if(!Checked) + Sprite.Render(X,Y,Tint); + else + SpriteChecked.Render(X,Y,Tint); + var measure = Raylib.MeasureTextEx(Theme.Font, Label.ToScopeCStr!(), Theme.FontSize, 0); + Raylib.DrawTextEx(Theme.Font, Label.ToScopeCStr!(), .(X + Sprite.Width + 2, (.)(Y + Sprite.Height/2 - 0.5*measure.y)), Theme.FontSize, 0, Theme.Text); + if(_IsHovered) + Sprite.Render(X,Y, HoverTint); + _IsHovered = false; + } +} \ No newline at end of file diff --git a/src/Components/Component.bf b/src/Components/Component.bf new file mode 100644 index 0000000..088d9df --- /dev/null +++ b/src/Components/Component.bf @@ -0,0 +1,103 @@ +namespace TheaterGui.Components; + +using System; +using RaylibBeef; + +abstract class Component +{ + public this(StringView pComponentName, StringView pLabel) + { + _ComponentType.Append(pComponentName); + Label = pLabel; + } + + /// + /// Fields + /// + public Container Parent; + public bool Enabled = true; + + public Color Tint = Theme.Tint; + public Color HoverTint = Theme.HoverTint; + public sprite Sprite; + + public int32 MarginTop = 0; + public int32 MarginBottom = 0; + public int32 MarginLeft = 0; + public int32 MarginRight = 0; + + /// + /// Properties + /// + public bool Selected + { //When something is selected its allowed to update every frame + get; + set + { + Selected = value; + App.Selected = this; + } + } + + public int32 Margin + { + //There seems to be no reasonable way to implement a getter here + set + { + MarginTop = value; + MarginBottom = value; + MarginLeft = value; + MarginRight = value; + } + } + + private int32 _X = 0; + public int32 X + { + get => _X; + private set + { + _X = value; + } //You may set this, but it might fuck up layouting + }; + + private int32 _Y = 0; + public int32 Y + { + get => _Y; + private set + { + _Y = value; + } //You may set this, but it might fuck up layouting + }; + + public int32 Width + { + public get; + protected set; //Please do not + } + + public int32 Height + { + public get; + protected set; //Please do not + } + + [DynamicString] //What this type of component is called + private String _ComponentType = new .() ~ delete _; + [DynamicString] //What the component says on it when rendered + private String _Label = null ~ delete _; + [DynamicString] //Usually only used by the OnHover tooltip + private String _Description = null ~ delete _; + + + /// + /// Functions + /// + public abstract void Render(); + + public virtual bool OnClick(int32 x, int32 y) => false; + public abstract Component OnHover(int32 x, int32 y); + + public virtual void WhileSelected() { } +} \ No newline at end of file diff --git a/src/Controls/Container.bf b/src/Components/Container.bf similarity index 73% rename from src/Controls/Container.bf rename to src/Components/Container.bf index f4fba74..17db09e 100644 --- a/src/Controls/Container.bf +++ b/src/Components/Container.bf @@ -1,20 +1,21 @@ -namespace TheaterGui.Controls; +namespace TheaterGui.Components; using System; using System.Collections; -class Container : GuiObject +class Container : Component { - private List _LayoutData = new .() ~ delete _; - public List Children = new .() ~ DeleteContainerAndItems!(_); //Entries => LayoutData without nulls - public this(StringView pName, StringView pComponentName = "Container") : base(pName, pComponentName) { } - ///Reorder all entries - /// @params w The Maximum width that the container should take + private List _LayoutData = new .() ~ delete _; + public List Children = new .() ~ DeleteContainerAndItems!(_); + + + + ///Recalculate the layout of all entries public virtual void Reorder(int32 w) { /* @@ -47,13 +48,12 @@ class Container : GuiObject if(e is Container) { ((Container)e).Reorder(w); - for(var i in ((Container)e).Children) - { - i.X += x + MarginLeft + e.MarginLeft; - i.Y += y + MarginTop + e.MarginTop; - } + MoveChildrenRecursive((Container)e, x, y); } + if(e is Toolbar) + e.Width = App.Width; + if(x+e.Width+e.MarginLeft > w-MarginLeft) //Change both instances of padding to 2*Padding to ensure proper padding on the leftmost side of the screen { //Automatic row break x = 0; @@ -61,9 +61,9 @@ class Container : GuiObject rowHeight = 0; } - e.X = x + e.MarginLeft; + e.[Friend]X = x + e.MarginLeft; x = x + e.Width + e.MarginLeft + e.MarginRight; - e.Y = y + e.MarginTop; + e.[Friend]Y = y + e.MarginTop; if(x > maxWidth) maxWidth = x; @@ -75,15 +75,26 @@ class Container : GuiObject Height = y + rowHeight + MarginTop + MarginBottom; } + private void MoveChildrenRecursive(Container c, int32 x, int32 y) + { + for(var i in c.Children) + { + i.[Friend]X += x + MarginLeft + c.MarginLeft; + i.[Friend]Y += y + MarginTop + c.MarginTop; + if(i is Container) + MoveChildrenRecursive((Container)i, x, y); + } + } + ///Add a new item to the list of items - public void AddChild(GuiObject pToAdd) + public void AddChild(Component pToAdd) { _LayoutData.Add(pToAdd); Children.Add(pToAdd); pToAdd.Parent = this; } - public virtual void InsertBefore(GuiObject pToAdd, GuiObject Position, bool pReorder = true) + public virtual void InsertBefore(Component pToAdd, Component Position, bool pReorder = true) { var idx = _LayoutData.IndexOf(Position); if(idx < 0) @@ -99,7 +110,7 @@ class Container : GuiObject App.ForceReorder(); } - public virtual void InsertAfter(GuiObject pToAdd, GuiObject Position, bool pReorder = true) + public virtual void InsertAfter(Component pToAdd, Component Position, bool pReorder = true) { var idx = _LayoutData.IndexOf(Position); idx++; @@ -122,7 +133,9 @@ class Container : GuiObject public override void Render() { for(var i in Children) - i.Render(); + { + i.Render(); + } } public override bool OnClick(int32 x, int32 y) @@ -135,7 +148,7 @@ class Container : GuiObject return false; } - public override GuiObject OnHover(int32 x, int32 y) + public override Component OnHover(int32 x, int32 y) { for(var e in Children) { diff --git a/src/Controls/IconButton.bf b/src/Components/IconButton.bf similarity index 94% rename from src/Controls/IconButton.bf rename to src/Components/IconButton.bf index 161b6a9..9d24c5d 100644 --- a/src/Controls/IconButton.bf +++ b/src/Components/IconButton.bf @@ -1,4 +1,4 @@ -namespace TheaterGui.Controls; +namespace TheaterGui.Components; using System; diff --git a/src/Components/Label.bf b/src/Components/Label.bf new file mode 100644 index 0000000..e6ae47d --- /dev/null +++ b/src/Components/Label.bf @@ -0,0 +1,25 @@ +namespace TheaterGui.Components; + +using System; +using RaylibBeef; + +class Label : Component +{ + private Vector2 _Measure; + public this(StringView pName) : base(Label, pName) + { + Label = pName; + Margin = 3; + _Measure = Raylib.MeasureTextEx(Theme.FontLarge, Label.Ptr, Theme.FontSizeLarge, 0); + Height = (.)_Measure.y; + Width = (.)_Measure.x; + } + + public override Component OnHover(int32 x, int32 y) => null; + + public override void Render() + { + Raylib.DrawTextEx(Theme.FontLarge, Label.Ptr, .(X,Y), Theme.FontSizeLarge, 0, Theme.Text); + Raylib.DrawLine(X,Y + (.)_Measure.y,X + (.)_Measure.x, Y + (.)_Measure.y, Theme.Text); + } +} \ No newline at end of file diff --git a/src/Controls/LargeButton.bf b/src/Components/LargeButton.bf similarity index 61% rename from src/Controls/LargeButton.bf rename to src/Components/LargeButton.bf index 0180936..f63a8a0 100644 --- a/src/Controls/LargeButton.bf +++ b/src/Components/LargeButton.bf @@ -1,4 +1,4 @@ -namespace TheaterGui.Controls; +namespace TheaterGui.Components; using System; using RaylibBeef; @@ -15,8 +15,8 @@ abstract class LargeButton : Button public override void Render() { Sprite.Render(X,Y, Tint); - var measure = Raylib.MeasureTextEx(Theme.Font, Label, Theme.FontSize, 0); - Raylib.DrawTextEx(Theme.Font, Label, .(X+Width/2-measure.x/2,Y+Height/2-measure.y/2), Theme.FontSize, 0, Theme.Text); + var measure = Raylib.MeasureTextEx(Theme.Font, Label.Ptr, Theme.FontSize, 0); + Raylib.DrawTextEx(Theme.Font, Label.Ptr, .(X+Width/2-measure.x/2,Y+Height/2-measure.y/2), Theme.FontSize, 0, Theme.Text); if(_IsHovered) Sprite.Render(X,Y, HoverTint); _IsHovered = false; diff --git a/src/Controls/NButton.bf b/src/Components/NButton.bf similarity index 76% rename from src/Controls/NButton.bf rename to src/Components/NButton.bf index d838cc0..baa5b75 100644 --- a/src/Controls/NButton.bf +++ b/src/Components/NButton.bf @@ -1,4 +1,4 @@ -namespace TheaterGui.Controls; +namespace TheaterGui.Components; using System; using RaylibBeef; @@ -27,8 +27,8 @@ abstract class NButton : Button 0, Tint); - var measure = Raylib.MeasureTextEx(Theme.Font, Label, Theme.FontSize, 0); - Raylib.DrawTextEx(Theme.Font, Label, .(X+Width/2-measure.x/2,Y+Height/2-measure.y/2), Theme.FontSize, 0, Theme.Text); + var measure = Raylib.MeasureTextEx(Theme.Font, Label.ToScopeCStr!(), Theme.FontSize, 0); + Raylib.DrawTextEx(Theme.Font, Label.ToScopeCStr!(), .(X+Width/2-measure.x/2,Y+Height/2-measure.y/2), Theme.FontSize, 0, Theme.Text); if(!Enabled) { diff --git a/src/Components/Placeholder.bf b/src/Components/Placeholder.bf new file mode 100644 index 0000000..bd85351 --- /dev/null +++ b/src/Components/Placeholder.bf @@ -0,0 +1,13 @@ +namespace TheaterGui.Components; + +class Placeholder : Component +{ + public this(int32 w, int32 h) : base("Placeholder", "Placeholder") + { + Width = w; + Height = h; + } + + public override Component OnHover(int32 x, int32 y) => null; + public override void Render() {} +} \ No newline at end of file diff --git a/src/Components/RadioButton.bf b/src/Components/RadioButton.bf new file mode 100644 index 0000000..448ca7e --- /dev/null +++ b/src/Components/RadioButton.bf @@ -0,0 +1,62 @@ +namespace TheaterGui.Components; + +using System; + +abstract class RadioButton : Container +{ + public int32 Checked = -1; + + public this(StringView pName) : base(pName, "RadioButtons") + { + Description = pName; + } + + public virtual void OnCheck(int32 value) + { + + } + + ///Set all of the fields to be used as checkboxes + public void SetBoxes(int32 selectedIndex, params Span args) + { + Checked = selectedIndex; + //Clear out old ones + this.[Friend]_LayoutData.ClearAndDeleteItems(); + Children.Clear(); + + for(var i in args) + { + var toAdd = new RadioCheckbox(i); + toAdd.Description = Description; + if(@i.Index == selectedIndex) + toAdd.Checked = true; + AddChild(toAdd); + EndRow(); + } + } + + private void SelectButton(RadioCheckbox toSelect) + { + for(var i in Children) + if(i == toSelect) + { + ((RadioCheckbox)i).Checked = true; + Checked = (.)@i.Index; + OnCheck((.)@i.Index); + } + else + ((RadioCheckbox)i).Checked = false; + } + + private class RadioCheckbox : Checkbox + { + public this(StringView pName) : base(pName) + { + Margin = 2; + } + public override void OnCheck(bool checkValue) + { + ((RadioButton)Parent).SelectButton(this); + } + } +} \ No newline at end of file diff --git a/src/Controls/Screen.bf b/src/Components/Screen.bf similarity index 91% rename from src/Controls/Screen.bf rename to src/Components/Screen.bf index a28442e..b1df64a 100644 --- a/src/Controls/Screen.bf +++ b/src/Components/Screen.bf @@ -1,4 +1,4 @@ -namespace TheaterGui.Controls; +namespace TheaterGui.Components; using TheaterGui; using System; diff --git a/src/Components/Toolbar.bf b/src/Components/Toolbar.bf new file mode 100644 index 0000000..95301c4 --- /dev/null +++ b/src/Components/Toolbar.bf @@ -0,0 +1,142 @@ +namespace TheaterGui.Components; + +using System; +using System.Collections; +using RaylibBeef; + +class Toolbar : Component +{ + public NPatchInfo PatchInfo; + public List Categories = new .() ~ DeleteContainerAndItems!(_); + private ToolbarPopup _CurrentPopup = null; + private ToolbarCategory _HoveredPopup = null; + + + + public this(StringView pLabel) : base("Toolbar", pLabel) + { + Height = 24; + Sprite = App.Textures.GetAsset("horizontal_patch"); + PatchInfo =.(Sprite.SourceRect, 2,2,2,2,(.)NPatchLayout.NPATCH_NINE_PATCH); + + Categories.Add(new .("File", new .("Load file",null), new .("Save file",null), new .("Recent Files",null))); + Categories.Add(new .("Edit", new .("Revert",null), new .("Really long thing goes here",null))); + Categories.Add(new .("Secret third thing", new .("Load file",null), new .("Save file",null), new .("Recent Files",null))); + } + + public override void Render() + { + Raylib.DrawTextureNPatch( + *Sprite.Source, + PatchInfo, + .(X, Y, Width, Height), + .(0,0), + 0, + Tint); + int32 x = 5; + for(var i in Categories) + { + Raylib.DrawTextEx(Theme.Font, scope String(scope $"{i.Name}"), .(X + x, Y + Height/2 - i.NameMeasurement.y/2), Theme.FontSize, 0, Theme.Text); + if(i == _HoveredPopup) + { + //5 padding in every direction + Raylib.DrawTextureNPatch( + *Sprite.Source, + PatchInfo, + .(X + x-5, Y, i.NameMeasurement.x+10, Height), + .(0,0), + 0, + HoverTint); + } + + x += (.)i.NameMeasurement.x + 10; + } + } + + public override bool OnClick(int32 x, int32 y) + { + int32 hor = 10; + for(var i in Categories) + { + if(x >= hor && x <= hor + i.NameMeasurement.x) + { + var popup = new ToolbarPopup(hor-10,Height, i); + App.OpenPopup(popup); + _CurrentPopup = popup; + return true; + } + hor += (.)i.NameMeasurement.x + 10; + } + return false; + } + + public override Component OnHover(int32 x, int32 y) + { + int32 hor = 10; + for(var i in Categories) + { + if(x >= hor && x <= hor + i.NameMeasurement.x) + { + _HoveredPopup = i; + + if(_CurrentPopup != null && _CurrentPopup.Category != i) + { + _CurrentPopup.Category = i; + _CurrentPopup.[Friend]X = hor-10; + } + return this; + } + hor += (.)i.NameMeasurement.x + 10; + } + _HoveredPopup = null; + + return null; + } + + public class ToolbarCategory + { + private String _Name = null ~ delete _; + public Vector2 NameMeasurement = .(0,0); + public StringView Name + { + public get => (_Name != null) ? _Name : default; + public set + { + String.NewOrSet!(_Name, value); + NameMeasurement = Raylib.MeasureTextEx(Theme.Font, _Name, Theme.FontSize, 0); + } + } + + public this(StringView pName, params Span pItems) + { + Name = pName; + for(var i in pItems) + Items.Add(i); + } + + public class ToolbarItem + { + private String _Name = null ~ delete _; + public Vector2 NameMeasurement = .(0,0); + public StringView Name + { + public get => (_Name != null) ? _Name : default; + public set + { + String.NewOrSet!(_Name, value); + NameMeasurement = Raylib.MeasureTextEx(Theme.Font, _Name, Theme.FontSize, 0); + } + } + + public this(StringView pName, delegate void() pClickAction) + { + Name = pName; + ClickAction = pClickAction; + } + + public delegate void() ClickAction = null ~ delete _; + } + + public List Items = new .() ~ DeleteContainerAndItems!(_); + } +} \ No newline at end of file diff --git a/src/Components/ToolbarPopup.bf b/src/Components/ToolbarPopup.bf new file mode 100644 index 0000000..3b453f1 --- /dev/null +++ b/src/Components/ToolbarPopup.bf @@ -0,0 +1,57 @@ +namespace TheaterGui.Components; + +using System; +using RaylibBeef; + +class ToolbarPopup : Component +{ + public NPatchInfo PatchInfo; + + private Toolbar.ToolbarCategory _Category = null; + public Toolbar.ToolbarCategory Category + { + get => _Category; + set + { + _Category = value; + int32 maxWidth = 0; + for(var i in _Category.Items) + if(maxWidth <= i.NameMeasurement.x) + maxWidth = (.)i.NameMeasurement.x; + + Width = maxWidth + 10 + 32; + Height = (.)(5 +((_Category.NameMeasurement.y + 5) * _Category.Items.Count)); + } + } + + public this(int32 x, int32 y, Toolbar.ToolbarCategory category) : base("ToolbarPopup", category.Name) + { + this.[Friend]X = x; + this.[Friend]Y = y-1; + Sprite = App.Textures.GetAsset("horizontal_patch"); + PatchInfo =.(Sprite.SourceRect, 2,2,2,2,(.)NPatchLayout.NPATCH_NINE_PATCH); + Category = category; + } + + public override void Render() + { + Raylib.DrawTextureNPatch( + *Sprite.Source, + PatchInfo, + .(X, Y, Width, Height), + .(0,0), + 0, + Tint); + int32 y = 5; + for(var i in Category.Items) + { + Raylib.DrawTextEx(Theme.Font, scope String(i.Name), .(X + 5 + 32, Y + y), Theme.FontSize, 0, Theme.Text); + y += (.)i.NameMeasurement.y+5; + } + } + + public override Component OnHover(int32 x, int32 y) + { + return default; + } +} \ No newline at end of file diff --git a/src/Controls/Checkbox.bf b/src/Controls/Checkbox.bf deleted file mode 100644 index 54bca34..0000000 --- a/src/Controls/Checkbox.bf +++ /dev/null @@ -1,21 +0,0 @@ -namespace TheaterGui.Controls; - -using System; - -class Checkbox : GuiObject -{ - public sprite Sprite; - public sprite SpriteChecked; - - public override GuiObject OnHover(int32 x, int32 y) - { - return null; - } - - public this(StringView pName) : base("Checkbox", pName) - { - Sprite = App.Textures.GetAsset("checkbox"); - SpriteChecked = App.Textures.GetAsset("checkbox_checked"); - Label.Append(pName); - } -} \ No newline at end of file diff --git a/src/Controls/GuiObject.bf b/src/Controls/GuiObject.bf deleted file mode 100644 index 5ca29d3..0000000 --- a/src/Controls/GuiObject.bf +++ /dev/null @@ -1,109 +0,0 @@ -namespace TheaterGui.Controls; - -using System; -using System.Collections; -using RaylibBeef; - -abstract class GuiObject -{ - public String ComponentName = new .() ~ delete _; - public String Name = new .() ~ delete _; - public String Label = new .() ~ delete _; - public String Description = new .() ~ delete _; - public Color Tint = Theme.Tint; - public Color HoverTint = Theme.HoverTint; - public bool Enabled = true; - - public this(StringView pComponentName, StringView pName) - { - ComponentName.Append(pComponentName); - Name.Append(pName); - } - - private Container _Parent = null; - public Container Parent - { - get => _Parent; - set - { - OnParentChange(value); - _Parent = value; - } - }; - - ///Margin stuff - public int32 MarginTop = 0; - public int32 MarginBottom = 0; - public int32 MarginLeft = 0; - public int32 MarginRight = 0; - public int32 Margin - { - //There seems to be no reasonable way to implement a getter here - set - { - MarginTop = value; - MarginBottom = value; - MarginLeft = value; - MarginRight = value; - } - }; - - public int32 X - { - get; - set; - } = 0; - public int32 Y - { - get; - set; - } = 0; - public int32 Width - { - public get; - protected set; - }; - public int32 Height - { - public get; - protected set; - }; - public bool Selected - { - get; - set - { - Selected = value; - App.Selected = this; - } - } - - public Vector2 SizeOf - { - get => .(Width + MarginLeft + MarginRight, Height + MarginTop + MarginBottom); - } - - ///Returns true if the onclick has been handled - public virtual bool OnClick(int32 x, int32 y) - { - return false; - } - - public abstract GuiObject OnHover(int32 x, int32 y); - - //Called every frame if the object is selected - public virtual void WhileSelected() - { - - } - - public virtual void OnParentChange(Container pNewParent) - { - - } - - public virtual void Render() - { - - } -} \ No newline at end of file diff --git a/src/Controls/Label.bf b/src/Controls/Label.bf deleted file mode 100644 index 48f9c35..0000000 --- a/src/Controls/Label.bf +++ /dev/null @@ -1,28 +0,0 @@ -namespace TheaterGui.Controls; - -using System; -using RaylibBeef; - -class Label : GuiObject -{ - private Vector2 _Measure; - public this(StringView pName) : base(Label, pName) - { - Label.Append(pName); - Margin = 3; - _Measure = Raylib.MeasureTextEx(Theme.Font, Label, Theme.FontSize, 0); - Height = (.)_Measure.y; - Width = (.)_Measure.x; - } - - public override GuiObject OnHover(int32 x, int32 y) - { - return null; - } - - public override void Render() - { - Raylib.DrawTextEx(Theme.Font, Label, .(X,Y), Theme.FontSize, 0, Theme.Text); - Raylib.DrawLine(X,Y + (.)_Measure.y,X + (.)_Measure.x, Y + (.)_Measure.y, Theme.Text); - } -} \ No newline at end of file diff --git a/src/Controls/Placeholder.bf b/src/Controls/Placeholder.bf deleted file mode 100644 index 4d1525d..0000000 --- a/src/Controls/Placeholder.bf +++ /dev/null @@ -1,12 +0,0 @@ -namespace TheaterGui.Controls; - -class Placeholder : GuiObject -{ - public this(int32 w, int32 h) : base("Placeholder", "Placeholder") - { - Width = w; - Height = h; - } - - public override GuiObject OnHover(int32 x, int32 y) => null; -} \ No newline at end of file diff --git a/src/Extensions.bf b/src/Extensions.bf index eae7eea..66de043 100644 --- a/src/Extensions.bf +++ b/src/Extensions.bf @@ -1,15 +1,34 @@ -namespace TheaterGui; - namespace RaylibBeef { extension Rectangle { public bool Overlaps(int32 x, int32 y) { - if(x >= this.x && x <= this.x + this.width) - if(y >= this.y && y <= this.y + this.height) + if (x >= this.x && x <= this.x + this.width) + if (y >= this.y && y <= this.y + this.height) return true; return false; } } +} + +namespace System +{ + using System.Reflection; + + [AttributeUsage(.Field)] + struct DynamicStringAttribute : Attribute, IOnFieldInit + { + [Comptime] + public void OnFieldInit(FieldInfo fieldInfo, Self* prev) + { + Compiler.EmitTypeBody(fieldInfo.DeclaringType, scope $""" + public StringView {fieldInfo.Name.Substring(1)} + {{ + public get => {fieldInfo.Name}; + public set => String.NewOrSet!({fieldInfo.Name}, value); + }}; + """); + } + } } \ No newline at end of file diff --git a/src/Generators/ButtonGenerator.bf b/src/Generators/ButtonGenerator.bf index 61562e1..e810ea1 100644 --- a/src/Generators/ButtonGenerator.bf +++ b/src/Generators/ButtonGenerator.bf @@ -34,7 +34,7 @@ class ButtonGenerator : Compiler.Generator public this() : base("{name}") {{ {!enabled ? "Enabled = false;" : ""} - Description.Append("{description}"); + Description = "{description}"; }} //What happens when the button is clicked diff --git a/src/Generators/CheckboxGenerator.bf b/src/Generators/CheckboxGenerator.bf new file mode 100644 index 0000000..bb33d42 --- /dev/null +++ b/src/Generators/CheckboxGenerator.bf @@ -0,0 +1,48 @@ +namespace TheaterGui.Generators; + +using System; + +class CheckboxGenerator : Compiler.Generator +{ + public override String Name => "TheaterGui -> Generate Checkbox" + + public override void InitUI() + { + AddEdit("name", "Checkbox Name", ""); + AddCheckbox("enabled", "Enabled", true); + AddEdit("description", "Description", ""); + } + + public override void Generate(String outFileName, String outText, ref Flags generateFlags) + { + var name = mParams["name"]; + if (name.EndsWith(".bf", .OrdinalIgnoreCase)) + name.RemoveFromEnd(3); + + var enabled = bool.Parse(mParams["enabled"]); + var description = mParams["description"]; + + outFileName.Append(scope $"TG{name}"); + + outText.Append(scope $""" + namespace {Namespace}; + + using TheaterGui.Controls; + + class TG{name} : Checkbox + {{ + public this() : base("{name}") + {{ + {!enabled ? "Enabled = false;" : ""} + Description = "{description}"; + }} + + //What happens when the button is clicked + public override void OnCheck(bool checkValue) + {{ + + }} + }} + """); + } +} \ No newline at end of file diff --git a/src/Generators/IconButtonGenerator.bf b/src/Generators/IconButtonGenerator.bf index 924a9bb..1d05d68 100644 --- a/src/Generators/IconButtonGenerator.bf +++ b/src/Generators/IconButtonGenerator.bf @@ -35,7 +35,7 @@ class IconButtonGenerator : Compiler.Generator {{ Icon = .Icon_Folder; //Use this to set the icon sprite {!enabled ? "Enabled = false;" : ""} - Description.Append("{description}"); + Description = "{description}"; }} //What happens when the button is clicked diff --git a/src/Generators/LargeButtonGenerator.bf b/src/Generators/LargeButtonGenerator.bf index 02c4736..a520eed 100644 --- a/src/Generators/LargeButtonGenerator.bf +++ b/src/Generators/LargeButtonGenerator.bf @@ -34,7 +34,7 @@ class LargeButtonGenerator : Compiler.Generator public this() : base("{name}") {{ {!enabled ? "Enabled = false;" : ""} - Description.Append("{description}"); + Description = "{description}"; }} //What happens when the button is clicked diff --git a/src/Generators/NButtonGenerator.bf b/src/Generators/NButtonGenerator.bf index 392b573..10043b5 100644 --- a/src/Generators/NButtonGenerator.bf +++ b/src/Generators/NButtonGenerator.bf @@ -34,7 +34,7 @@ class NButtonGenerator : Compiler.Generator public this() : base("{name}") {{ {!enabled ? "Enabled = false;" : ""} - Description.Append("{description}"); + Description = "{description}"; }} //What happens when the button is clicked diff --git a/src/Generators/RadioButtonGenerator.bf b/src/Generators/RadioButtonGenerator.bf new file mode 100644 index 0000000..cdbee98 --- /dev/null +++ b/src/Generators/RadioButtonGenerator.bf @@ -0,0 +1,48 @@ +namespace TheaterGui.Generators; + +using System; + +class RadioButtonGenerator : Compiler.Generator +{ + public override String Name => "TheaterGui -> Generate Radio Button" + + public override void InitUI() + { + AddEdit("name", "ComponentName Name", ""); + AddCheckbox("enabled", "Enabled", true); + AddEdit("description", "Description", ""); + } + + public override void Generate(String outFileName, String outText, ref Flags generateFlags) + { + var name = mParams["name"]; + if (name.EndsWith(".bf", .OrdinalIgnoreCase)) + name.RemoveFromEnd(3); + + var enabled = bool.Parse(mParams["enabled"]); + var description = mParams["description"]; + + outFileName.Append(scope $"TG{name}"); + + outText.Append(scope $""" + namespace {Namespace}; + + using TheaterGui.Controls; + + class TG{name} : RadioButton + {{ + public this() : base("{name}") + {{ + {!enabled ? "Enabled = false;" : ""} + Description = "{description}"; + //Use SetBoxes(int, params StringView) to set the elements of the radio buttons + }} + + //React to the check event, or use the Checked field to get the value directly + public override void OnCheck(int32 value) + {{ + }} + }} + """); + } +} \ No newline at end of file diff --git a/src/PlatformLayer.bf b/src/PlatformLayer.bf new file mode 100644 index 0000000..9680f25 --- /dev/null +++ b/src/PlatformLayer.bf @@ -0,0 +1,21 @@ +namespace TheaterGui; + +//Which backend does theatergui use +#define BACKEND_RAYLIB + + + +#if BACKEND_RAYLIB +using RaylibBeef; +#endif + +class PlatformLayer +{ + /// + public static void DrawSprite() + { + + } +} + +//TODO: Actually implement this \ No newline at end of file diff --git a/src/Textures.bf b/src/Textures.bf index 98aacf6..080cb7c 100644 --- a/src/Textures.bf +++ b/src/Textures.bf @@ -17,6 +17,8 @@ class Textures LoadAsset(Theme.Texture_LargeButton, "large_button"); LoadAsset(Theme.Texture_Checkbox, "checkbox"); LoadAsset(Theme.Texture_Checkbox_Checked, "checkbox_checked"); + LoadAsset(Theme.Horizontal_Patch, "horizontal_patch"); + LoadAsset(Theme.Texture_TheaterIcon, "theater_icon"); LoadAsset(Theme.Texture_FolderIcon, "folder_icon"); diff --git a/src/Theme.bf b/src/Theme.bf index a424835..c1d131c 100644 --- a/src/Theme.bf +++ b/src/Theme.bf @@ -6,7 +6,12 @@ using RaylibBeef; public class Theme { public static Font Font ~ Raylib.UnloadFont(_); + public static Font FontSmall ~ Raylib.UnloadFont(_); + public static Font FontLarge ~ Raylib.UnloadFont(_); + public static int32 FontSize = 16; + public static int32 FontSizeLarge = 20; + public static int32 FontSizeSmall = 12; public static Color Background = .(45, 45, 49, 255); public static Color Tint = .(153, 36, 72, 255); @@ -29,5 +34,7 @@ public class Theme public static uint8[?] Texture_Button = Compiler.ReadBinary("assets/button.png"); public static uint8[?] Texture_Checkbox = Compiler.ReadBinary("assets/checkbox.png"); public static uint8[?] Texture_Checkbox_Checked = Compiler.ReadBinary("assets/checkbox_checked.png"); + public static uint8[?] Horizontal_Patch = Compiler.ReadBinary("assets/horizontal_patch.png"); + }