Quellcode durchsuchen

Rename panel to vertical, new horizontal

Jean-David Moisan vor 3 Jahren
Ursprung
Commit
669854b293

+ 3 - 3
Example/Game/GameRoot.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using Apos.Gui;
 using Apos.Input;
 using FontStashSharp;
@@ -50,7 +50,7 @@ namespace GameProject {
 
             _ui.UpdateAll(gameTime);
 
-            Panel.Push().XY = new Vector2(100, 100);
+            MenuPanel.Push().XY = new Vector2(100, 100);
             if (_menu == Menu.Main) {
                 Label.Put("Main Menu");
                 Label.Put($"Your name is '{_name}'");
@@ -68,7 +68,7 @@ namespace GameProject {
                 if (Button.Put("Yes").Clicked) Exit();
                 if (Button.Put("No").Clicked) _menu = Menu.Main;
             }
-            Panel.Pop();
+            MenuPanel.Pop();
 
             GuiHelper.UpdateCleanup();
             base.Update(gameTime);

+ 1 - 1
Example/Platforms/WindowsDX/WindowsDX.csproj

@@ -5,7 +5,7 @@
     <TargetFramework>net6-windows</TargetFramework>
     <AssemblyName>Example</AssemblyName>
     <MonoGamePlatform>Windows</MonoGamePlatform>
-    <PublishTrimmed>true</PublishTrimmed>
+    <PublishTrimmed>false</PublishTrimmed>
     <PublishReadyToRun>false</PublishReadyToRun>
     <TieredCompilation>false</TieredCompilation>
   </PropertyGroup>

+ 2 - 2
README.md

@@ -26,7 +26,7 @@ UI library for MonoGame.
 You can create a simple UI with the following code that you'll put in the Update call:
 
 ```csharp
-Panel.Push();
+MenuPanel.Push();
 if (Button.Put("Show fun").Clicked) {
     _showFun = !_showFun;
 }
@@ -36,7 +36,7 @@ if (_showFun) {
 if (Button.Put("Quit").Clicked) {
     Exit();
 }
-Panel.Pop();
+MenuPanel.Pop();
 ```
 
 The code above will create 2 buttons, "Show Fun" and "Quit". You can use your mouse, keyboard, or gamepad to interact with them. Clicking on "Show Fun" will insert a label in between them with the text "This is fun!".

+ 1 - 1
Source/Component.cs

@@ -61,7 +61,7 @@ namespace Apos.Gui {
             return this;
         }
 
-        public virtual Action<IComponent> GrabFocus { get; set; } = c => { };
+        public virtual Action<IComponent?> GrabFocus { get; set; } = c => { };
         public virtual void SendToTop() {
             Parent?.SendToTop(this);
         }

+ 1 - 1
Source/FloatingWindow.cs

@@ -4,7 +4,7 @@ using Microsoft.Xna.Framework;
 using MonoGame.Extended;
 
 namespace Apos.Gui {
-    public class FloatingWindow : Panel {
+    public class FloatingWindow : Vertical {
         public FloatingWindow(int id) : base(id) { }
 
         public override bool IsFocusable { get; set; } = true;

+ 1 - 0
Source/GuiHelper.cs

@@ -44,6 +44,7 @@ namespace Apos.Gui {
         public static float Scale {
             get => _scale;
             set {
+                // TODO: Delay scale change until next frame? It's not likely to be desirable to update before a layout refresh.
                 if (value > 0f) {
                     _scale = value;
                     _virtualScale = (float)Math.Ceiling(_scale);

+ 269 - 0
Source/Horizontal.cs

@@ -0,0 +1,269 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Apos.Input;
+using Track = Apos.Input.Track;
+using Microsoft.Xna.Framework;
+using System;
+using Apos.Tweens;
+
+namespace Apos.Gui {
+    public class Horizontal : Component, IParent {
+        public Horizontal(int id) : base(id) { }
+
+        public float OffsetX {
+            get => _offsetX;
+            set {
+                SetOffset(_offsetXTween, ClampOffsetX(value));
+            }
+        }
+        public float OffsetY {
+            get => _offsetY;
+            set {
+                SetOffset(_offsetYTween, ClampOffsetY(value));
+            }
+        }
+        public float FullWidth { get; set; } = 100;
+        public float FullHeight { get; set; } = 100;
+
+        public Vector2 OffsetXY {
+            get => new Vector2(OffsetX, OffsetY);
+            set {
+                OffsetX = value.X;
+                OffsetY = value.Y;
+            }
+        }
+        public Vector2 FullSize {
+            get => new Vector2(FullWidth, FullHeight);
+            set {
+                FullWidth = value.X;
+                FullHeight = value.Y;
+            }
+        }
+
+        public float ScrollIncrement { get; set; } = 50f;
+        public float ScrollSpeed { get; set; } = 0.25f;
+        public long ScrollMaxDuration { get; set; } = 1000;
+
+        public override void UpdatePrefSize(GameTime gameTime) {
+            float maxWidth = 0;
+            float maxHeight = 0;
+
+            foreach (var c in _children) {
+                c.UpdatePrefSize(gameTime);
+                maxWidth += c.PrefWidth;
+                maxHeight = MathHelper.Max(c.PrefHeight, maxHeight);
+            }
+
+            PrefWidth = maxWidth;
+            PrefHeight = maxHeight;
+        }
+        public override void UpdateSetup(GameTime gameTime) {
+            // TODO: Keep current focus in view if it's in view?
+
+            if (_offsetXTween.B != ClampOffsetY(_offsetXTween.B)) {
+                SetOffset(_offsetXTween, ClampOffsetY(_offsetXTween.B));
+            }
+
+            _offsetX = _offsetXTween.Value;
+            _offsetY = _offsetYTween.Value;
+
+            float maxWidth = Width;
+            float maxHeight = Height;
+
+            float currentX = 0f;
+            foreach (var c in _children) {
+                c.X = currentX + X + OffsetX;
+                c.Y = Y + OffsetY;
+                c.Width = c.PrefWidth;
+                c.Height = c.PrefHeight;
+
+                maxHeight = MathHelper.Max(c.PrefHeight, maxHeight);
+                c.Clip = c.Bounds.Intersection(Clip);
+
+                c.UpdateSetup(gameTime);
+
+                currentX += c.Width;
+            }
+
+            FullWidth = MathHelper.Max(currentX, maxWidth);
+            FullHeight = maxHeight;
+        }
+        public override void UpdateInput(GameTime gameTime) {
+            for (int i = _childrenRenderOrder.Count - 1; i >= 0; i--) {
+                _childrenRenderOrder[i].UpdateInput(gameTime);
+            }
+
+            // TODO: If we don't scroll, don't consume the scroll to bubble the event up. Allows recursive scrolling.
+            if (Clip.Contains(GuiHelper.Mouse) && Track.MouseCondition.Scrolled()) {
+                SetOffset(_offsetXTween, ClampOffsetX(_offsetXTween.B + Math.Sign(MouseCondition.ScrollDelta) * ScrollIncrement));
+            }
+
+            // TODO: Consume clicks on the panel? Otherwise it's possible to click stuff under it.
+        }
+        public override void Update(GameTime gameTime) {
+            foreach (var c in _children)
+                c.Update(gameTime);
+        }
+        public override void Draw(GameTime gameTime) {
+            foreach (var c in _childrenRenderOrder) {
+                if (Clip.Intersects(c.Clip)) {
+                    c.Draw(gameTime);
+                }
+            }
+
+            // TODO: Draw scrollbars if needed?
+        }
+
+        public virtual void Add(IComponent c) {
+            c.Parent = this;
+            _children.Insert(c.Index, c);
+
+            // TODO: Optimize this?
+            _childrenRenderOrder.Add(c);
+            _childrenRenderOrder.Sort((a, b) => {
+                if (a.IsFloatable && b.IsFloatable) {
+                    return 0;
+                } else if (!a.IsFloatable && !b.IsFloatable) {
+                    return a.Index.CompareTo(b.Index);
+                } else if (a.IsFloatable) {
+                    return 1;
+                } else {
+                    return -1;
+                }
+            });
+        }
+        public virtual void Remove(IComponent c) {
+            c.Parent = null;
+            _children.Remove(c);
+            _childrenRenderOrder.Remove(c);
+        }
+        public virtual void Reset() {
+            _nextChildIndex = 0;
+        }
+        public virtual int NextIndex() {
+            return _nextChildIndex++;
+        }
+
+        /// <summary>
+        /// If this component has a parent, it will ask the parent to return this component's previous neighbor.
+        /// If it has children, it will return the last one.
+        /// Otherwise it will return itself.
+        /// </summary>
+        public override IComponent GetPrev() {
+            return Parent != null ? Parent.GetPrev(this) : _children.Count > 0 ? _children.Last().GetLast() : this;
+        }
+        /// <summary>
+        /// If this component has children, it will return the first one.
+        /// If it has a parent it will ask the parent to return this component's next neighbor.
+        /// Otherwise, it will return itself.
+        /// </summary>
+        public override IComponent GetNext() {
+            return _children.Count > 0 ? _children.First() : Parent != null ? Parent.GetNext(this) : this;
+        }
+        /// <summary>
+        /// If the child isn't the first one, it will return the child before it.
+        /// Otherwise it will return itself.
+        /// </summary>
+        public virtual IComponent GetPrev(IComponent c) {
+            int index = c.Index - 1;
+            return index >= 0 ? _children[index].GetLast() : this;
+        }
+        /// <summary>
+        /// If the child isn't the last one, it will return the child after it.
+        /// If it has a parent, it will ask the parent to return this component's next neighbor.
+        /// Otherwise it will return itself.
+        /// </summary>
+        public virtual IComponent GetNext(IComponent c) {
+            int index = c.Index + 1;
+            return index < _children.Count ? _children[index] : Parent != null ? Parent.GetNext(this) : this;
+        }
+        /// <summary>
+        /// Returns the last child in this component tree.
+        /// </summary>
+        public virtual IComponent GetLast() {
+            return _children.Count > 0 ? _children.Last().GetLast() : this;
+        }
+
+        public virtual void SendToTop(IComponent c) {
+            if (c.IsFloatable) {
+                _childrenRenderOrder.Remove(c);
+                _childrenRenderOrder.Add(c);
+            }
+
+            if (c.X < X) {
+                float xDiff = X - c.X;
+                float oDiff = _offsetXTween.B - _offsetX;
+                SetOffset(_offsetXTween, ClampOffsetX(_offsetXTween.B + xDiff - oDiff));
+            }
+            if (c.Right > Right) {
+                float xDiff = Right - c.Right;
+                float oDiff = _offsetXTween.B - _offsetX;
+                SetOffset(_offsetXTween, ClampOffsetX(_offsetXTween.B + xDiff - oDiff));
+            }
+
+            Parent?.SendToTop(this);
+        }
+
+        protected virtual float ClampOffsetX(float x) {
+            return MathHelper.Min(MathHelper.Max(x, Width - FullWidth), 0f);
+        }
+        protected virtual float ClampOffsetY(float y) {
+            return MathHelper.Min(MathHelper.Max(y, Height - FullHeight), 0f);
+        }
+
+        protected void SetOffset(FloatTween ft, float b) {
+            var a = ft.Value;
+            ft.StartTime = TweenHelper.TotalMS;
+            ft.A = a;
+            ft.B = b;
+            ft.Duration = GetDuration(a, b, ScrollSpeed, ScrollMaxDuration);
+        }
+
+        protected long GetDuration(float a, float b, float speed, long maxDuration) {
+            return (long)Math.Min(Math.Abs((b - a) / speed), maxDuration);
+        }
+
+        public static Horizontal Push([CallerLineNumber] int id = 0, bool isAbsoluteId = false) {
+            // 1. Check if horizontal with id already exists.
+            //      a. If already exists. Get it.
+            //      b  If not, create it.
+            // 3. Push it on the stack.
+            // 4. Ping it.
+            id = GuiHelper.CurrentIMGUI.CreateId(id, isAbsoluteId);
+            GuiHelper.CurrentIMGUI.TryGetValue(id, out IComponent c);
+
+            Horizontal a;
+            if (c is Horizontal) {
+                a = (Horizontal)c;
+            } else {
+                a = new Horizontal(id);
+            }
+
+            IParent parent = GuiHelper.CurrentIMGUI.GrabParent(a);
+
+            if (a.LastPing != InputHelper.CurrentFrame) {
+                a.Reset();
+                a.LastPing = InputHelper.CurrentFrame;
+                a.Index = parent.NextIndex();
+            }
+
+            GuiHelper.CurrentIMGUI.Push(a);
+
+            return a;
+        }
+        public static void Pop() {
+            GuiHelper.CurrentIMGUI.Pop();
+        }
+
+        protected int _nextChildIndex = 0;
+        protected List<IComponent> _children = new List<IComponent>();
+        protected List<IComponent> _childrenRenderOrder = new List<IComponent>();
+
+        protected FloatTween _offsetXTween = new FloatTween(0f, 0f, 0, Easing.ExpoOut);
+        protected FloatTween _offsetYTween = new FloatTween(0f, 0f, 0, Easing.ExpoOut);
+
+        protected float _offsetX = 0;
+        protected float _offsetY = 0;
+    }
+}

+ 82 - 1
Source/IMGUI.cs

@@ -6,7 +6,7 @@ using Microsoft.Xna.Framework;
 
 namespace Apos.Gui {
     // NOTE: IMGUI is NOT recursive. It should always be the top level component.
-    public class IMGUI : Panel {
+    public class IMGUI : Component, IParent {
         public IMGUI() : base(0) {
             _currentParent = this;
             _activeComponents.Add(Id, this);
@@ -254,6 +254,83 @@ namespace Apos.Gui {
             }
         }
 
+        public virtual void Add(IComponent c) {
+            c.Parent = this;
+            _children.Insert(c.Index, c);
+
+            // TODO: Optimize this?
+            _childrenRenderOrder.Add(c);
+            _childrenRenderOrder.Sort((a, b) => {
+                if (a.IsFloatable && b.IsFloatable) {
+                    return 0;
+                } else if (!a.IsFloatable && !b.IsFloatable) {
+                    return a.Index.CompareTo(b.Index);
+                } else if (a.IsFloatable) {
+                    return 1;
+                } else {
+                    return -1;
+                }
+            });
+        }
+        public virtual void Remove(IComponent c) {
+            c.Parent = null;
+            _children.Remove(c);
+            _childrenRenderOrder.Remove(c);
+        }
+        public virtual void Reset() {
+            _nextChildIndex = 0;
+        }
+        public virtual int NextIndex() {
+            return _nextChildIndex++;
+        }
+
+        /// <summary>
+        /// If this component has a parent, it will ask the parent to return this component's previous neighbor.
+        /// If it has children, it will return the last one.
+        /// Otherwise it will return itself.
+        /// </summary>
+        public override IComponent GetPrev() {
+            return Parent != null ? Parent.GetPrev(this) : _children.Count > 0 ? _children.Last().GetLast() : this;
+        }
+        /// <summary>
+        /// If this component has children, it will return the first one.
+        /// If it has a parent it will ask the parent to return this component's next neighbor.
+        /// Otherwise, it will return itself.
+        /// </summary>
+        public override IComponent GetNext() {
+            return _children.Count > 0 ? _children.First() : Parent != null ? Parent.GetNext(this) : this;
+        }
+        /// <summary>
+        /// If the child isn't the first one, it will return the child before it.
+        /// Otherwise it will return itself.
+        /// </summary>
+        public virtual IComponent GetPrev(IComponent c) {
+            int index = c.Index - 1;
+            return index >= 0 ? _children[index].GetLast() : this;
+        }
+        /// <summary>
+        /// If the child isn't the last one, it will return the child after it.
+        /// If it has a parent, it will ask the parent to return this component's next neighbor.
+        /// Otherwise it will return itself.
+        /// </summary>
+        public virtual IComponent GetNext(IComponent c) {
+            int index = c.Index + 1;
+            return index < _children.Count ? _children[index] : Parent != null ? Parent.GetNext(this) : this;
+        }
+        /// <summary>
+        /// Returns the last child in this component tree.
+        /// </summary>
+        public virtual IComponent GetLast() {
+            return _children.Count > 0 ? _children.Last().GetLast() : this;
+        }
+
+        public virtual void SendToTop(IComponent c) {
+            if (c.IsFloatable) {
+                _childrenRenderOrder.Remove(c);
+                _childrenRenderOrder.Add(c);
+            }
+        }
+
         private void Cleanup() {
             Reset();
             foreach (var kc in _activeComponents.Reverse()) {
@@ -351,5 +428,9 @@ namespace Apos.Gui {
         private bool _isTick0 = true;
         private Queue<Action> _nextTick0 = new Queue<Action>();
         private Queue<Action> _nextTick1 = new Queue<Action>();
+
+        private int _nextChildIndex = 0;
+        private List<IComponent> _children = new List<IComponent>();
+        private List<IComponent> _childrenRenderOrder = new List<IComponent>();
     }
 }

+ 3 - 6
Source/MenuPanel.cs

@@ -4,7 +4,7 @@ using Apos.Tweens;
 using Microsoft.Xna.Framework;
 
 namespace Apos.Gui {
-    public class MenuPanel : Panel {
+    public class MenuPanel : Vertical {
         public MenuPanel(int id) : base(id) { }
 
         public override void UpdatePrefSize(GameTime gameTime) {
@@ -64,8 +64,8 @@ namespace Apos.Gui {
             return MathHelper.Min(MathHelper.Max(y, Height - FullHeight), FullHeight < Height ? Height / 2f - FullHeight / 2f : 0f);
         }
 
-        public static MenuPanel Push([CallerLineNumber] int id = 0, bool isAbsoluteId = false) {
-            // 1. Check if ScreenPanel with id already exists.
+        public static new MenuPanel Push([CallerLineNumber] int id = 0, bool isAbsoluteId = false) {
+            // 1. Check if MenuPanel with id already exists.
             //      a. If already exists. Get it.
             //      b  If not, create it.
             // 3. Push it on the stack.
@@ -92,9 +92,6 @@ namespace Apos.Gui {
 
             return a;
         }
-        public static void Pop() {
-            GuiHelper.CurrentIMGUI.Pop();
-        }
 
         protected bool _snap = false;
     }

+ 1 - 0
Source/Textbox.cs

@@ -35,6 +35,7 @@ namespace Apos.Gui {
         public int FontSize {
             get => _fontSize;
             set {
+                // TODO: Only change size on next loop since it changes the layout.
                 if (value != _fontSize) {
                     _fontSize = value;
                     _size = GuiHelper.MeasureString(_text, _fontSize);

+ 8 - 8
Source/Panel.cs → Source/Vertical.cs

@@ -8,8 +8,8 @@ using System;
 using Apos.Tweens;
 
 namespace Apos.Gui {
-    public class Panel : Component, IParent {
-        public Panel(int id) : base(id) { }
+    public class Vertical : Component, IParent {
+        public Vertical(int id) : base(id) { }
 
         public float OffsetX {
             get => _offsetX;
@@ -224,8 +224,8 @@ namespace Apos.Gui {
             return (long)Math.Min(Math.Abs((b - a) / speed), maxDuration);
         }
 
-        public static Panel Push([CallerLineNumber] int id = 0, bool isAbsoluteId = false) {
-            // 1. Check if panel with id already exists.
+        public static Vertical Push([CallerLineNumber] int id = 0, bool isAbsoluteId = false) {
+            // 1. Check if vertical with id already exists.
             //      a. If already exists. Get it.
             //      b  If not, create it.
             // 3. Push it on the stack.
@@ -233,11 +233,11 @@ namespace Apos.Gui {
             id = GuiHelper.CurrentIMGUI.CreateId(id, isAbsoluteId);
             GuiHelper.CurrentIMGUI.TryGetValue(id, out IComponent c);
 
-            Panel a;
-            if (c is Panel) {
-                a = (Panel)c;
+            Vertical a;
+            if (c is Vertical) {
+                a = (Vertical)c;
             } else {
-                a = new Panel(id);
+                a = new Vertical(id);
             }
 
             IParent parent = GuiHelper.CurrentIMGUI.GrabParent(a);

+ 2 - 2
docs/getting-started.md

@@ -43,7 +43,7 @@ protected override void Update(GameTime gameTime) {
     _ui.UpdateAll(gameTime);
 
     // Create your UI.
-    Panel.Push();
+    MenuPanel.Push();
     if (Button.Put("Show fun").Clicked) {
         _showFun = !_showFun;
     }
@@ -53,7 +53,7 @@ protected override void Update(GameTime gameTime) {
     if (Button.Put("Quit").Clicked) {
         Exit();
     }
-    Panel.Pop();
+    MenuPanel.Pop();
 
     // Call UpdateCleanup at the end.
     GuiHelper.UpdateCleanup();