diff --git a/Paper/Paper.csproj b/Paper/Paper.csproj index a6530e9..6975884 100644 --- a/Paper/Paper.csproj +++ b/Paper/Paper.csproj @@ -31,7 +31,7 @@ - + diff --git a/Samples/RaylibSample/RaylibCanvasRenderer.cs b/Samples/RaylibSample/RaylibCanvasRenderer.cs index 91b53e3..23a52fa 100644 --- a/Samples/RaylibSample/RaylibCanvasRenderer.cs +++ b/Samples/RaylibSample/RaylibCanvasRenderer.cs @@ -188,7 +188,7 @@ public object CreateTexture(uint width, uint height) { var image = GenImageColor((int)width, (int)height, new Color(0, 0, 0, 0)); var texture = LoadTextureFromImage(image); - SetTextureFilter(texture, TextureFilter.Point); + SetTextureFilter(texture, TextureFilter.Bilinear); return texture; } diff --git a/Samples/Shared/PaperDemo.Components.cs b/Samples/Shared/PaperDemo.Components.cs index 03845db..9817f3b 100644 --- a/Samples/Shared/PaperDemo.Components.cs +++ b/Samples/Shared/PaperDemo.Components.cs @@ -21,119 +21,30 @@ public static class Button { public static void DefineStyles() { - // Primary button variant - PaperDemo.Gui.CreateStyleFamily("shadcs-button-primary") - .Base(new StyleTemplate() - .Height(40) - .Rounded(8) - .BackgroundColor(Themes.primaryColor) - .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.ScaleX, 0.1) - .Transition(GuiProp.ScaleY, 0.1)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(150, Themes.primaryColor))) - .Active(new StyleTemplate() - .Scale(0.95) - .BackgroundColor(Color.FromArgb(200, Themes.primaryColor))) - .Register(); - // Secondary button variant PaperDemo.Gui.CreateStyleFamily("shadcs-button-secondary") .Base(new StyleTemplate() .Height(40) .Rounded(8) - .BackgroundColor(Themes.secondaryColor) + .BackgroundColor(Themes.base300) .Transition(GuiProp.BackgroundColor, 0.2) .Transition(GuiProp.ScaleX, 0.1) .Transition(GuiProp.ScaleY, 0.1)) .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(150, Themes.secondaryColor))) + .BackgroundColor(Color.FromArgb(150, Themes.base300))) .Active(new StyleTemplate() .Scale(0.95) - .BackgroundColor(Color.FromArgb(200, Themes.primaryColor))) - .Register(); - - // Outline button variant - PaperDemo.Gui.CreateStyleFamily("shadcs-button-outline") - .Base(new StyleTemplate() - .Height(40) - .Rounded(8) - .BackgroundColor(Themes.backgroundColor) - .BorderColor(Themes.secondaryColor) - .BorderWidth(1) - .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.ScaleX, 0.1) - .Transition(GuiProp.ScaleY, 0.1)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(150, Themes.backgroundColor))) - .Active(new StyleTemplate() - .Scale(0.95) - .BackgroundColor(Color.FromArgb(200, Themes.primaryColor))) - .Register(); - - // Icon button styles - PaperDemo.Gui.CreateStyleFamily("shadcs-icon-button-primary") - .Base(new StyleTemplate() - .Width(40) - .Height(40) - .Rounded(8) - .BackgroundColor(Themes.primaryColor) - // .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.Rounded, 0.2)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(100, Themes.primaryColor)) - .Rounded(20)) - .Register(); - - // Icon button styles secondary - PaperDemo.Gui.CreateStyleFamily("shadcs-icon-button-secondary") - .Base(new StyleTemplate() - .Width(40) - .Height(40) - .Rounded(8) - .BackgroundColor(Themes.secondaryColor) - // .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.Rounded, 0.2)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(100, Themes.secondaryColor)) - .Rounded(20)) + .BackgroundColor(Color.FromArgb(200, Themes.primary))) .Register(); } - public static ElementBuilder Primary(string stringID, string text = "", int intID = 0, [CallerLineNumber] int lineID = 0) - { - return PaperDemo.Gui.Box(stringID, intID, lineID) - .Text(text, Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleCenter) - .Style("shadcs-button-primary"); - } public static ElementBuilder Secondary(string stringID, string text = "", int intID = 0, [CallerLineNumber] int lineID = 0) { return PaperDemo.Gui.Box(stringID, intID, lineID) - .Text(text, Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter) + .Text(text, Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleCenter) .Style("shadcs-button-secondary"); } - - public static ElementBuilder Outline(string stringID, string text = "", int intID = 0, [CallerLineNumber] int lineID = 0) - { - return PaperDemo.Gui.Box(stringID, intID, lineID) - .Text(text, Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter) - .Style("shadcs-button-outline"); - } - - public static ElementBuilder IconPrimary(string stringID, string text = "", int intID = 0, [CallerLineNumber] int lineID = 0) - { - return PaperDemo.Gui.Box(stringID, intID, lineID) - .Text(text, Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleCenter) - .Style("shadcs-icon-button-primary"); - } - - public static ElementBuilder IconSecondary(string stringID, string text = "", int intID = 0, [CallerLineNumber] int lineID = 0) - { - return PaperDemo.Gui.Box(stringID, intID, lineID) - .Text(text, Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter) - .Style("shadcs-icon-button-secondary"); - } } public static class Input @@ -146,15 +57,15 @@ public static void DefineStyles() .Width(300) .Height(40) .Rounded(8) - .BackgroundColor(Themes.secondaryColor) + .BackgroundColor(Themes.base300) .BorderColor(Color.Transparent) .BorderWidth(0) .Transition(GuiProp.BorderColor, 0.2) .Transition(GuiProp.BorderWidth, 0.2)) .Focused(new StyleTemplate() - .BorderColor(Themes.primaryColor) + .BorderColor(Themes.primary) .BorderWidth(1) - .BackgroundColor(Themes.secondaryColor)) + .BackgroundColor(Themes.base300)) .Register(); } @@ -170,14 +81,9 @@ public static ElementBuilder Secondary(string stringID, string value, Action @@ -249,7 +155,7 @@ public static ElementBuilder Primary(string stringID, double sliderValue, Action .Width(PaperDemo.Gui.Percent(sliderValue * 100)) .MinWidth(10) .RoundedLeft(10) - .BackgroundColor(Themes.primaryColor) + .BackgroundColor(Themes.primary) //.Style(BoxStyle.SolidRounded(primaryColor, 10f)) .Enter()) { @@ -259,7 +165,7 @@ public static ElementBuilder Primary(string stringID, double sliderValue, Action .Width(20) .Height(20) .Rounded(10) - .BackgroundColor(Themes.textColor) + .BackgroundColor(Themes.primaryContent) //.Style(BoxStyle.SolidRounded(textColor, 10f)) .PositionType(PositionType.SelfDirected) .Enter()) { } @@ -268,40 +174,6 @@ public static ElementBuilder Primary(string stringID, double sliderValue, Action cleanup.Dispose(); return parent; - // // OLD - // using (PaperDemo.Gui.Box("SliderTrack") - // .Height(20) - // .BackgroundColor(Color.FromArgb(30, 0, 0, 0)) - // //.Style(BoxStyle.SolidRounded(Color.FromArgb(30, 0, 0, 0), 10f)) - // .Margin(0, 0, 20, 0) - // .OnHeld((e) => - // { - // double parentWidth = e.ElementRect.width; - // double pointerX = e.PointerPosition.x - e.ElementRect.x; - - // // Calculate new slider value based on pointer position - // sliderValue = Math.Clamp(pointerX / parentWidth, 0f, 1f); - // }) - // .Enter()) - // { - // // Filled part of slider - // using (Gui.Box("SliderFill") - // .Width(Gui.Percent(sliderValue * 100)) - // .BackgroundColor(Themes.primaryColor) - // //.Style(BoxStyle.SolidRounded(primaryColor, 10f)) - // .Enter()) - // { - // // Slider handle - // using (Gui.Box("SliderHandle") - // .Left(Gui.Percent(100, -10)) - // .Width(20) - // .Height(20) - // .BackgroundColor(Themes.textColor) - // //.Style(BoxStyle.SolidRounded(textColor, 10f)) - // .PositionType(PositionType.SelfDirected) - // .Enter()) { } - // } - // } } public static ElementBuilder Secondary(string stringID, double sliderValue, Action onChange, int intID = 0, [CallerLineNumber] int lineID = 0) @@ -309,7 +181,7 @@ public static ElementBuilder Secondary(string stringID, double sliderValue, Acti var parent = PaperDemo.Gui.Box(stringID, intID, lineID) .Height(20) .Rounded(10) - .BackgroundColor(Themes.backgroundColor) + .BackgroundColor(Themes.base100) //.Style(BoxStyle.SolidRounded(Color.FromArgb(30, 0, 0, 0), 10f)) .Margin(0, 0, 20, 0) .OnHeld((e) => @@ -329,7 +201,7 @@ public static ElementBuilder Secondary(string stringID, double sliderValue, Acti .Width(PaperDemo.Gui.Percent(sliderValue * 100)) .MinWidth(10) .RoundedLeft(10) - .BackgroundColor(Themes.secondaryColor) + .BackgroundColor(Themes.base300) //.Style(BoxStyle.SolidRounded(primaryColor, 10f)) .Enter()) { @@ -339,7 +211,7 @@ public static ElementBuilder Secondary(string stringID, double sliderValue, Acti .Width(20) .Height(20) .Rounded(10) - .BackgroundColor(Themes.textColor) + .BackgroundColor(Themes.primaryContent) //.Style(BoxStyle.SolidRounded(textColor, 10f)) .PositionType(PositionType.SelfDirected) .Enter()) { } @@ -366,10 +238,10 @@ public static void DefineStyles() .Register(); PaperDemo.Gui.RegisterStyle("toggle-on", new StyleTemplate() - .BackgroundColor(Themes.primaryColor)); + .BackgroundColor(Themes.primary)); PaperDemo.Gui.RegisterStyle("toggle-off", new StyleTemplate() - .BackgroundColor(Color.FromArgb(100, Themes.secondaryColor))); + .BackgroundColor(Color.FromArgb(100, Themes.base300))); PaperDemo.Gui.RegisterStyle("toggle-dot", new StyleTemplate() .Width(24) @@ -472,94 +344,9 @@ public static ElementBuilder Primary(string stringID, double[] values, double st vg.Circle(centerX, centerY, radius * 0.4f); vg.SetFillColor(Color.White); vg.Fill(); - - // Draw center text - // Draw center text - //vg.FillColor(textColor); - //vg.TextAlign(NvgSharp.Align.Center | NvgSharp.Align.Middle); - //vg.FontSize(20); - //vg.Text(centerX, centerY, $"Analytics\n{(sliderValue * 100):F0}%"); - //vg.Text(fontSmall, $"Analytics\n{(sliderValue * 100):F0}%", centerX, centerY); }); } return builder; } - - // // OLD - // // "Analysis" mock content - // using (Gui.Box("AnalyticsVisual") - // .Margin(20) - // .Enter()) - // { - // // Add a simple pie chart visualization - // Gui.AddActionElement((vg, rect) => { - // double centerX = rect.x + rect.width / 2; - // double centerY = rect.y + rect.height / 2; - // double radius = Math.Min(rect.width, rect.height) * 0.4f; - - // double startAngle = 0; - // double[] values = { sliderValue, 0.2f, 0.15f, 0.25f, 0.1f }; - - // // Normalize Values - // double total = values.Sum(); - // for (int i = 0; i < values.Length; i++) - // values[i] /= total; - - - // for (int i = 0; i < values.Length; i++) - // { - // // Calculate angles - // double angle = values[i] * Math.PI * 2; - // double endAngle = startAngle + angle; - - // // Draw pie slice - // vg.BeginPath(); - // vg.MoveTo(centerX, centerY); - // vg.Arc(centerX, centerY, radius, startAngle, endAngle); - // vg.LineTo(centerX, centerY); - // vg.SetFillColor(Themes.colorPalette[i % Themes.colorPalette.Length]); - // vg.Fill(); - - // // Draw outline - // vg.BeginPath(); - // vg.MoveTo(centerX, centerY); - // vg.Arc(centerX, centerY, radius, startAngle, endAngle); - // vg.LineTo(centerX, centerY); - // vg.SetStrokeColor(Color.White); - // vg.SetStrokeWidth(2); - // vg.Stroke(); - - // // Draw percentage labels - // double labelAngle = startAngle + angle / 2; - // double labelRadius = radius * 0.7f; - // double labelX = centerX + Math.Cos(labelAngle) * labelRadius; - // double labelY = centerY + Math.Sin(labelAngle) * labelRadius; - - // string label = $"{values[i] * 100:F0}%"; - // vg.SetFillColor(Color.White); - // //vg.TextAlign(Align.Center | Align.Middle); - // //vg.FontSize(16); - // //vg.Text(labelX, labelY, label); - // vg.DrawText(Fonts.fontSmall, label, labelX, labelY, Color.White); - - // // Move to next slice - // startAngle = endAngle; - // } - - // // Draw center circle - // vg.BeginPath(); - // vg.Circle(centerX, centerY, radius * 0.4f); - // vg.SetFillColor(Color.White); - // vg.Fill(); - - // // Draw center text - // // Draw center text - // //vg.FillColor(textColor); - // //vg.TextAlign(NvgSharp.Align.Center | NvgSharp.Align.Middle); - // //vg.FontSize(20); - // //vg.Text(centerX, centerY, $"Analytics\n{(sliderValue * 100):F0}%"); - // //vg.Text(fontSmall, $"Analytics\n{(sliderValue * 100):F0}%", centerX, centerY); - // }); - // } } -} +} \ No newline at end of file diff --git a/Samples/Shared/PaperDemo.New.cs b/Samples/Shared/PaperDemo.New.cs new file mode 100644 index 0000000..3b3cba4 --- /dev/null +++ b/Samples/Shared/PaperDemo.New.cs @@ -0,0 +1,156 @@ +using Prowl.PaperUI; + +namespace Shared +{ + public static partial class PaperDemo + { + + public static TabsManager tabsManager; + + public static Paper Gui; + static double value = 0; + + public static void Initialize(Paper paper) + { + Gui = paper; + Fonts.Initialize(Gui); + Themes.Initialize(); + + // create the tabs + tabsManager = new TabsManager(paper); + } + + public static void RenderUI() + { + using (Gui.Box("App").BackgroundColor(Themes.base100).Enter()) + { + using (Gui.Column("EditorContainer").Margin(8).Enter()) + { + TitleBarUI(); + + using (Gui.Row("3 Columns Editor Layout").RowBetween(6).Enter()) + { + using (Gui.Column("Left Panel").ColBetween(8).Width(250).Enter()) + { + using (WindowContainer("Scene Tree Window").Enter()) + { + tabsManager.DrawGroup(["hierarchy", "assets"]); + } + + using (WindowContainer("Files Window Container").Enter()) + { + tabsManager.DrawGroup(["files", "settings"]); + } + } + + using (Gui.Column("Center Panel").Enter()) + { + using (WindowContainer("Game and Scene Window").Enter()) + { + tabsManager.DrawGroup(["chart", "pong", "platformer", "slimefriends", "asteroids"]); + } + } + + using (Gui.Column("Right Panel").Width(250).Enter()) + { + using (WindowContainer("Inspector Window").Enter()) + { + tabsManager.DrawGroup(["inspector", "example"]); + } + } + } + } + } + } + + private static ElementBuilder WindowContainer(string id) + { + return Gui.Box(id) + .BackgroundColor(Themes.base100) + .Rounded(5) + .BorderColor(Themes.base200) + .BorderWidth(1); + } + + private static void TitleBarUI() + { + using (Gui.Row("Header").Height(28).Bottom(8).Enter()) + { + Gui.Box("tab 1") + .Width(80).Height(28) + .BackgroundColor(Themes.base100) + .Text(Icons.Hammer + " Bevy", Fonts.arial) + .TextColor(Themes.baseContent) + .Hovered + .BackgroundColor(Themes.base300) + .End() + .Rounded(5) + .Alignment(TextAlignment.MiddleCenter); + + Gui.Box("tab 2") + .Width(45).Height(28) + .BackgroundColor(Themes.base100) + .Text("File", Fonts.arial) + .TextColor(Themes.baseContent) + .Rounded(5) + .Hovered + .BackgroundColor(Themes.base300) + .End() + .Alignment(TextAlignment.MiddleCenter) + .Left(5); + + Gui.Box("tab 3") + .Width(45).Height(28) + .BackgroundColor(Themes.base100) + .Text("Edit", Fonts.arial) + .TextColor(Themes.baseContent) + .Rounded(5) + .Hovered + .BackgroundColor(Themes.base300) + .End() + .Alignment(TextAlignment.MiddleCenter) + .Left(5); + + Gui.Box("tab 4") + .Width(65).Height(28) + .BackgroundColor(Themes.base100) + .Text("Debug", Fonts.arial) + .TextColor(Themes.baseContent) + .Rounded(5) + .Hovered + .BackgroundColor(Themes.base300) + .End() + .Alignment(TextAlignment.MiddleCenter) + .Left(5); + + Gui.Box("Spacer"); + + Gui.Box("Play / Pause") + .Width(64).Height(28) + .BackgroundColor(Themes.base200) + .Text(Icons.Play + " " + Icons.Pause, Fonts.arial) + .TextColor(Themes.baseContent) + .Rounded(5) + .Hovered + .BackgroundColor(Themes.base250) + .End() + .Alignment(TextAlignment.MiddleCenter) + .Left(500); + } + } + + private static void ComponentDemo() + { + using (Gui.Column("EditorContainer").Margin(5).Enter()) + { + Button.Secondary("Primary Button").OnClick((_) => Console.WriteLine("Primary Button Clicked")).Margin(5); + Button.Secondary("Secondary Button").OnClick((_) => Console.WriteLine("Secondary Button Clicked")).Margin(5); + + TextArea.Secondary("TextArea", "This is a secondary text area. It can hold multiple lines of text and is styled differently from the primary text area.").Margin(5); + Slider.Primary("Slider 1", value, (v) => { value = v; }); + Slider.Secondary("Slider 1", value, (v) => { value = v; }); + PieChart.Primary("Pie Chart", new double[] { 10, 20, 30, 40 }, value).Height(200).Margin(5); + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/PaperDemo.cs b/Samples/Shared/PaperDemo.Old.cs similarity index 91% rename from Samples/Shared/PaperDemo.cs rename to Samples/Shared/PaperDemo.Old.cs index 649bd23..a572388 100644 --- a/Samples/Shared/PaperDemo.cs +++ b/Samples/Shared/PaperDemo.Old.cs @@ -8,7 +8,7 @@ namespace Shared { - public static partial class PaperDemo + public static partial class PaperDemoOld { // Track state for interactive elements static double sliderValue = 0.5f; @@ -44,7 +44,7 @@ public static void RenderUI() // Main container with light gray background using (Gui.Column("MainContainer") - .BackgroundColor(Themes.backgroundColor) + .BackgroundColor(Themes.base100) //.Style(BoxStyle.Solid(backgroundColor)) .Enter()) { @@ -158,12 +158,12 @@ private static void RenderTopNavBar() Gui.Box("LogoInner") .Size(50) .Margin(10) - .Text(Icons.Newspaper, Fonts.arial).FontSize(32).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter); + .Text(Icons.Newspaper, Fonts.arial).FontSize(32).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleCenter); Gui.Box("LogoText") .PositionType(PositionType.SelfDirected) .Left(50 + 15) - .Text("PaperUI Demo", Fonts.arial).FontSize(40).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft); + .Text("PaperUI Demo", Fonts.arial).FontSize(40).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft); } // Spacer @@ -174,17 +174,17 @@ private static void RenderTopNavBar() .Margin(0, 15, 15, 0); // Theme Switch - using icon-button style - Button.IconPrimary("LightIcon", Icons.Lightbulb) + Button.Secondary("LightIcon", Icons.Lightbulb) .Margin(0, 10, 15, 0) - .OnClick((rect) => Themes.ToggleTheme()); + .OnClick((rect) => {/*Themes.ToggleTheme() */}); // Notification icon - Button.IconSecondary("NotificationIcon", Icons.CircleExclamation) + Button.Secondary("NotificationIcon", Icons.CircleExclamation) .Margin(0, 10, 15, 0) .OnClick((rect) => Console.WriteLine("Notifications clicked")); // User Profile - Button.IconPrimary("UserProfile", "M") + Button.Secondary("UserProfile", "M") .Margin(0, 15, 15, 0) .OnClick((rect) => Console.WriteLine("Profile clicked")); } @@ -200,7 +200,7 @@ private static void RenderSidebar() // Menu header Gui.Box("MenuHeader") .Height(60) - .Text("Menu", Fonts.arial).FontSize(26).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleCenter); + .Text("Menu", Fonts.arial).FontSize(26).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleCenter); string[] menuIcons = { Icons.House, Icons.ChartBar, Icons.User, Icons.Gear, Icons.WindowMaximize }; string[] menuItems = { "Dashboard", "Analytics", "Users", "Settings" };//, "Windows" }; @@ -220,13 +220,13 @@ private static void RenderSidebar() Gui.Box("MenuItemIcon", i) .Width(55) .Height(50) - .Text(menuIcons[i], Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleCenter).FontSize(19); + .Text(menuIcons[i], Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleCenter).FontSize(19); Gui.Box("MenuItem", i) .Width(100) .PositionType(PositionType.SelfDirected) .Left(50 + 15) - .Text(menuItems[i], Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleCenter).FontSize(19); + .Text(menuItems[i], Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleCenter).FontSize(19); } } @@ -238,7 +238,7 @@ private static void RenderSidebar() .Margin(15) .Height(Gui.Auto) .Rounded(8) - .BackgroundColor(Themes.primaryColor) + .BackgroundColor(Themes.primary) .AspectRatio(0.5f) .Enter()) { @@ -254,7 +254,7 @@ private static void RenderSidebar() .Style("button") .Height(30) .BackgroundColor(Color.White) - .Text("Upgrade", Fonts.arial).TextColor(Themes.primaryColor).Alignment(TextAlignment.MiddleCenter).FontSize(19) + .Text("Upgrade", Fonts.arial).TextColor(Themes.primary).Alignment(TextAlignment.MiddleCenter).FontSize(19) .OnClick((rect) => Console.WriteLine("Upgrade clicked")); } } @@ -294,7 +294,7 @@ private static void RenderTabsNavigation() { int index = i; bool isSelected = i == selectedTabIndex; - Color tabColor = isSelected ? Themes.primaryColor : Themes.lightTextColor; + Color tabColor = isSelected ? Themes.primary : Themes.baseContent; double tabWidth = 1.0f / tabNames.Length; using (Gui.Box("Tab", i) @@ -309,7 +309,7 @@ private static void RenderTabsNavigation() { Gui.Box("TabIndicator", i) .Height(4) - .BackgroundColor(Themes.primaryColor) + .BackgroundColor(Themes.primary) .Rounded(2); } } @@ -356,10 +356,10 @@ private static void RenderDashboardTab() { Gui.Box("StatLabel", i) .Height(Gui.Pixels(25)) - .Text(statNames[i], Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleLeft).FontSize(19); + .Text(statNames[i], Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19); Gui.Box("StatValue", i) - .Text(statValues[i], Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(32); + .Text(statValues[i], Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(32); } } } @@ -374,7 +374,7 @@ private static void RenderDashboardTab() using (Gui.Box("ChartArea") .Width(Gui.Stretch(0.7f)) .Rounded(8) - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) //.Style(BoxStyle.SolidRounded(cardBackground, 8f)) .Enter()) { @@ -385,7 +385,7 @@ private static void RenderDashboardTab() .Enter()) { using (Gui.Box("ChartTitle") - .Text("Performance Overview", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(26) + .Text("Performance Overview", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(26) .Enter()) { } using (Gui.Row("ChartControls") @@ -400,12 +400,12 @@ private static void RenderDashboardTab() .Height(30) .Rounded(8) .Margin(5, 5, 0, 0) - .BackgroundColor(period == "Week" ? Themes.primaryColor : Color.FromArgb(50, 0, 0, 0)) + .BackgroundColor(period == "Week" ? Themes.primary : Color.FromArgb(50, 0, 0, 0)) .Hovered - .BackgroundColor(Color.FromArgb(50, Themes.primaryColor)) + .BackgroundColor(Color.FromArgb(50, Themes.primary)) .End() .Transition(GuiProp.BackgroundColor, 0.2f) - .Text(period, Fonts.arial).TextColor(period == "Week" ? Color.White : Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter).FontSize(19) + .Text(period, Fonts.arial).TextColor(period == "Week" ? Color.White : Themes.baseContent).Alignment(TextAlignment.MiddleCenter).FontSize(19) .OnClick((rect) => Console.WriteLine($"Period {period} clicked")) .Enter()) { } } @@ -435,7 +435,7 @@ private static void RenderDashboardTab() vg.BeginPath(); vg.MoveTo(rect.x, y); vg.LineTo(rect.x + rect.width, y); - vg.SetStrokeColor(Themes.lightTextColor); + vg.SetStrokeColor(Themes.baseContent); vg.SetStrokeWidth(1); vg.Stroke(); } @@ -475,7 +475,7 @@ private static void RenderDashboardTab() // Color.FromArgb(10, primaryColor)); //vg.SetFillPaint(paint); vg.SaveState(); - vg.SetLinearBrush(rect.x, rect.y, rect.x, rect.y + rect.height, Color.FromArgb(100, Themes.primaryColor), Color.FromArgb(10, Themes.primaryColor)); + vg.SetLinearBrush(rect.x, rect.y, rect.x, rect.y + rect.height, Color.FromArgb(100, Themes.primary), Color.FromArgb(10, Themes.primary)); vg.FillComplex(); vg.RestoreState(); @@ -498,7 +498,7 @@ private static void RenderDashboardTab() vg.LineTo(x, y); } - vg.SetStrokeColor(Themes.primaryColor); + vg.SetStrokeColor(Themes.primary); vg.SetStrokeWidth(3); vg.Stroke(); @@ -519,7 +519,7 @@ private static void RenderDashboardTab() vg.BeginPath(); vg.Circle(x, y, 4); - vg.SetFillColor(Themes.primaryColor); + vg.SetFillColor(Themes.primary); vg.Fill(); } @@ -537,7 +537,7 @@ private static void RenderDashboardTab() { // Activity panel using (Gui.Box("ActivityPanel") - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) //.Style(BoxStyle.SolidRounded(cardBackground, 8f)) .Rounded(8) .Enter()) @@ -546,7 +546,7 @@ private static void RenderDashboardTab() using (Gui.Box("PanelHeader") .Height(60) .Margin(20, 20, 20, 0) - .Text("Recent Activity", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(26) + .Text("Recent Activity", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(26) .Enter()) { } // Activity items @@ -587,12 +587,12 @@ private static void RenderDashboardTab() using (Gui.Box("ActivityText", i) .Height(Gui.Pixels(20)) .Margin(0, 0, 15, 0) - .Text(activities[i], Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(19) + .Text(activities[i], Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(19) .Enter()) { } using (Gui.Box("ActivityTime", i) .Height(Gui.Pixels(20)) - .Text(timestamps[i], Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleLeft).FontSize(19) + .Text(timestamps[i], Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19) .Enter()) { } } } @@ -614,7 +614,7 @@ private static void RenderAnalyticsTab() .Enter()) { using (Gui.Box("AnalyticsContent") - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) //.Style(BoxStyle.SolidRounded(cardBackground, 8f)) .Margin(0, 15 / 2, 15, 0) .Enter()) @@ -623,7 +623,7 @@ private static void RenderAnalyticsTab() using (Gui.Box("AnalyticsHeader") .Height(80) .Margin(20) - .Text("Analytics Dashboard", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(32) + .Text("Analytics Dashboard", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(32) .Enter()) { } // Interactive slider as a demo control @@ -634,7 +634,7 @@ private static void RenderAnalyticsTab() { using (Gui.Box("SliderLabel") .Height(30) - .Text($"Green Amount: {sliderValue:F2}", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(26) + .Text($"Green Amount: {sliderValue:F2}", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(26) .Enter()) { } // Slider control @@ -646,7 +646,7 @@ private static void RenderAnalyticsTab() } using (Gui.Box("ScrollTest") - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) //.Style(BoxStyle.SolidRounded(cardBackground, 8f)) .Margin(15 / 2, 0, 15, 0) .Enter()) @@ -706,7 +706,7 @@ private static void RenderAnalyticsTab() .Height(70) .Margin(10, 10, 5, 5) .BackgroundColor(Color.FromArgb(230, itemColor)) - .BorderColor(Themes.isDark ? Color.FromArgb(50, 255, 255, 255) : Color.FromArgb(50, 0, 0, 0)) + .BorderColor(Color.FromArgb(50, 255, 255, 255)) .BorderWidth(1) .Rounded(12) .Enter()) @@ -721,7 +721,7 @@ private static void RenderAnalyticsTab() .Height(50) .Rounded(25) .BackgroundColor(Color.FromArgb(60, 255, 255, 255)) - .Text(icon, Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleCenter).FontSize(26) + .Text(icon, Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleCenter).FontSize(26) .Enter()) { } // Content @@ -731,11 +731,11 @@ private static void RenderAnalyticsTab() { using (Gui.Box("CardTitle", i) .Height(25) - .Text($"Item {i}", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(26) + .Text($"Item {i}", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(26) .Enter()) { } using (Gui.Box("CardDescription", i) - .Text("Interactive card with animations", Fonts.arial).TextColor(Color.FromArgb(200, Themes.textColor)).Alignment(TextAlignment.MiddleLeft).FontSize(19) + .Text("Interactive card with animations", Fonts.arial).TextColor(Color.FromArgb(200, Themes.primaryContent)).Alignment(TextAlignment.MiddleLeft).FontSize(19) .Enter()) { } } } @@ -757,7 +757,7 @@ private static void RenderProfileTab() // Left panel - profile info using (Gui.Column("ProfileDetails") .Width(Gui.Stretch(0.4f)) - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) //.Style(BoxStyle.SolidRounded(cardBackground, 8f)) .Enter()) { @@ -779,7 +779,7 @@ private static void RenderProfileTab() using (Gui.Box("Avatar") .Width(120) .Height(120) - .BackgroundColor(Themes.secondaryColor) + .BackgroundColor(Themes.base300) //.Style(BoxStyle.SolidRounded(secondaryColor, 60f)) .Text("J", Fonts.arial).TextColor(Color.White).Alignment(TextAlignment.MiddleCenter).FontSize(48) .Enter()) { } @@ -792,13 +792,13 @@ private static void RenderProfileTab() // User name using (Gui.Box("UserName") .Height(40) - .Text("John Doe", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleCenter).FontSize(32) + .Text("John Doe", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleCenter).FontSize(32) .Enter()) { } // User title using (Gui.Box("UserTitle") .Height(30) - .Text("Senior Developer", Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter).FontSize(26) + .Text("Senior Developer", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleCenter).FontSize(26) .Enter()) { } } @@ -819,12 +819,12 @@ private static void RenderProfileTab() { using (Gui.Box("StatValue", i) .Height(40) - .Text(statValues[i], Fonts.arial).TextColor(Themes.primaryColor).Alignment(TextAlignment.MiddleCenter).FontSize(32) + .Text(statValues[i], Fonts.arial).TextColor(Themes.primary).Alignment(TextAlignment.MiddleCenter).FontSize(32) .Enter()) { } using (Gui.Box("StatLabel", i) .Height(30) - .Text(statLabels[i], Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter).FontSize(19) + .Text(statLabels[i], Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleCenter).FontSize(19) .Enter()) { } } } @@ -846,11 +846,11 @@ private static void RenderProfileTab() { using (Gui.Box("ContactLabel", i) .Width(100) - .Text(contactLabels[i] + ":", Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleLeft).FontSize(19) + .Text(contactLabels[i] + ":", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19) .Enter()) { } using (Gui.Box("ContactValue", i) - .Text(contactValues[i], Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(19) + .Text(contactValues[i], Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(19) .Enter()) { } } } @@ -866,7 +866,7 @@ private static void RenderProfileTab() // Activity tracker using (Gui.Box("ActivityTracker") .Height(Gui.Stretch(0.6f)) - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) //.Style(BoxStyle.SolidRounded(cardBackground, 8f)) .Enter()) { @@ -874,7 +874,7 @@ private static void RenderProfileTab() using (Gui.Box("ActivityHeader") .Height(60) .Margin(20, 20, 0, 0) - .Text("Activity Tracker", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(26) + .Text("Activity Tracker", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(26) .Enter()) { } // Week days @@ -887,7 +887,7 @@ private static void RenderProfileTab() foreach (var day in days) { using (Gui.Box("Day", day.GetHashCode()) - .Text(day, Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleCenter).FontSize(19) + .Text(day, Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleCenter).FontSize(19) .Enter()) { } } } @@ -924,7 +924,7 @@ private static void RenderProfileTab() // Apply color based on intensity int alpha = (int)(40 + value * 215); - vg.SetFillColor(Color.FromArgb(alpha, Themes.primaryColor)); + vg.SetFillColor(Color.FromArgb(alpha, Themes.primary)); vg.Fill(); } } @@ -935,7 +935,7 @@ private static void RenderProfileTab() // Skills section using (Gui.Box("SkillsSection") .Height(Gui.Stretch(0.4f)) - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) //.Style(BoxStyle.SolidRounded(cardBackground, 8f)) .Margin(0, 0, 15, 0) .Enter()) @@ -944,7 +944,7 @@ private static void RenderProfileTab() using (Gui.Box("SkillsHeader") .Height(20) .Margin(20, 20, 20, 0) - .Text("Skills", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(26) + .Text("Skills", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(26) .Enter()) { } // Skill bars @@ -965,7 +965,7 @@ private static void RenderProfileTab() // Skill label using (Gui.Box("SkillLabel", i) .Height(25) - .Text(skills[i], Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(19) + .Text(skills[i], Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(19) .Enter()) { } // Skill bar @@ -987,7 +987,7 @@ private static void RenderProfileTab() // Percentage label using (Gui.Box("SkillPercent", i) .Width(40) - .Text($"{animatedLevel * 100:F0}%", Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.Right).FontSize(19) + .Text($"{animatedLevel * 100:F0}%", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.Right).FontSize(19) .Enter()) { } } } @@ -1029,10 +1029,10 @@ private static void RenderSettingsTab() for (int i = 0; i < categories.Length; i++) { bool isSelected = i == 0; - Color itemTextColor = isSelected ? Themes.primaryColor : Themes.textColor; + Color itemTextColor = isSelected ? Themes.primary : Themes.primaryContent; var index = i; - Button.Outline("SettingsCat", $" {categories[i]}", i) + Button.Secondary("SettingsCat", $" {categories[i]}", i) .Margin(10, 10, 5, 5) // .StyleIf(isSelected, "period-button-selected") .OnClick((rect) => { Console.WriteLine($"Category {categories[index]} clicked"); }); @@ -1049,7 +1049,7 @@ private static void RenderSettingsTab() Gui.Box("SettingsHeader") .Height(80) .Margin(20) - .Text("General Settings", Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(32); + .Text("General Settings", Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(32); // Toggle options - much cleaner now! string[] options = { @@ -1066,7 +1066,7 @@ private static void RenderSettingsTab() { // Option label Gui.Box("SettingLabel", i) - .Text(options[i], Fonts.arial).TextColor(Themes.textColor).Alignment(TextAlignment.MiddleLeft).FontSize(26); + .Text(options[i], Fonts.arial).TextColor(Themes.primaryContent).Alignment(TextAlignment.MiddleLeft).FontSize(26); bool isOn = toggleState[i]; int index = i; @@ -1127,20 +1127,20 @@ private static void RenderFooter() using (Gui.Row("Footer") .Height(50) .Rounded(8) - .BackgroundColor(Themes.cardBackground) + .BackgroundColor(Themes.base200) .Margin(15, 15, 0, 15) .Enter()) { // Copyright using (Gui.Box("Copyright") .Margin(15, 0, 0, 0) - .Text("© 2025 PaperUI Demo.", Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleLeft).FontSize(19) + .Text("© 2025 PaperUI Demo.", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19) .Enter()) { } // FPS Counter - Gui.Box("FPS").Text($"FPS: {1f / Gui.DeltaTime:F1}", Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleLeft).FontSize(19); - Gui.Box("NodeCounter").Text($"Nodes: {Gui.CountOfAllElements}", Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleLeft).FontSize(19); - Gui.Box("MS").Text($"Frame ms: {Gui.MillisecondsSpent}", Fonts.arial).TextColor(Themes.lightTextColor).Alignment(TextAlignment.MiddleLeft).FontSize(19); + Gui.Box("FPS").Text($"FPS: {1f / Gui.DeltaTime:F1}", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19); + Gui.Box("NodeCounter").Text($"Nodes: {Gui.CountOfAllElements}", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19); + Gui.Box("MS").Text($"Frame ms: {Gui.MillisecondsSpent}", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19); // Footer links string[] links = { "Terms", "Privacy", "Contact", "Help" }; @@ -1151,7 +1151,7 @@ private static void RenderFooter() { using (Gui.Box("Link", link.GetHashCode()) .Width(Gui.Stretch(1f / links.Length)) - .Text(link, Fonts.arial).TextColor(Themes.primaryColor).Alignment(TextAlignment.MiddleCenter).FontSize(19) + .Text(link, Fonts.arial).TextColor(Themes.primary).Alignment(TextAlignment.MiddleCenter).FontSize(19) .OnClick((rect) => Console.WriteLine($"Link {link} clicked")) .Enter()) { } } diff --git a/Samples/Shared/PaperDemo.Tabs.cs b/Samples/Shared/PaperDemo.Tabs.cs new file mode 100644 index 0000000..f6875a2 --- /dev/null +++ b/Samples/Shared/PaperDemo.Tabs.cs @@ -0,0 +1,197 @@ +using System.Formats.Asn1; + +using Prowl.PaperUI; +using Prowl.PaperUI.LayoutEngine; + +using Shared.Tabs; + +namespace Shared +{ + public abstract class Tab + { + public string id; + public string title; + public double width; + protected Paper Gui; + + public Tab(Paper gui) + { + this.Gui = gui; + } + + public virtual void Draw() + { + Gui.Box("Tab Title").Text("[" + this.title + " Tab]", Fonts.arial) + .TextColor(Themes.baseContent) + .Left(8) + .Alignment(TextAlignment.MiddleCenter); + } + + /// + /// When the tab becomes focused + /// + public virtual void Focus() + { + Console.WriteLine("FOCUS " + GetType().ToString()); + } + + /// + /// When the tab loses focus + /// + public virtual void Blur() + { + Console.WriteLine("BLUR " + GetType().ToString()); + } + } + + public class TabsManager + { + public string ActiveTabId = ""; + public Dictionary Entries = new Dictionary(); + + private Paper Gui; + private Tab FocusedTab; + + public TabsManager(Paper gui) + { + this.Gui = gui; + + AddTab("assets", new AssetsTab(gui)); + AddTab("files", new FilesTab(gui)); + AddTab("chart", new ChartTab(gui)); + AddTab("hierarchy", new HierarchyTab(gui)); + AddTab("inspector", new InspectorTab(gui)); + AddTab("settings", new SettingsTab(gui)); + AddTab("example", new ExampleTab(gui)); + AddTab("pong", new PongTab(gui)); + AddTab("platformer", new PlatformerTab(gui)); + AddTab("slimefriends", new SlimeFriendsTab(gui)); + AddTab("asteroids", new AsteroidsTab(gui)); + } + + public void AddTab(string id, Tab tab) + { + Entries.Add(id, tab); + ActiveTabId = id; + } + + public void DrawGroup(string[] tabs) + { + var group = Entries.Values.Where((tab, i) => tabs.Contains(tab.id)).ToArray(); + + // Get stored active tab ID or use first tab as default + var storageKey = string.Join("_", tabs) + "_activeTabId"; // auto invalidate the key if the group changes + var localGroupTabId = Gui.GetElementStorage(storageKey, tabs[0]); + + var currentElement = Gui.CurrentParent; + + TabsDisplay("Tabs Container", group, storageKey, currentElement, localGroupTabId); + + if (tabs.Length == 0 || group.Length == 0) return; + + // If active tab exists in current group, draw its body + if (Entries.TryGetValue(localGroupTabId, out Tab tab)) + { + using (Gui.Column("Body") + .OnPress((_) => + { + ActiveTabId = tab.id; + Console.WriteLine("Pressed " + tab.GetType().ToString()); + }) + .OnEnter((_) => + { + // blur old focused tab + // then call focus on the new focused tab + FocusedTab?.Blur(); + FocusedTab = tab; + FocusedTab.Focus(); + }) + .BackgroundColor(Themes.base200) + .Enter()) + { + tab.Draw(); + } + } + } + + private void TabsDisplay(string id, Tab[] tabs, string tabIdStorageKey, ElementHandle elementWithTabIdStorage, string localGroupId) + { + using (Gui.Row("Tabs " + id).Height(28).Enter()) + { + foreach (var tab in tabs) + { + if (tab.id == localGroupId) + { + using (Gui.Box("Active Tab" + tab.id).Width(tab.width).Height(28).Left(5) + .OnClick((_) => + { + Console.WriteLine("clicked tab " + tab.id); + ActiveTabId = tab.id; + Gui.SetElementStorage(elementWithTabIdStorage, tabIdStorageKey, tab.id); + }) + .Enter()) + { + if (tab.id == ActiveTabId) + { + Gui.Box("Highlight").Layer(Layer.Overlay).PositionType(PositionType.SelfDirected).RoundedTop(3).BackgroundColor(Themes.primary).Height(3); + } + + Gui.Box("tab 1") + .RoundedTop(3) + .BackgroundColor(Themes.base200) + .Text(tab.title, Fonts.arial) + .TextColor(Themes.baseContent) + .Alignment(TextAlignment.MiddleCenter); + } + } + else + { + Gui.Box("Inactive Tab" + tab.id) + .OnClick((_) => + { + Gui.SetElementStorage(elementWithTabIdStorage, tabIdStorageKey, tab.id); + Console.WriteLine("clicked tab " + tab.id); + ActiveTabId = tab.id; + }) + .RoundedTop(3) + .Left(5) + .Width(tab.width).Height(28) + .BackgroundColor(Themes.base100) + .Text(tab.title, Fonts.arial) + .TextColor(Themes.baseContent) + .Alignment(TextAlignment.MiddleCenter) + .Hovered + .BackgroundColor(Themes.base300) + .End(); + } + } + + Gui.Box("Plus Tab") + .Width(20).Height(20) + .BackgroundColor(Themes.base100) + .Text("+", Fonts.arial) + .TextColor(Themes.baseContent) + .Alignment(TextAlignment.MiddleCenter) + .Rounded(5) + .Margin(4) + .Hovered + .BackgroundColor(Themes.base300) + .End(); + + Gui.Box("Spacer"); // automatically grows + + Gui.Box("Options") + .Width(20).Height(20) + .BackgroundColor(Themes.base100) + .FontSize(12) + .Text(Icons.Grip, Fonts.arial).TextColor(Themes.baseContent) + .Alignment(TextAlignment.MiddleCenter) + .Rounded(5) + .Margin(4) + .Hovered + .BackgroundColor(Themes.base300) + .End(); + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/PaperDemo.Theme.cs b/Samples/Shared/PaperDemo.Theme.cs index aee8222..f20e820 100644 --- a/Samples/Shared/PaperDemo.Theme.cs +++ b/Samples/Shared/PaperDemo.Theme.cs @@ -1,330 +1,40 @@ using System.Drawing; -using Prowl.PaperUI; - -// SHADCN discord theme colors -// discovered through https://ui.jln.dev/ -// @layer base { -// -// .dark { -// --background: 217.5 9.09% 17.25%; /* 40, 43, 48 */ -// --foreground: 334 34% 98%; /* 255, 247, 252 */ -// --muted: 210 9.09% 12.94%; /* 30, 32, 36 */ -// --muted-foreground: 334 0% 60.77%; /* 155, 155, 155 */ -// --popover: 210 9.09% 12.94%; /* 30, 32, 36 */ -// --popover-foreground: 334 34% 98%; /* 255, 247, 252 */ -// --card: 210 9.09% 12.94%; /* 30, 32, 36 */ -// --card-foreground: 334 34% 98%; /* 255, 247, 252 */ -// --border: 334 0% 18.46%; /* 47, 47, 47 */ -// --input: 214.29 5.04% 27.25%; /* 66, 68, 72 */ -// --primary: 226.73 58.43% 65.1%; /* 69, 135, 235 */ -// --primary-foreground: 0 0% 100%; /* 255, 255, 255 */ -// --secondary: 214.29 5.04% 27.25%; /* 66, 68, 72 */ -// --secondary-foreground: 334 0% 100%; /* 255, 255, 255 */ -// --accent: 217.5 9.09% 17.25%; /* 40, 43, 48 */ -// --accent-foreground: 226.73 58.43% 65.1%; /* 69, 135, 235 */ -// --destructive: 358.16 68.78% 53.53%; /* 230, 43, 52 */ -// --destructive-foreground: 0 0% 100%; /* 255, 255, 255 */ -// --ring: 217.5 9.09% 17.25%; /* 40, 43, 48 */ -// --chart-1: 226.73 58.43% 65.1%; /* 69, 135, 235 */ -// --chart-2: 214.29 5.04% 27.25%; /* 66, 68, 72 */ -// --chart-3: 217.5 9.09% 17.25%; /* 40, 43, 48 */ -// --chart-4: 214.29 5.04% 30.25%; /* 72, 74, 79 */ -// --chart-5: 226.73 61.43% 65.1%; /* 64, 135, 239 */ -// } -// -// :root { -// --background: 0 0% 97.69%; /* 249, 249, 249 */ -// --foreground: 334 55% 1%; /* 4, 1, 3 */ -// --muted: 0 0% 93.85%; /* 239, 239, 239 */ -// --muted-foreground: 0 0% 10.2%; /* 26, 26, 26 */ -// --popover: 0 0% 100%; /* 255, 255, 255 */ -// --popover-foreground: 0 0% 10.2%; /* 26, 26, 26 */ -// --card: 0 0% 100%; /* 255, 255, 255 */ -// --card-foreground: 0 0% 13.73%; /* 35, 35, 35 */ -// --border: 0 0% 80.78%; /* 206, 206, 206 */ -// --input: 0 0% 80.78%; /* 206, 206, 206 */ -// --primary: 211.29 100% 50%; /* 0, 149, 255 */ -// --primary-foreground: 0 0% 100%; /* 255, 255, 255 */ -// --secondary: 0 0% 85.49%; /* 218, 218, 218 */ -// --secondary-foreground: 334 0% 10%; /* 26, 26, 26 */ -// --accent: 211.29 100% 50%; /* 0, 149, 255 */ -// --accent-foreground: 334 0% 100%; /* 255, 255, 255 */ -// --destructive: 3.19 100% 59.41%; /* 303, 1, 0 */ -// --destructive-foreground: 18 0% 100%; /* 255, 255, 255 */ -// --ring: 0 0% 60%; /* 153, 153, 153 */ -// --chart-1: 211.29 100% 50%; /* 0, 149, 255 */ -// --chart-2: 0 0% 85.49%; /* 218, 218, 218 */ -// --chart-3: 211.29 100% 50%; /* 0, 149, 255 */ -// --chart-4: 0 0% 88.49%; /* 226, 226, 226 */ -// --chart-5: 211.29 103% 50%; /* 0, 152, 255 */ -// --radius: 0.5rem; -// } -// } namespace Shared { public static class Themes { //Theme - public static Color backgroundColor; - public static Color cardBackground; - public static Color primaryColor; - public static Color secondaryColor; - public static Color textColor; - public static Color lightTextColor; + public static Color base100; + public static Color base200; + public static Color base250; + public static Color base300; + public static Color base400; + public static Color baseContent; // text + public static Color primary; + public static Color primaryContent; // text public static Color[] colorPalette; - public static bool isDark; public static void Initialize() { - ToggleTheme(); - } - - public static void ToggleTheme() - { - isDark = !isDark; + //Dark + base100 = Color.FromArgb(255, 31, 31, 36); + base200 = Color.FromArgb(255, 42, 42, 46); + base250 = Color.FromArgb(255, 54, 55, 59); + base300 = Color.FromArgb(255, 64, 64, 68); + base400 = Color.FromArgb(255, 70, 71, 76); + baseContent = Color.FromArgb(255, 230, 230, 230); + primary = Color.FromArgb(255, 69, 135, 235); + primaryContent = Color.FromArgb(255, 226, 232, 240); + colorPalette = [ + Color.FromArgb(255, 94, 234, 212), // Cyan + Color.FromArgb(255, 162, 155, 254), // Purple + Color.FromArgb(255, 249, 115, 22), // Orange + Color.FromArgb(255, 248, 113, 113), // Red + Color.FromArgb(255, 250, 204, 21) // Yellow + ]; - if (isDark) - { - //Dark - backgroundColor = Color.FromArgb(255, 40, 43, 48); - cardBackground = Color.FromArgb(255, 30, 32, 36); - primaryColor = Color.FromArgb(255, 69, 135, 235); - secondaryColor = Color.FromArgb(255, 66, 68, 72); - textColor = Color.FromArgb(255, 226, 232, 240); - lightTextColor = Color.FromArgb(255, 148, 163, 184); - colorPalette = [ - Color.FromArgb(255, 94, 234, 212), // Cyan - Color.FromArgb(255, 162, 155, 254), // Purple - Color.FromArgb(255, 249, 115, 22), // Orange - Color.FromArgb(255, 248, 113, 113), // Red - Color.FromArgb(255, 250, 204, 21) // Yellow - ]; - } - else - { - //Light - backgroundColor = Color.FromArgb(255, 240, 240, 240); - cardBackground = Color.FromArgb(255, 255, 255, 255); - primaryColor = Color.FromArgb(255, 0, 149, 255); - secondaryColor = Color.FromArgb(255, 218, 218, 218); - textColor = Color.FromArgb(255, 31, 41, 55);//4, 1, 3); - lightTextColor = Color.FromArgb(255, 107, 114, 128); - colorPalette = [ - Color.FromArgb(255, 59, 130, 246), // Blue - Color.FromArgb(255, 16, 185, 129), // Teal - Color.FromArgb(255, 239, 68, 68), // Red - Color.FromArgb(255, 245, 158, 11), // Amber - Color.FromArgb(255, 139, 92, 246) // Purple - ]; - } - - // Redefine styles with new theme colors - DefineStyles(); Components.DefineStyles(); } - - public static void DefineStyles() - { - // Card styles with hover effects - PaperDemo.Gui.CreateStyleFamily("card") - .Base(new StyleTemplate() - .BackgroundColor(cardBackground) - .Rounded(8) - .BoxShadow(0, 2, 6, 0, Color.FromArgb(100, 0, 0, 0)) - .Transition(GuiProp.BoxShadow, 0.2) - .Transition(GuiProp.Rounded, 0.2) - .Transition(GuiProp.BorderColor, 0.3) - .Transition(GuiProp.BorderWidth, 0.2) - .Transition(GuiProp.ScaleX, 0.2) - .Transition(GuiProp.ScaleY, 0.2)) - .Hovered(new StyleTemplate() - .Rounded(12) - .BorderColor(primaryColor) - .BorderWidth(2) - .BoxShadow(0, 4, 12, 0, Color.FromArgb(160, 0, 0, 0)) - .Scale(1.05)) - .Register(); - - // Button styles - PaperDemo.Gui.CreateStyleFamily("button") - .Base(new StyleTemplate() - .Height(40) - .Rounded(8) - .BackgroundColor(Color.FromArgb(50, 0, 0, 0)) - .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.Rounded, 0.2) - .Transition(GuiProp.ScaleX, 0.1) - .Transition(GuiProp.ScaleY, 0.1)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(100, primaryColor)) - .Rounded(12)) - .Active(new StyleTemplate() - .Scale(0.95) - .BackgroundColor(Color.FromArgb(150, primaryColor))) - .Register(); - - // Primary button variant - PaperDemo.Gui.CreateStyleFamily("button-primary") - .Base(new StyleTemplate() - .Height(50) - .Rounded(8) - .BackgroundColor(primaryColor) - .BackgroundLinearGradient(0, 0, 0, 1, primaryColor, secondaryColor) - .Transition(GuiProp.BackgroundGradient, 0.2) - .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.ScaleX, 0.1) - .Transition(GuiProp.ScaleY, 0.1)) - .Hovered(new StyleTemplate() - .BackgroundColor(secondaryColor) - .BackgroundLinearGradient(0, 0, 0, 1, secondaryColor, primaryColor)) - .Active(new StyleTemplate() - .Scale(0.95) - .BackgroundColor(Color.FromArgb(200, primaryColor)) - .BackgroundLinearGradient(0, 0, 0, 1, Color.FromArgb(200, primaryColor), Color.FromArgb(200, secondaryColor))) - .Register(); - - // Icon button styles - PaperDemo.Gui.CreateStyleFamily("icon-button") - .Base(new StyleTemplate() - .Width(40) - .Height(40) - .Rounded(8) - .BackgroundColor(Color.FromArgb(50, 0, 0, 0)) - .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.Rounded, 0.2)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(100, primaryColor)) - .Rounded(20)) - .Register(); - - // Sidebar styles - PaperDemo.Gui.CreateStyleFamily("sidebar") - .Base(new StyleTemplate() - .BackgroundColor(cardBackground) - .Rounded(8) - .Width(75) - .Transition(GuiProp.Width, 0.25, Easing.EaseIn) - .Transition(GuiProp.BorderColor, 0.75) - .Transition(GuiProp.BorderWidth, 0.75) - .Transition(GuiProp.Rounded, 0.25)) - .Hovered(new StyleTemplate() - .Width(240) - .BorderColor(primaryColor) - .BorderWidth(3) - .Rounded(16)) - .Register(); - - // Menu item styles - PaperDemo.Gui.CreateStyleFamily("menu-item") - .Base(new StyleTemplate() - .Height(50) - .Margin(10, 10, 5, 5) - .Rounded(8) - .BorderColor(Color.Transparent) - .BorderWidth(0) - .Transition(GuiProp.BackgroundColor, 0.2) - .Transition(GuiProp.BorderWidth, 0.1) - .Transition(GuiProp.BorderColor, 0.2)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(20, primaryColor)) - .BorderWidth(2) - .BorderColor(primaryColor)) - .Register(); - - // Menu item selected state - PaperDemo.Gui.RegisterStyle("menu-item-selected", new StyleTemplate() - .BorderColor(primaryColor) - .BorderWidth(2) - .BackgroundColor(Color.FromArgb(30, primaryColor))); - - // Tab styles - PaperDemo.Gui.CreateStyleFamily("tab") - .Base(new StyleTemplate() - .Transition(GuiProp.BackgroundColor, 0.2)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(20, primaryColor))) - .Register(); - - // Text field styles - PaperDemo.Gui.CreateStyleFamily("text-field") - .Base(new StyleTemplate() - .Width(300) - .Height(40) - .Rounded(8) - .BackgroundColor(Color.FromArgb(50, 0, 0, 0)) - .BorderColor(Color.Transparent) - .BorderWidth(0) - .Transition(GuiProp.BorderColor, 0.2) - .Transition(GuiProp.BorderWidth, 0.2) - .Transition(GuiProp.BackgroundColor, 0.2)) - .Focused(new StyleTemplate() - .BorderColor(primaryColor) - .BorderWidth(2) - .BackgroundColor(Color.FromArgb(80, 0, 0, 0))) - .Register(); - - // Stat card styles - PaperDemo.Gui.CreateStyleFamily("stat-card") - .Base(new StyleTemplate() - .BackgroundColor(cardBackground) - .Rounded(8) - .BoxShadow(4, 4, 5, 0, Color.FromArgb(100, 0, 0, 0)) - .Transition(GuiProp.BoxShadow, 0.2) - .Transition(GuiProp.Rounded, 0.2) - .Transition(GuiProp.BorderColor, 0.3) - .Transition(GuiProp.BorderWidth, 0.2) - .Transition(GuiProp.ScaleX, 0.2) - .Transition(GuiProp.ScaleY, 0.2)) - .Hovered(new StyleTemplate() - .Rounded(12) - .BorderWidth(2) - .BoxShadow(0, 0, 4, 0, Color.FromArgb(0, 0, 0, 0)) - .Scale(1.05)) - .Register(); - - // Period button styles - PaperDemo.Gui.CreateStyleFamily("period-button") - .Base(new StyleTemplate() - .Width(60) - .Height(30) - .Rounded(8) - .Margin(5, 5, 0, 0) - .BackgroundColor(Color.FromArgb(50, 0, 0, 0)) - .Transition(GuiProp.BackgroundColor, 0.2)) - .Hovered(new StyleTemplate() - .BackgroundColor(Color.FromArgb(50, primaryColor))) - .Register(); - - PaperDemo.Gui.RegisterStyle("period-button-selected", new StyleTemplate() - .BackgroundColor(primaryColor)); - - // Activity icon styles - PaperDemo.Gui.RegisterStyle("activity-icon", new StyleTemplate() - .Width(40) - .Height(40) - .Rounded(20)); - - // Separator style - PaperDemo.Gui.RegisterStyle("separator", new StyleTemplate() - .Height(1) - .Margin(15, 15, 0, 0) - .BackgroundColor(Color.FromArgb(30, 0, 0, 0))); - - // Container styles - PaperDemo.Gui.RegisterStyle("container", new StyleTemplate() - .BackgroundColor(cardBackground) - .Rounded(8)); - - // Skill bar styles - PaperDemo.Gui.RegisterStyle("skill-bar-bg", new StyleTemplate() - .Height(15) - .Rounded(7.5) - .BackgroundColor(Color.FromArgb(30, 0, 0, 0))); - - PaperDemo.Gui.RegisterStyle("skill-bar-fg", new StyleTemplate() - .Rounded(7.5)); - } - } -} +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/AssetsTab.cs b/Samples/Shared/Tabs/AssetsTab.cs new file mode 100644 index 0000000..0dc9592 --- /dev/null +++ b/Samples/Shared/Tabs/AssetsTab.cs @@ -0,0 +1,58 @@ +using Prowl.PaperUI; + +namespace Shared.Tabs +{ + public class AssetsTab : Tab + { + public AssetsTab(Paper gui) : base(gui) + { + title = "Assets"; + id = "assets"; + width = 65; + } + + public override void Draw() + { + using (Gui.Box("Search Box").Margin(5).Height(28).Bottom(8).Rounded(5).BackgroundColor(Themes.base300).Enter()) + { + Gui.Box("Search").Text("Filter...", Fonts.arial) + .TextColor(Themes.baseContent) + .Left(8) + .Alignment(TextAlignment.MiddleLeft); + } + + using (Gui.Column("Assets view").Margin(5).ColBetween(8).SetScroll(Scroll.ScrollY).Enter()) + { + for (int i = 0; i < 64; i++) + { + using (Gui.Row("Folders and assets", i).Height(100).RowBetween(8).Margin(5).Enter()) + { + Gui.Box("Folder") + .Width(52) + .Height(100) + .FontSize(32) + .Text(Icons.Folder, Fonts.arial); + + Gui.Box("Folder") + .Width(52) + .Height(100) + .FontSize(32) + .Text(Icons.Folder, Fonts.arial); + + Gui.Box("Folder") + .Width(52) + .Height(100) + .FontSize(32) + .Text(Icons.Folder, Fonts.arial); + + Gui.Box("Folder") + .Width(52) + .Height(100) + .FontSize(32) + .Text(Icons.Folder, Fonts.arial); + } + } + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/AsteroidsTab.cs b/Samples/Shared/Tabs/AsteroidsTab.cs new file mode 100644 index 0000000..4dd879a --- /dev/null +++ b/Samples/Shared/Tabs/AsteroidsTab.cs @@ -0,0 +1,589 @@ +using Prowl.PaperUI; +using Prowl.Vector; +using Prowl.Scribe; +using System; +using System.Collections.Generic; + +namespace Shared.Tabs +{ + public class AsteroidsTab : Tab + { + const int SHIP_SIZE = 15; + const double SHIP_ROTATION_SPEED = 4.0; + const double SHIP_THRUST = 300.0; + const double SHIP_DRAG = 0.98; + const double SHIP_MAX_SPEED = 400.0; + const double BULLET_SPEED = 500.0; + const double BULLET_LIFETIME = 1.5; + const int MAX_BULLETS = 5; + const double FIRE_COOLDOWN = 0.2; + const int STARTING_LIVES = 3; + const double INVULNERABILITY_TIME = 2.0; + const double RESPAWN_DELAY = 1.5; + const int STARTING_ASTEROIDS = 4; + + enum AsteroidSize + { + Large, + Medium, + Small + } + + struct Ship + { + public Vector2 position; + public Vector2 velocity; + public double rotation; + public bool thrusting; + public double invulnerabilityTimer; + + public Ship(Vector2 position) + { + this.position = position; + this.velocity = Vector2.zero; + this.rotation = -Math.PI / 2; + this.thrusting = false; + this.invulnerabilityTimer = INVULNERABILITY_TIME; + } + } + + struct Bullet + { + public Vector2 position; + public Vector2 velocity; + public double lifetime; + + public Bullet(Vector2 position, Vector2 velocity) + { + this.position = position; + this.velocity = velocity; + this.lifetime = BULLET_LIFETIME; + } + } + + struct Asteroid + { + public Vector2 position; + public Vector2 velocity; + public double rotation; + public double rotationSpeed; + public AsteroidSize size; + public Vector2[] shape; + + public Asteroid(Vector2 position, Vector2 velocity, AsteroidSize size, System.Random rand) + { + this.position = position; + this.velocity = velocity; + this.size = size; + this.rotation = rand.NextDouble() * Math.PI * 2; + this.rotationSpeed = (rand.NextDouble() - 0.5) * 2.0; + + // Generate random asteroid shape + int points = rand.Next(8, 12); + this.shape = new Vector2[points]; + double baseRadius = GetRadius(size); + + for (int i = 0; i < points; i++) + { + double angle = (i / (double)points) * Math.PI * 2; + double radius = baseRadius * (0.7 + rand.NextDouble() * 0.3); + this.shape[i] = new Vector2( + Math.Cos(angle) * radius, + Math.Sin(angle) * radius + ); + } + } + + public static double GetRadius(AsteroidSize size) + { + return size switch + { + AsteroidSize.Large => 40, + AsteroidSize.Medium => 25, + AsteroidSize.Small => 15, + _ => 40 + }; + } + + public static int GetScore(AsteroidSize size) + { + return size switch + { + AsteroidSize.Large => 20, + AsteroidSize.Medium => 50, + AsteroidSize.Small => 100, + _ => 20 + }; + } + } + + struct Particle + { + public Vector2 position; + public Vector2 velocity; + public double lifetime; + public double maxLifetime; + + public Particle(Vector2 position, Vector2 velocity, double lifetime) + { + this.position = position; + this.velocity = velocity; + this.lifetime = lifetime; + this.maxLifetime = lifetime; + } + } + + Ship ship; + List bullets = new List(); + List asteroids = new List(); + List particles = new List(); + + bool leftPressed = false; + bool rightPressed = false; + bool thrustPressed = false; + bool shootPressed = false; + + double fireCooldownTimer = 0; + int score = 0; + int lives = STARTING_LIVES; + bool gameOver = false; + double respawnTimer = 0; + bool shipActive = true; + int level = 1; + + Rect _rect; + System.Random rand = new System.Random(); + + TextLayoutSettings scoreLayout = new TextLayoutSettings + { + Font = Fonts.arial, + PixelSize = 20, + Alignment = Prowl.Scribe.TextAlignment.Left + }; + + TextLayoutSettings gameOverLayout = new TextLayoutSettings + { + Font = Fonts.arial, + PixelSize = 32, + Alignment = Prowl.Scribe.TextAlignment.Center + }; + + public AsteroidsTab(Paper gui) : base(gui) + { + title = "Asteroids"; + id = "asteroids"; + width = 90; + + InitializeGame(); + } + + private void InitializeGame() + { + ship = new Ship(new Vector2(0, 0)); + bullets.Clear(); + asteroids.Clear(); + particles.Clear(); + score = 0; + lives = STARTING_LIVES; + gameOver = false; + shipActive = true; + level = 1; + respawnTimer = 0; + + SpawnAsteroids(STARTING_ASTEROIDS); + } + + private void SpawnAsteroids(int count) + { + for (int i = 0; i < count; i++) + { + Vector2 position; + do + { + position = new Vector2( + rand.NextDouble() * (_rect.width > 0 ? _rect.width : 800), + rand.NextDouble() * (_rect.height > 0 ? _rect.height : 600) + ); + } while (shipActive && (position - ship.position).magnitude < 150); + + Vector2 velocity = new Vector2( + (rand.NextDouble() - 0.5) * 100, + (rand.NextDouble() - 0.5) * 100 + ); + + asteroids.Add(new Asteroid(position, velocity, AsteroidSize.Large, rand)); + } + } + + private void SpawnParticles(Vector2 position, int count, double speed) + { + for (int i = 0; i < count; i++) + { + double angle = rand.NextDouble() * Math.PI * 2; + Vector2 velocity = new Vector2( + Math.Cos(angle) * speed * (0.5 + rand.NextDouble()), + Math.Sin(angle) * speed * (0.5 + rand.NextDouble()) + ); + particles.Add(new Particle(position, velocity, 0.5 + rand.NextDouble() * 0.5)); + } + } + + private void RespawnShip() + { + ship = new Ship(new Vector2(_rect.width / 2, _rect.height / 2)); + shipActive = true; + } + + private void DestroyShip() + { + SpawnParticles(ship.position, 20, 150); + shipActive = false; + lives--; + + if (lives <= 0) + { + gameOver = true; + } + else + { + respawnTimer = RESPAWN_DELAY; + } + } + + private Vector2 WrapPosition(Vector2 position) + { + double x = position.x; + double y = position.y; + + if (x < 0) x = _rect.width; + if (x > _rect.width) x = 0; + if (y < 0) y = _rect.height; + if (y > _rect.height) y = 0; + + return new Vector2(x, y); + } + + private void SplitAsteroid(Asteroid asteroid) + { + score += Asteroid.GetScore(asteroid.size); + SpawnParticles(asteroid.position, 10, 100); + + if (asteroid.size != AsteroidSize.Small) + { + AsteroidSize newSize = asteroid.size == AsteroidSize.Large ? + AsteroidSize.Medium : AsteroidSize.Small; + + for (int i = 0; i < 2; i++) + { + double angle = rand.NextDouble() * Math.PI * 2; + Vector2 velocity = new Vector2( + Math.Cos(angle) * 120, + Math.Sin(angle) * 120 + ); + asteroids.Add(new Asteroid(asteroid.position, velocity, newSize, rand)); + } + } + } + + public override void Focus() + { + Console.WriteLine("Asteroids is focused"); + } + + public override void Blur() + { + Console.WriteLine("Asteroids is blurred"); + } + + public override void Draw() + { + using (Gui.Box("Container") + .Margin(5) + .Focused + .BorderWidth(2) + .BorderColor(Themes.primary) + .End() + .OnKeyPressed((e) => + { + if (e.Key == PaperKey.A || e.Key == PaperKey.Left) leftPressed = true; + if (e.Key == PaperKey.D || e.Key == PaperKey.Right) rightPressed = true; + if (e.Key == PaperKey.W || e.Key == PaperKey.Up) thrustPressed = true; + if (e.Key == PaperKey.Space) shootPressed = true; + if (e.Key == PaperKey.R && gameOver) InitializeGame(); + }) + .Enter()) + { + Gui.AddActionElement((canvas, rect) => + { + _rect = rect; + var deltaTime = Gui.DeltaTime; + + // Initialize ship position if needed + if (ship.position.x == 0 && ship.position.y == 0) + { + ship.position = new Vector2(rect.width / 2, rect.height / 2); + } + + if (!gameOver) + { + // Handle respawn + if (!shipActive) + { + respawnTimer -= deltaTime; + if (respawnTimer <= 0) + { + RespawnShip(); + } + } + + // Update ship + if (shipActive) + { + if (leftPressed) ship.rotation -= SHIP_ROTATION_SPEED * deltaTime; + if (rightPressed) ship.rotation += SHIP_ROTATION_SPEED * deltaTime; + + ship.thrusting = thrustPressed; + + if (thrustPressed) + { + Vector2 thrust = new Vector2( + Math.Cos(ship.rotation), + Math.Sin(ship.rotation) + ) * SHIP_THRUST * deltaTime; + ship.velocity += thrust; + + // Spawn thrust particles + if (rand.NextDouble() < 0.5) + { + Vector2 exhaustPos = ship.position - new Vector2( + Math.Cos(ship.rotation) * SHIP_SIZE, + Math.Sin(ship.rotation) * SHIP_SIZE + ); + SpawnParticles(exhaustPos, 1, 50); + } + } + + ship.velocity *= SHIP_DRAG; + double speed = ship.velocity.magnitude; + if (speed > SHIP_MAX_SPEED) + { + ship.velocity = ship.velocity.normalized * SHIP_MAX_SPEED; + } + + ship.position += ship.velocity * deltaTime; + ship.position = WrapPosition(ship.position); + + if (ship.invulnerabilityTimer > 0) + { + ship.invulnerabilityTimer -= deltaTime; + } + + // Shooting + if (shootPressed && fireCooldownTimer <= 0 && bullets.Count < MAX_BULLETS) + { + Vector2 bulletVelocity = new Vector2( + Math.Cos(ship.rotation), + Math.Sin(ship.rotation) + ) * BULLET_SPEED; + + bullets.Add(new Bullet(ship.position, bulletVelocity)); + fireCooldownTimer = FIRE_COOLDOWN; + } + } + + // Update fire cooldown + if (fireCooldownTimer > 0) + { + fireCooldownTimer -= deltaTime; + } + + // Update bullets + for (int i = bullets.Count - 1; i >= 0; i--) + { + var bullet = bullets[i]; + bullet.position += bullet.velocity * deltaTime; + bullet.position = WrapPosition(bullet.position); + bullet.lifetime -= deltaTime; + + if (bullet.lifetime <= 0) + { + bullets.RemoveAt(i); + } + else + { + bullets[i] = bullet; + } + } + + // Update asteroids + for (int i = 0; i < asteroids.Count; i++) + { + var asteroid = asteroids[i]; + asteroid.position += asteroid.velocity * deltaTime; + asteroid.position = WrapPosition(asteroid.position); + asteroid.rotation += asteroid.rotationSpeed * deltaTime; + asteroids[i] = asteroid; + } + + // Check bullet-asteroid collisions + for (int i = bullets.Count - 1; i >= 0; i--) + { + for (int j = asteroids.Count - 1; j >= 0; j--) + { + double distance = (bullets[i].position - asteroids[j].position).magnitude; + if (distance < Asteroid.GetRadius(asteroids[j].size)) + { + SplitAsteroid(asteroids[j]); + asteroids.RemoveAt(j); + bullets.RemoveAt(i); + break; + } + } + } + + // Check ship-asteroid collisions + if (shipActive && ship.invulnerabilityTimer <= 0) + { + for (int i = 0; i < asteroids.Count; i++) + { + double distance = (ship.position - asteroids[i].position).magnitude; + if (distance < SHIP_SIZE + Asteroid.GetRadius(asteroids[i].size)) + { + DestroyShip(); + break; + } + } + } + + // Update particles + for (int i = particles.Count - 1; i >= 0; i--) + { + var particle = particles[i]; + particle.position += particle.velocity * deltaTime; + particle.lifetime -= deltaTime; + + if (particle.lifetime <= 0) + { + particles.RemoveAt(i); + } + else + { + particles[i] = particle; + } + } + + // Check for level complete + if (asteroids.Count == 0) + { + level++; + SpawnAsteroids(STARTING_ASTEROIDS + level - 1); + } + } + + // Draw background + canvas.RectFilled(rect.x, rect.y, rect.width, rect.height, System.Drawing.Color.Black); + + // Draw particles + foreach (var particle in particles) + { + double alpha = particle.lifetime / particle.maxLifetime; + canvas.CircleFilled( + rect.x + particle.position.x, + rect.y + particle.position.y, + 2, + System.Drawing.Color.FromArgb((int)(255 * alpha), 255, 255, 255) + ); + } + + // Draw asteroids + foreach (var asteroid in asteroids) + { + canvas.BeginPath(); + for (int i = 0; i < asteroid.shape.Length; i++) + { + double cos = Math.Cos(asteroid.rotation); + double sin = Math.Sin(asteroid.rotation); + double x = asteroid.shape[i].x * cos - asteroid.shape[i].y * sin; + double y = asteroid.shape[i].x * sin + asteroid.shape[i].y * cos; + + if (i == 0) + canvas.MoveTo(rect.x + asteroid.position.x + x, rect.y + asteroid.position.y + y); + else + canvas.LineTo(rect.x + asteroid.position.x + x, rect.y + asteroid.position.y + y); + } + canvas.ClosePath(); + canvas.SetStrokeColor(System.Drawing.Color.White); + canvas.SetStrokeWidth(2); + canvas.Stroke(); + } + + // Draw ship + if (shipActive && (ship.invulnerabilityTimer <= 0 || ((int)(ship.invulnerabilityTimer * 10) % 2 == 0))) + { + Vector2 front = new Vector2( + Math.Cos(ship.rotation) * SHIP_SIZE, + Math.Sin(ship.rotation) * SHIP_SIZE + ); + Vector2 back1 = new Vector2( + Math.Cos(ship.rotation + 2.5) * SHIP_SIZE * 0.7, + Math.Sin(ship.rotation + 2.5) * SHIP_SIZE * 0.7 + ); + Vector2 back2 = new Vector2( + Math.Cos(ship.rotation - 2.5) * SHIP_SIZE * 0.7, + Math.Sin(ship.rotation - 2.5) * SHIP_SIZE * 0.7 + ); + + canvas.BeginPath(); + canvas.MoveTo(rect.x + ship.position.x + front.x, rect.y + ship.position.y + front.y); + canvas.LineTo(rect.x + ship.position.x + back1.x, rect.y + ship.position.y + back1.y); + canvas.LineTo(rect.x + ship.position.x + back2.x, rect.y + ship.position.y + back2.y); + canvas.ClosePath(); + canvas.SetStrokeColor(System.Drawing.Color.White); + canvas.SetStrokeWidth(2); + canvas.Stroke(); + + // Draw thrust + if (ship.thrusting) + { + Vector2 thrustPoint = new Vector2( + Math.Cos(ship.rotation + Math.PI) * SHIP_SIZE * 0.8, + Math.Sin(ship.rotation + Math.PI) * SHIP_SIZE * 0.8 + ); + canvas.BeginPath(); + canvas.MoveTo(rect.x + ship.position.x + back1.x, rect.y + ship.position.y + back1.y); + canvas.LineTo(rect.x + ship.position.x + thrustPoint.x, rect.y + ship.position.y + thrustPoint.y); + canvas.LineTo(rect.x + ship.position.x + back2.x, rect.y + ship.position.y + back2.y); + canvas.SetStrokeColor(System.Drawing.Color.Orange); + canvas.SetStrokeWidth(2); + canvas.Stroke(); + } + } + + // Draw bullets + foreach (var bullet in bullets) + { + canvas.CircleFilled(rect.x + bullet.position.x, rect.y + bullet.position.y, 2, System.Drawing.Color.White); + } + + // Draw UI + canvas.DrawText($"Score: {score}", rect.x + 10, rect.y + 25, System.Drawing.Color.White, scoreLayout); + canvas.DrawText($"Lives: {lives}", rect.x + 10, rect.y + 50, System.Drawing.Color.White, scoreLayout); + canvas.DrawText($"Level: {level}", rect.x + 10, rect.y + 75, System.Drawing.Color.White, scoreLayout); + + // Draw game over + if (gameOver) + { + canvas.DrawText("GAME OVER", rect.x + rect.width / 2, rect.y + rect.height / 2 - 20, System.Drawing.Color.White, gameOverLayout); + canvas.DrawText($"Final Score: {score}", rect.x + rect.width / 2, rect.y + rect.height / 2 + 20, System.Drawing.Color.White, scoreLayout); + canvas.DrawText("Press R to Restart", rect.x + rect.width / 2, rect.y + rect.height / 2 + 50, System.Drawing.Color.White, scoreLayout); + } + + leftPressed = false; + rightPressed = false; + thrustPressed = false; + shootPressed = false; + }); + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/ChartTab.cs b/Samples/Shared/Tabs/ChartTab.cs new file mode 100644 index 0000000..d34e6b1 --- /dev/null +++ b/Samples/Shared/Tabs/ChartTab.cs @@ -0,0 +1,185 @@ +using Prowl.PaperUI; + +using Prowl.Vector; + +namespace Shared.Tabs +{ + public class ChartTab : Tab + { + double time = 0; + static double[] dataPoints = { 0.2f, 0.5f, 0.3f, 0.8f, 0.4f, 0.7f, 0.6f }; + static Vector2 chartPosition = new Vector2(0, 0); + static double zoomLevel = 1.0f; + + public ChartTab(Paper gui) : base(gui) + { + title = "Chart"; + id = "chart"; + width = 64; + } + + public override void Draw() + { + // Update time for animations + // time += 0.016f; // Assuming ~60fps + time += Gui.DeltaTime; + + using (Gui.Row("Tools").RowBetween(5).Margin(4).Top(5).Height(20).Enter()) + { + Gui.Box("Tool 1") + .Text(Icons.ArrowsTurnToDots, Fonts.arial).FontSize(12) + .Width(20).Rounded(5).Alignment(TextAlignment.MiddleCenter) + .Hovered.BackgroundColor(Themes.base250).End(); + Gui.Box("Tool 2") + .Text(Icons.ArrowsDownToLine, Fonts.arial).FontSize(12) + .Width(20).Rounded(5).Alignment(TextAlignment.MiddleCenter) + .Hovered.BackgroundColor(Themes.base250).End(); + Gui.Box("Tool 3") + .Text(Icons.Anchor, Fonts.arial).FontSize(12) + .Width(20).Rounded(5).Alignment(TextAlignment.MiddleCenter) + .Hovered.BackgroundColor(Themes.base250).End(); + Gui.Box("Tool 4") + .Text(Icons.AngleUp, Fonts.arial).FontSize(12) + .Width(20).Rounded(5).Alignment(TextAlignment.MiddleCenter) + .Hovered.BackgroundColor(Themes.base250).End(); + + Gui.Box("Spacer"); + + Gui.Box("Tool 4") + .Text(Icons.ArrowsLeftRight, Fonts.arial).FontSize(12) + .Width(20).Rounded(5).Alignment(TextAlignment.MiddleCenter) + .Hovered.BackgroundColor(Themes.base250).End(); + } + + using (Gui.Box("Game View").Margin(2).Rounded(5).BackgroundColor(System.Drawing.Color.Black).Enter()) + { + + // Chart content + using (Gui.Box("Chart") + .Margin(20) + .OnDragging((e) => chartPosition += e.Delta) + .OnScroll((e) => zoomLevel = Math.Clamp(zoomLevel + e.Delta * 0.1f, 0.5f, 2.0f)) + .Clip() + .Enter()) + { + using (Gui.Box("ChartCanvas") + .Translate(chartPosition.x, chartPosition.y) + .Scale(zoomLevel) + .Enter()) + { + // Draw a simple chart with animated data + Gui.AddActionElement((vg, rect) => + { + + // Draw grid lines + for (int i = 0; i <= 5; i++) + { + double y = rect.y + (rect.height / 5) * i; + vg.BeginPath(); + vg.MoveTo(rect.x, y); + vg.LineTo(rect.x + rect.width, y); + vg.SetStrokeColor(Themes.baseContent); + vg.SetStrokeWidth(1); + vg.Stroke(); + } + + // Draw animated data points + vg.BeginPath(); + double pointSpacing = rect.width / (dataPoints.Length - 1); + double animatedValue; + + // Draw fill + vg.MoveTo(rect.x, rect.y + rect.height); + + for (int i = 0; i < dataPoints.Length; i++) + { + animatedValue = dataPoints[i] + Math.Sin(time * 0.25f + i * 0.5f) * 0.1f; + //animatedValue = Math.Clamp(animatedValue, 0.1f, 0.9f); + animatedValue = Math.Min(Math.Max(animatedValue, 0.1f), 0.9f); // Clamp to [0.1, 0.9] + + double x = rect.x + i * pointSpacing; + double y = rect.y + rect.height - (animatedValue * rect.height); + + if (i == 0) + vg.MoveTo(x, y); + else + vg.LineTo(x, y); + } + + // Complete the fill path + vg.LineTo(rect.x + rect.width, rect.y + rect.height); + vg.LineTo(rect.x, rect.y + rect.height); + + // Fill with gradient + //var paint = vg.LinearGradient( + // rect.x, rect.y, + // rect.x, rect.y + rect.height, + // Color.FromArgb(100, primaryColor), + // Color.FromArgb(10, primaryColor)); + //vg.SetFillPaint(paint); + vg.SaveState(); + vg.SetLinearBrush(rect.x, rect.y, rect.x, rect.y + rect.height, System.Drawing.Color.FromArgb(100, Themes.primary), System.Drawing.Color.FromArgb(10, Themes.primary)); + vg.FillComplex(); + vg.RestoreState(); + + vg.ClosePath(); + + // Draw the line + vg.BeginPath(); + for (int i = 0; i < dataPoints.Length; i++) + { + animatedValue = dataPoints[i] + Math.Sin(time * 0.25f + i * 0.5f) * 0.1f; + //animatedValue = Math.Clamp(animatedValue, 0.1f, 0.9f); + animatedValue = Math.Min(Math.Max(animatedValue, 0.1f), 0.9f); // Clamp to [0.1, 0.9] + + double x = rect.x + i * pointSpacing; + double y = rect.y + rect.height - (animatedValue * rect.height); + + if (i == 0) + vg.MoveTo(x, y); + else + vg.LineTo(x, y); + } + + vg.SetStrokeColor(Themes.primary); + vg.SetStrokeWidth(3); + vg.Stroke(); + + // Draw points + for (int i = 0; i < dataPoints.Length; i++) + { + animatedValue = dataPoints[i] + Math.Sin(time * 0.25f + i * 0.5f) * 0.1f; + //animatedValue = Math.Clamp(animatedValue, 0.1f, 0.9f); + animatedValue = Math.Min(Math.Max(animatedValue, 0.1f), 0.9f); // Clamp to [0.1, 0.9] + + double x = rect.x + i * pointSpacing; + double y = rect.y + rect.height - (animatedValue * rect.height); + + vg.BeginPath(); + vg.Circle(x, y, 6); + vg.SetFillColor(System.Drawing.Color.White); + vg.Fill(); + + vg.BeginPath(); + vg.Circle(x, y, 4); + vg.SetFillColor(Themes.primary); + vg.Fill(); + } + + vg.ClosePath(); + }); + } + } + + + using (Gui.Row("Details").Height(28).Margin(8).Enter()) + { + // FPS Counter + Gui.Box("FPS").Text($"FPS: {1f / Gui.DeltaTime:F1}", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19); + Gui.Box("NodeCounter").Text($"Nodes: {Gui.CountOfAllElements}", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19); + Gui.Box("MS").Text($"Frame ms: {Gui.MillisecondsSpent}", Fonts.arial).TextColor(Themes.baseContent).Alignment(TextAlignment.MiddleLeft).FontSize(19); + } + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/ExampleTab.cs b/Samples/Shared/Tabs/ExampleTab.cs new file mode 100644 index 0000000..e1c1536 --- /dev/null +++ b/Samples/Shared/Tabs/ExampleTab.cs @@ -0,0 +1,14 @@ +using Prowl.PaperUI; + +namespace Shared.Tabs +{ + public class ExampleTab : Tab + { + public ExampleTab(Paper gui) : base(gui) + { + title = "Example"; + id = "example"; + width = 70; + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/FilesTab.cs b/Samples/Shared/Tabs/FilesTab.cs new file mode 100644 index 0000000..23d7db5 --- /dev/null +++ b/Samples/Shared/Tabs/FilesTab.cs @@ -0,0 +1,98 @@ +using Prowl.PaperUI; + +namespace Shared.Tabs +{ + public class FilesTab : Tab + { + public FilesTab(Paper gui) : base(gui) + { + title = "Files"; + id = "files"; + width = 52; + } + + private string selectedFolderId; + + public override void Draw() + { + using (Gui.Box("Search Box").Height(28).Margin(5).Rounded(5).BackgroundColor(Themes.base300).Enter()) + { + Gui.Box("Search").Text("Filter...", Fonts.arial) + .TextColor(Themes.baseContent) + .Left(8) + .Alignment(TextAlignment.MiddleLeft); + } + + using (Gui.Column("Folders view").SetScroll(Scroll.ScrollY).Enter()) + { + DrawFolderItem("Folders", true, new[] { + ("Models", new[] { + "Character.fbx", + "Tree.fbx", + "House.fbx" + }), + ("Textures", new[] { + "Grass.png", + "Sky.png" + }), + ("Scripts", new[] { + "Player.cs", + "Enemy.cs" + }) + }); + } + } + + private void DrawFolderItem(string name, bool expanded = false, (string name, string[] children)[] subFolders = null) + { + var isSelected = selectedFolderId == name; + + using (Gui.Row(name).Height(28).Margin(5).Top(0).Bottom(0).Rounded(5) + .BackgroundColor(isSelected ? Themes.base250 : Themes.base200) + .Hovered + .BackgroundColor(Themes.base250) + .End() + .OnClick((_) => selectedFolderId = name) + .Enter()) + { + if (subFolders != null) + { + Gui.Box($"toggle{name}").Text(expanded ? Icons.ChevronDown : Icons.ChevronRight, Fonts.arial) + .Width(28) + .Alignment(TextAlignment.MiddleCenter) + .FontSize(8); + } + + Gui.Box($"icon{name}").Text(Icons.Folder, Fonts.arial) + .Width(28) + .Alignment(TextAlignment.MiddleCenter) + .FontSize(10); + + Gui.Box($"name{name}").Text(name, Fonts.arial) + .TextColor(Themes.baseContent) + .Alignment(TextAlignment.MiddleLeft); + } + + if (expanded && subFolders != null) + { + using (Gui.Box($"Children{name}").Left(20).Enter()) + { + foreach (var (folderName, children) in subFolders) + { + DrawFolderItem(folderName, false, null); + if (children != null) + { + using (Gui.Box($"Files{folderName}").Left(20).Enter()) + { + foreach (var file in children) + { + DrawFolderItem(file); + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/HierarchyTab.cs b/Samples/Shared/Tabs/HierarchyTab.cs new file mode 100644 index 0000000..6f2affe --- /dev/null +++ b/Samples/Shared/Tabs/HierarchyTab.cs @@ -0,0 +1,221 @@ +using Prowl.PaperUI; +using Prowl.PaperUI.LayoutEngine; + +namespace Shared.Tabs +{ + public class HierarchyTab : Tab + { + public HierarchyTab(Paper gui) : base(gui) + { + title = "Hierarchy"; + id = "hierarchy"; + width = 83; + } + + public override void Draw() + { + using (Gui.Box("Search Box") + .OnDragEnd((e) => + { + Console.WriteLine("drag end", e); + }) + .OnDragStart((e) => + { + // e.DataTransfer.setData("tabId", id); + // e.DataTransfer.setImage(imageOfThingBeingDragged); // shows an image that moves with your cursor as long as you are dragging + Console.WriteLine("drag start", e); + }) + .OnDragging((e) => + { + Console.WriteLine("dragging", e); + }) + // .OnDrop((e) => + // { + // var data = e.DataTransfer.getData("tabId"); + // }) + .Height(28).Margin(5).Top(8).Bottom(8).Rounded(5) + .BackgroundColor(Themes.base300).Enter()) + { + Gui.Box("Search").Text("Filter...", Fonts.arial) + .TextColor(Themes.baseContent) + .Left(8) + .Alignment(TextAlignment.MiddleLeft); + } + + using (Gui.Box("Hierarchy container").SetScroll(Scroll.ScrollY).Enter()) + { + HierarchyItem(rootItem, 0); + Gui.Box("Spacer"); // this will take up any remaining space (do not remove) + } + } + + public class Item + { + public string id; + public string title; + public Item[] children; + public bool expanded; + public bool selected; + public Action onClick; // Open inspector etc + } + + public static string selectedItemId = ""; + + private void HierarchyItem(Item item, int depth) + { + var isSelected = selectedItemId == item.id; + + using (Gui.Column("Hierarchy item " + item.id, depth).MinHeight(item.expanded ? UnitValue.Auto : 28).Left(depth * 8).Enter()) + { + // ensure proper padding with parent + using (Gui.Row(item.id).Height(28).Margin(5).Top(0).Bottom(0).Rounded(5) + .BackgroundColor(isSelected ? Themes.base250 : Themes.base200) + .Hovered + .BackgroundColor(Themes.base250) + .End() + .OnClick((_) => + { + selectedItemId = item.id; + item.onClick?.Invoke(item); + }) + .OnDragging((e) => + { + var source = e.Source; + Console.WriteLine("dragged over", item.title); + }) + .Enter()) + { + if (isSelected) + { + Gui.Box("indicator") + .PositionType(PositionType.SelfDirected) + .Height(28).Width(3).Rounded(1) + .BackgroundColor(Themes.primary); + } + + if (item.children != null && item.children.Length > 0) + { + Gui.Box("toggle" + item.id).Text(item.expanded ? Icons.ChevronDown : Icons.ChevronRight, Fonts.arial) + .Width(28) + .Alignment(TextAlignment.MiddleCenter) + .FontSize(8) + .OnClick((_) => item.expanded = !item.expanded); + } + else + { + Gui.Box("icon" + item.id).Text(Icons.Cube, Fonts.arial) + .Width(28) + .Alignment(TextAlignment.MiddleCenter) + .FontSize(10) + .OnClick((_) => item.expanded = !item.expanded); + } + + Gui.Box("box" + item.id).Text(item.title, Fonts.arial) + .TextColor(Themes.baseContent) + .Alignment(TextAlignment.MiddleLeft); + + } + + if (item.expanded && item.children != null) + { + foreach (var child in item.children) + { + HierarchyItem(child, depth + 1); + } + } + } + } + + public static Item rootItem = new Item + { + id = "root object", + title = "Game World", + expanded = true, + selected = false, + onClick = (item) => Console.WriteLine($"Clicked {item.title}"), + children = new[] { + new Item { + id = "2", + title = "Player", + expanded = true, + children = new[] { + new Item { id = "2.1", title = "Inventory"}, + new Item { id = "2.2", title = "Equipment"}, + new Item { id = "2.3", title = "Stats" }, + new Item { id = "2.4", title = "Skills" } + } + }, + new Item { + id = "3", + title = "Environment", + expanded = false, + children = new[] { + new Item { + id = "3.1", + title = "Terrain", + children = new[] { + new Item { id = "3.1.1", title = "Mountains" }, + new Item { id = "3.1.2", title = "Forest" }, + new Item { id = "3.1.3", title = "Desert" } + } + }, + new Item { + id = "3.2", + title = "Weather", + children = new[] { + new Item { id = "3.2.1", title = "Rain System" }, + new Item { id = "3.2.2", title = "Wind System" } + } + } + } + }, + new Item { + id = "4", + title = "NPCs", + expanded = false, + children = new[] { + new Item { + id = "4.1", + title = "Friendly", + children = new[] { + new Item { id = "4.1.1", title = "Merchants" }, + new Item { id = "4.1.2", title = "Quest Givers" }, + new Item { id = "4.1.3", title = "Villagers" } + } + }, + new Item { + id = "4.2", + title = "Hostile", + children = new[] { + new Item { id = "4.2.1", title = "Goblins" }, + new Item { id = "4.2.2", title = "Dragons" }, + new Item { id = "4.2.3", title = "Bandits" } + } + } + } + }, + new Item { + id = "5", + title = "UI Elements", + expanded = false, + children = new[] { + new Item { id = "5.1", title = "Main Menu" }, + new Item { id = "5.2", title = "HUD" }, + new Item { id = "5.3", title = "Inventory UI" }, + new Item { id = "5.4", title = "Quest Log" } + } + }, + new Item { + id = "6", + title = "Audio", + expanded = false, + children = new[] { + new Item { id = "6.1", title = "Music" }, + new Item { id = "6.2", title = "Sound Effects" }, + new Item { id = "6.3", title = "Ambient" } + } + } + } + }; + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/InspectorTab.cs b/Samples/Shared/Tabs/InspectorTab.cs new file mode 100644 index 0000000..a2d8c55 --- /dev/null +++ b/Samples/Shared/Tabs/InspectorTab.cs @@ -0,0 +1,14 @@ +using Prowl.PaperUI; + +namespace Shared.Tabs +{ + public class InspectorTab : Tab + { + public InspectorTab(Paper gui) : base(gui) + { + title = "Inspector"; + id = "inspector"; + width = 70; + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/PlatformerTab.cs b/Samples/Shared/Tabs/PlatformerTab.cs new file mode 100644 index 0000000..987bda2 --- /dev/null +++ b/Samples/Shared/Tabs/PlatformerTab.cs @@ -0,0 +1,180 @@ +using Prowl.PaperUI; +using Prowl.Vector; + +namespace Shared.Tabs +{ + public class PlatformerTab : Tab + { + const int PLAYER_SIZE = 20; + const double PLAYER_ACCELERATION = 1200.0; + const double PLAYER_MAX_SPEED = 200.0; + const double PLAYER_FRICTION = 0.93; + const double JUMP_FORCE = -400.0; + const double GRAVITY = 800.0; + + double playerX = 100; + double playerY = 0; + double velocityX = 0; + double velocityY = 0; + bool isGrounded = false; + bool leftPressed = false; + bool rightPressed = false; + + Rect _rect; + + public PlatformerTab(Paper gui) : base(gui) + { + title = "Platformer"; + id = "platformer"; + width = 90; + } + + public override void Focus() + { + Console.WriteLine("Platformer is focused"); + } + + public override void Blur() + { + Console.WriteLine("Platformer is blurred"); + } + + struct Platform + { + public double x; + public double y; + public double width; + public double height; + + public Platform(double x, double y, double width, double height) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + } + + Platform[] platforms = new Platform[] { + new Platform(100, 300, 100, 20), + new Platform(250, 250, 100, 20), + new Platform(400, 200, 100, 20), + new Platform(200, 150, 100, 20), + }; + + private bool CheckCollision(double x, double y, Platform platform) + { + return x < platform.x + platform.width && + x + PLAYER_SIZE > platform.x && + y < platform.y + platform.height && + y + PLAYER_SIZE > platform.y; + } + + public override void Draw() + { + using (Gui.Box("Container") + .Margin(5) + .Focused + .BorderWidth(2) + .BorderColor(Themes.primary) + .End() + .OnKeyPressed((e) => + { + if (e.Key == PaperKey.A) leftPressed = true; + if (e.Key == PaperKey.D) rightPressed = true; + if (e.Key == PaperKey.Space && isGrounded) velocityY = JUMP_FORCE; + }) + .Enter()) + { + Gui.AddActionElement((canvas, rect) => + { + _rect = rect; + var deltaTime = Gui.DeltaTime; + + // Horizontal movement with acceleration + if (leftPressed) velocityX -= PLAYER_ACCELERATION * deltaTime; + if (rightPressed) velocityX += PLAYER_ACCELERATION * deltaTime; + + // Apply friction when no keys are pressed + velocityX *= PLAYER_FRICTION; + + // Clamp horizontal speed + velocityX = Math.Clamp(velocityX, -PLAYER_MAX_SPEED, PLAYER_MAX_SPEED); + + // Apply gravity + velocityY += GRAVITY * deltaTime; + + // Update position + double newPlayerX = playerX + velocityX * deltaTime; + double newPlayerY = playerY + velocityY * deltaTime; + + // Platform collision detection + isGrounded = false; + foreach (var platform in platforms) + { + if (CheckCollision(newPlayerX, newPlayerY, platform)) + { + // Vertical collision + if (velocityY > 0 && playerY + PLAYER_SIZE <= platform.y + 5) + { + newPlayerY = platform.y - PLAYER_SIZE; + velocityY = 0; + isGrounded = true; + } + // Head collision + else if (velocityY < 0 && playerY >= platform.y + platform.height - 5) + { + newPlayerY = platform.y + platform.height; + velocityY = 0; + } + // Horizontal collision + else if (velocityX != 0) + { + newPlayerX = velocityX > 0 ? + platform.x - PLAYER_SIZE : + platform.x + platform.width; + velocityX = 0; + } + } + } + + // Ground collision + if (newPlayerY + PLAYER_SIZE >= rect.height) + { + newPlayerY = rect.height - PLAYER_SIZE; + velocityY = 0; + isGrounded = true; + } + + // Wall collision + newPlayerX = Math.Clamp(newPlayerX, 0, rect.width - PLAYER_SIZE); + + // Update position + playerX = newPlayerX; + playerY = newPlayerY; + + // Draw platforms + foreach (var platform in platforms) + { + canvas.RectFilled( + rect.x + platform.x, + rect.y + platform.y, + platform.width, + platform.height, + System.Drawing.Color.Gray + ); + } + + // Draw player + canvas.RectFilled(rect.x + playerX, rect.y + playerY, PLAYER_SIZE, PLAYER_SIZE, System.Drawing.Color.White); + + // Draw ground + canvas.RectFilled(rect.x, rect.y + rect.height - 2, rect.width, 2, System.Drawing.Color.White); + + leftPressed = false; + rightPressed = false; + }); + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/PongTab.cs b/Samples/Shared/Tabs/PongTab.cs new file mode 100644 index 0000000..dcf6ee5 --- /dev/null +++ b/Samples/Shared/Tabs/PongTab.cs @@ -0,0 +1,212 @@ +using Prowl.PaperUI; +using Prowl.Quill; +using Prowl.Scribe; + +namespace Shared.Tabs +{ + public class PongTab : Tab + { + const int BALL_SIZE = 10; + const int PADDLE_WIDTH = 10; + const int PADDLE_HEIGHT = 80; + const double BALL_MAX_VELOCITY = 100.0; + const double PLAYERS_MAX_VELOCITY = 400.0; + const double PLAYER_START_VELOCITY = 150.0; // Base velocity for paddle movement + + TextLayoutSettings _scoreLayoutSettings = TextLayoutSettings.Default; + + int player1Score = 0; + double player1Y = -2; + bool player1UpPressed = false; + bool player1DownPressed = false; + double player1Velocity = 0.0; + + int player2Score = 0; + double player2Y = -2; + bool player2UpPressed = false; + bool player2DownPressed = false; + double player2Velocity = 0.0; + + bool ballPositionCentered = false; + bool gameStarted = false; + double ballX = 0; + double ballY = 0; + double ballVelocityX = 0; + double ballVelocityY = 0; + + Prowl.Vector.Rect _rect; + + public PongTab(Paper gui) : base(gui) + { + title = "Pong"; + id = "pong"; + width = 64; + + // Larger font for score + _scoreLayoutSettings.Font = Fonts.arial; + _scoreLayoutSettings.PixelSize = 24; + _scoreLayoutSettings.Alignment = Prowl.Scribe.TextAlignment.Center; + } + + public override void Focus() + { + Console.WriteLine("Pong is focused"); + } + + private void ResetBall() + { + ballX = _rect.width / 2; + ballY = _rect.height / 2; + ballVelocityX = 150 * (Random.Shared.Next(2) * 2 - 1); // Random direction + ballVelocityY = 150 * (Random.Shared.Next(2) * 2 - 1); + } + + public override void Blur() + { + Console.WriteLine("Pong is blurred"); + } + + double ogBallVelX = 0; + double ogBallVelY = 0; + + public override void Draw() + { + using (Gui.Box("Container") + .Margin(5) + .Focused + .BorderWidth(2) + .BorderColor(Themes.primary) + .End() + .OnFocusChange((e) => + { + if (e.IsFocused) + { + gameStarted = true; + ballVelocityX = ogBallVelX; // Random direction + ballVelocityY = ogBallVelY; + } + else + { + ogBallVelX = ballVelocityX; + ogBallVelY = ballVelocityY; + ballVelocityX = 0; + ballVelocityY = 0; + } + }) + .OnKeyPressed((e) => + { + if (e.Key == PaperKey.W) player1UpPressed = true; + else player1UpPressed = false; + if (e.Key == PaperKey.S) player1DownPressed = true; + else player1DownPressed = false; + + if (e.Key == PaperKey.Up) player2UpPressed = true; + else player2UpPressed = false; + if (e.Key == PaperKey.Down) player2DownPressed = true; + else player2DownPressed = false; + }) + .Enter()) + { + Gui.AddActionElement((canvas, rect) => + { + _rect = rect; + + if (!ballPositionCentered && !gameStarted) + { + ballX = _rect.width / 2; + ballY = _rect.height / 2; + } + + if (gameStarted && !ballPositionCentered) + { + ResetBall(); + ballPositionCentered = true; + } + + // Initialize players in the middle if they haven't been positioned yet + if (player1Y < -1) player1Y = (rect.height - PADDLE_HEIGHT) / 2; + if (player2Y < -1) player2Y = (rect.height - PADDLE_HEIGHT) / 2; + + var deltaTime = Gui.DeltaTime; + + // Then modify the velocity calculations in the Draw() method: + if (player1UpPressed) player1Velocity = -PLAYER_START_VELOCITY * deltaTime; // Use constant for consistent initial velocity + if (player1DownPressed) player1Velocity = PLAYER_START_VELOCITY * deltaTime; // Use constant for consistent initial velocity + + player1Y += player1Velocity; + player1Velocity = Math.Clamp(player1Velocity * 0.98, -PLAYERS_MAX_VELOCITY, PLAYERS_MAX_VELOCITY); + + if (player2UpPressed) player2Velocity = -PLAYER_START_VELOCITY * deltaTime; // Use constant for consistent initial velocity + if (player2DownPressed) player2Velocity = PLAYER_START_VELOCITY * deltaTime; // Use constant for consistent initial velocity + + player2Y += player2Velocity; + player2Velocity = Math.Clamp(player2Velocity * 0.98, -PLAYERS_MAX_VELOCITY, PLAYERS_MAX_VELOCITY); + + player1Y = Math.Clamp(player1Y, 0, _rect.height - PADDLE_HEIGHT); + player2Y = Math.Clamp(player2Y, 0, _rect.height - PADDLE_HEIGHT); + + // Update ball position + ballX += ballVelocityX * deltaTime; + ballY += ballVelocityY * deltaTime; + + // Clamp ball velocity + ballVelocityX = Math.Clamp(ballVelocityX, -BALL_MAX_VELOCITY, BALL_MAX_VELOCITY); + ballVelocityY = Math.Clamp(ballVelocityY, -BALL_MAX_VELOCITY, BALL_MAX_VELOCITY); + + // Ball collision with top and bottom + if (ballY <= 0 || ballY >= rect.height - BALL_SIZE) + { + ballVelocityY = -ballVelocityY; + } + + // Ball collision with paddles + if (ballX <= PADDLE_WIDTH && ballY >= player1Y && ballY <= player1Y + PADDLE_HEIGHT) + { + ballVelocityX = Math.Abs(ballVelocityX); // Bounce right + } + if (ballX >= rect.width - PADDLE_WIDTH - BALL_SIZE && ballY >= player2Y && ballY <= player2Y + PADDLE_HEIGHT) + { + ballVelocityX = -Math.Abs(ballVelocityX); // Bounce left + } + + // Reset ball and update score if it goes past paddles + if (ballX < 0) + { + player2Score++; + ResetBall(); + } + else if (ballX > rect.width) + { + player1Score++; + ResetBall(); + } + + player1UpPressed = false; + player1DownPressed = false; + player2UpPressed = false; + player2DownPressed = false; + + // NOTE (do not remove): + // - canvas coordinates are unrestricted and can draw to anywhere on the entire window + // - rect is the local coordinates of the parent element, use it to keep the drawing of shapes within the parent UI element + + // Draw center line + // canvas.DashedLine(rect.x + rect.width / 2, rect.y, rect.x + rect.width / 2, rect.y + rect.height, System.Drawing.Color.White, 2, 5); + + // paddle 1 + canvas.RectFilled(rect.x, rect.y + player1Y, PADDLE_WIDTH, PADDLE_HEIGHT, System.Drawing.Color.White); + + // ball (now using ball position) + canvas.CircleFilled(rect.x + ballX, rect.y + ballY, BALL_SIZE, System.Drawing.Color.White); + + // paddle 2 + canvas.RectFilled(rect.x - PADDLE_WIDTH + rect.width, rect.y + player2Y, PADDLE_WIDTH, PADDLE_HEIGHT, System.Drawing.Color.White); + + // Draw score at the top + canvas.DrawText(player1Score.ToString(), rect.x + (rect.width / 4), rect.y + 30, Themes.baseContent, _scoreLayoutSettings); + canvas.DrawText(player2Score.ToString(), rect.x + (rect.width * 3 / 4), rect.y + 30, Themes.baseContent, _scoreLayoutSettings); + }); + } + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/SettingsTab.cs b/Samples/Shared/Tabs/SettingsTab.cs new file mode 100644 index 0000000..a947254 --- /dev/null +++ b/Samples/Shared/Tabs/SettingsTab.cs @@ -0,0 +1,14 @@ +using Prowl.PaperUI; + +namespace Shared.Tabs +{ + public class SettingsTab : Tab + { + public SettingsTab(Paper gui) : base(gui) + { + title = "Settings"; + id = "settings"; + width = 70; + } + } +} \ No newline at end of file diff --git a/Samples/Shared/Tabs/SlimeFriendsTab.cs b/Samples/Shared/Tabs/SlimeFriendsTab.cs new file mode 100644 index 0000000..a254c6f --- /dev/null +++ b/Samples/Shared/Tabs/SlimeFriendsTab.cs @@ -0,0 +1,338 @@ +using Prowl.PaperUI; +using Prowl.Vector; +using Prowl.Scribe; +using Prowl.Quill; + +namespace Shared.Tabs +{ + public class SlimeFriendsTab : Tab + { + const int SLIME_SIZE = 30; + const double BASE_SLIME_SPEED = 80.0; + const double HOP_DURATION = 1.0; + const double AVOIDANCE_RADIUS = 60.0; + const double AVOIDANCE_STRENGTH = 200.0; + const double HEART_DISPLAY_DURATION = 2.0; + const double SPAWN_BUTTON_WIDTH = 120; + const double SPAWN_BUTTON_HEIGHT = 40; + const double SPAWN_BUTTON_X = 10; + const double SPAWN_BUTTON_Y = 10; + + struct Slime + { + public Vector2 position; + public System.Drawing.Color color; + public string name; + public double hopProgress; + public double speed; + public double heartTimer; + + public Slime(Vector2 position, System.Drawing.Color color, string name, double speed) + { + this.position = position; + this.color = color; + this.name = name; + this.hopProgress = 0; + this.speed = speed; + this.heartTimer = 0; + } + } + + struct GrassBlade + { + public double x; + public double y; + public double height; + } + + struct Flower + { + public double x; + public double y; + public System.Drawing.Color color; + } + + List slimes = new List(); + List grassBlades = new List(); + List flowers = new List(); + Vector2 pointerPosition = Vector2.zero; + + TextLayoutSettings nameLayout = new TextLayoutSettings + { + Font = Fonts.arial, + PixelSize = 12, + Alignment = Prowl.Scribe.TextAlignment.Center + }; + + TextLayoutSettings buttonLayout = new TextLayoutSettings + { + Font = Fonts.arial, + PixelSize = 14, + Alignment = Prowl.Scribe.TextAlignment.Center + }; + + string[] slimeNames = { "Blob", "Gooey", "Squishy", "Bounce", "Jelly", "Puddle", "Glop", "Wobble", "Splat", "Mochi" }; + int nameIndex = 0; + + public SlimeFriendsTab(Paper gui) : base(gui) + { + title = "Slime Friends"; + id = "slimefriends"; + width = 110; + + // Initialize background elements + System.Random rand = new System.Random(42); + System.Drawing.Color[] flowerColors = { + System.Drawing.Color.FromArgb(255, 182, 193), + System.Drawing.Color.FromArgb(255, 255, 102), + System.Drawing.Color.FromArgb(186, 143, 255) + }; + + // Generate grass blades (relative positions 0-1) + for (int i = 0; i < 30; i++) + { + grassBlades.Add(new GrassBlade + { + x = rand.NextDouble(), + y = 0.6 + rand.NextDouble() * 0.4, + height = 10 + rand.NextDouble() * 15 + }); + } + + // Generate flowers (relative positions 0-1) + for (int i = 0; i < 10; i++) + { + flowers.Add(new Flower + { + x = rand.NextDouble(), + y = 0.65 + rand.NextDouble() * 0.3, + color = flowerColors[rand.Next(flowerColors.Length)] + }); + } + + // Start with only 1 slime + rand = new System.Random(); + slimes.Add(new Slime( + new Vector2(250, 200), + System.Drawing.Color.FromArgb( + rand.Next(100, 255), + rand.Next(100, 255), + rand.Next(100, 255) + ), + slimeNames[nameIndex++], + BASE_SLIME_SPEED * (0.7 + rand.NextDouble() * 0.6) + )); + } + + private double CircleSDF(Vector2 point, Vector2 center, double radius) + { + return (point - center).magnitude - radius; + } + + private void DrawPrairieBackground(Canvas canvas, Rect rect) + { + // Sky + canvas.RectFilled(rect.x, rect.y, rect.width, rect.height * 0.2, System.Drawing.Color.FromArgb(135, 206, 235)); + + // Ground + canvas.RectFilled(rect.x, rect.y + rect.height * 0.2, rect.width, rect.height * 0.8, System.Drawing.Color.FromArgb(124, 185, 82)); + + // Draw grass blades + foreach (var grass in grassBlades) + { + double x = rect.x + grass.x * rect.width; + double y = rect.y + grass.y * rect.height; + + canvas.BeginPath(); + canvas.LineTo(x, y); + canvas.LineTo(x, y - grass.height); + canvas.SetStrokeColor(System.Drawing.Color.FromArgb(85, 155, 60)); + canvas.SetStrokeWidth(2); + canvas.Stroke(); + } + + // Draw flowers + foreach (var flower in flowers) + { + double x = rect.x + flower.x * rect.width; + double y = rect.y + flower.y * rect.height; + + // Stem + canvas.BeginPath(); + canvas.LineTo(x, y); + canvas.LineTo(x, y - 15); + canvas.SetStrokeColor(System.Drawing.Color.FromArgb(85, 155, 60)); + canvas.SetStrokeWidth(2); + canvas.Stroke(); + + // Petals + canvas.CircleFilled(x, y - 15, 4, flower.color); + } + } + + private void DrawHeart(Canvas canvas, double x, double y, double size) + { + System.Drawing.Color heartColor = System.Drawing.Color.FromArgb(255, 105, 180); + double offset = size * 0.3; + + canvas.CircleFilled(x - offset, y, size * 0.5, heartColor); + canvas.CircleFilled(x + offset, y, size * 0.5, heartColor); + canvas.CircleFilled(x, y, size * 2, heartColor); + } + + private void SpawnNewSlime() + { + System.Random rand = new System.Random(); + slimes.Add(new Slime( + new Vector2(rand.Next(50, 500), rand.Next(50, 300)), + System.Drawing.Color.FromArgb( + rand.Next(100, 255), + rand.Next(100, 255), + rand.Next(100, 255) + ), + slimeNames[nameIndex % slimeNames.Length], + BASE_SLIME_SPEED * (0.7 + rand.NextDouble() * 0.6) + )); + nameIndex++; + } + + public override void Draw() + { + using (Gui.Box("Container") + .Margin(5) + .Focused + .BorderWidth(2) + .BorderColor(Themes.primary) + .End() + .OnHover((e) => + { + pointerPosition = e.PointerPosition; + }) + .Enter()) + { + Gui.AddActionElement((canvas, rect) => + { + DrawPrairieBackground(canvas, rect); + + var deltaTime = Gui.DeltaTime; + Vector2 localPointerPosition = new Vector2( + pointerPosition.x - rect.x, + pointerPosition.y - rect.y + ); + + // Handle slime clicks + if (Gui.IsPointerPressed(PaperMouseBtn.Left)) + { + for (int i = 0; i < slimes.Count; i++) + { + var slime = slimes[i]; + double distance = (localPointerPosition - slime.position).magnitude; + + if (distance <= SLIME_SIZE) + { + slime.heartTimer = HEART_DISPLAY_DURATION; + slimes[i] = slime; + } + } + } + + // Update and draw slimes + for (int i = 0; i < slimes.Count; i++) + { + var slime = slimes[i]; + + if (slime.heartTimer > 0) + { + slime.heartTimer -= deltaTime; + } + + Vector2 direction = localPointerPosition - slime.position; + if (direction.magnitude > 1) + { + direction = direction.normalized; + Vector2 moveVector = direction * slime.speed; + + // SDF-based collision avoidance + for (int j = 0; j < slimes.Count; j++) + { + if (i != j) + { + double sdfDistance = CircleSDF(slime.position, slimes[j].position, SLIME_SIZE); + + if (sdfDistance < AVOIDANCE_RADIUS) + { + Vector2 avoidanceDirection = (slime.position - slimes[j].position).normalized; + double avoidanceFactor = Math.Max(0, 1.0 - (sdfDistance / AVOIDANCE_RADIUS)); + moveVector += avoidanceDirection * AVOIDANCE_STRENGTH * avoidanceFactor; + } + } + } + + slime.position += moveVector * deltaTime; + } + + // Hop animation + slime.hopProgress = (slime.hopProgress + deltaTime) % HOP_DURATION; + float scale = 1.0f + (float)Math.Sin(slime.hopProgress * (Math.PI * 2 / HOP_DURATION)) * 0.2f; + + // Draw slime + int size = (int)(SLIME_SIZE * scale); + canvas.CircleFilled( + rect.x + slime.position.x, + rect.y + slime.position.y, + size, + slime.color + ); + + // Draw heart if active + if (slime.heartTimer > 0) + { + DrawHeart( + canvas, + rect.x + slime.position.x, + rect.y + slime.position.y - size - 20, + 10 + ); + } + + // Draw name + canvas.DrawText( + slime.name, + rect.x + slime.position.x, + rect.y + slime.position.y - size - 10, + System.Drawing.Color.White, + nameLayout + ); + + slimes[i] = slime; + } + + // Draw spawn button + double buttonX = rect.x + SPAWN_BUTTON_X; + double buttonY = rect.y + SPAWN_BUTTON_Y; + }); + } + + // Spawn button click handler + using (Gui.Box("SpawnButton") + .PositionType(PositionType.SelfDirected) + .Left(SPAWN_BUTTON_X) + .Top(SPAWN_BUTTON_Y) + .BackgroundColor(System.Drawing.Color.Violet).Rounded(5) + .Hovered + .BackgroundColor(System.Drawing.Color.Pink) + .End() + .Text("Spawn Slime", Fonts.arial).Alignment(Prowl.PaperUI.TextAlignment.MiddleCenter) + .Width(SPAWN_BUTTON_WIDTH) + .Height(SPAWN_BUTTON_HEIGHT) + .OnClick((e) => SpawnNewSlime()) + .Enter()) + { + + } + } + + public override void Focus() { } + public override void Blur() { } + } +} \ No newline at end of file