Compare commits

...

2 Commits

7 changed files with 243 additions and 22 deletions

@ -12,13 +12,22 @@ namespace Dashboard.Controls
public bool IsReadOnly => false; public bool IsReadOnly => false;
public event EventHandler<ChildEventArgs>? ChildAdded;
public event EventHandler<ChildEventArgs>? ChildRemoved;
public void Add(Control item) public void Add(Control item)
{ {
children.Add(item); children.Add(item);
OnChildAdded(new ChildEventArgs(item));
} }
public void Clear() public void Clear()
{ {
foreach (Control child in this)
{
OnChildRemoved(new ChildEventArgs(child));
}
children.Clear(); children.Clear();
} }
@ -39,7 +48,25 @@ namespace Dashboard.Controls
public bool Remove(Control item) public bool Remove(Control item)
{ {
return children.Remove(item); if (children.Remove(item))
{
OnChildRemoved(new ChildEventArgs(item));
return true;
}
return false;
}
public virtual void OnChildAdded(ChildEventArgs ea)
{
Adopt(ea.Child, this);
ChildAdded?.Invoke(this, ea);
}
public virtual void OnChildRemoved(ChildEventArgs ea)
{
Adopt(ea.Child, null);
ChildRemoved?.Invoke(this, ea);
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
@ -47,4 +74,13 @@ namespace Dashboard.Controls
return children.GetEnumerator(); return children.GetEnumerator();
} }
} }
public class ChildEventArgs : EventArgs
{
public Control Child { get; }
public ChildEventArgs(Control child)
{
Child = child;
}
}
} }

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Dashboard.ImmediateDraw; using Dashboard.ImmediateDraw;
using OpenTK.Mathematics;
namespace Dashboard.Controls namespace Dashboard.Controls
{ {
@ -8,6 +9,18 @@ namespace Dashboard.Controls
{ {
private readonly DrawList drawCommands = new DrawList(); private readonly DrawList drawCommands = new DrawList();
public string? Id { get; set; }
public override Vector2 Position
{
get => base.Position;
set
{
base.Position = value;
InvalidateLayout();
}
}
public Style Style { get; set; } = new Style(); public Style Style { get; set; } = new Style();
public float Padding public float Padding
{ {
@ -20,6 +33,8 @@ namespace Dashboard.Controls
protected bool IsLayoutSuspended { get; private set; } = false; protected bool IsLayoutSuspended { get; private set; } = false;
protected ResizedEventArgs? LastResizeEvent { get; private set; }
public void InvalidateVisual() public void InvalidateVisual()
{ {
IsVisualsValid = false; IsVisualsValid = false;
@ -72,6 +87,14 @@ namespace Dashboard.Controls
cmd.Splice(drawCommands); cmd.Splice(drawCommands);
if (this is IEnumerable<Control> children)
{
foreach (Control child in children)
{
child.Paint(cmd);
}
}
cmd.PopViewport(); cmd.PopViewport();
cmd.PopStyle(); cmd.PopStyle();
} }
@ -108,18 +131,12 @@ namespace Dashboard.Controls
LayoutValidated?.Invoke(sender, ea); LayoutValidated?.Invoke(sender, ea);
} }
protected void ValidateChildrenLayout() public override void OnResized(object sender, ResizedEventArgs ea)
{ {
if (this is IEnumerable<Control> enumerable) base.OnResized(sender, ea);
{
foreach (Control child in enumerable)
{
if (child.IsLayoutValid)
continue;
child.ValidateLayout(); LastResizeEvent = ea;
} InvalidateLayout();
}
} }
} }
} }

@ -17,7 +17,6 @@ namespace Dashboard.Controls
protected override void ValidateLayout() protected override void ValidateLayout()
{ {
ValidateChildrenLayout();
} }
protected override void ValidateVisual(DrawList cmd) protected override void ValidateVisual(DrawList cmd)

@ -0,0 +1,31 @@
using Dashboard.ImmediateDraw;
using Dashboard.Layout;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
public class GridBox : ContainerControl
{
protected override void ValidateLayout()
{
if (LastResizeEvent != null)
{
foreach (Control child in this)
{
GridLayoutAttribute attribute = GridLayoutAttribute.GetGridLayout(child);
attribute.Evaluate(LastResizeEvent, child.Bounds, out Rectangle newBounds);
child.Bounds = newBounds;
child.InvalidateLayout();
}
}
}
protected override void ValidateVisual(DrawList cmd)
{
cmd.Rectangle(new Rectangle(Size, Vector2.Zero));
}
}
}

@ -6,15 +6,15 @@ using OpenTK.Mathematics;
namespace Dashboard.Controls namespace Dashboard.Controls
{ {
/// <summary> /// <summary>
/// Bases for all UI elements. /// Bases for all UI elements.
/// </summary> /// </summary>
public abstract class UIBase : IDbUserdata public abstract class UIBase : IDbAttribute
{ {
private Vector2 size; private Vector2 size;
public UIBase? Parent { get; protected set; } public UIBase? Parent { get; protected set; }
public string? Id { get; set; }
public Rectangle Bounds public Rectangle Bounds
{ {
get => new Rectangle(Position + Size, Position); get => new Rectangle(Position + Size, Position);
@ -25,7 +25,7 @@ namespace Dashboard.Controls
} }
} }
public Vector2 Position { get; set; } public virtual Vector2 Position { get; set; }
public Vector2 Size public Vector2 Size
{ {
@ -60,7 +60,7 @@ namespace Dashboard.Controls
public bool IsMaximumSizeSet => MaximumSize != new Vector2(-1, -1); public bool IsMaximumSizeSet => MaximumSize != new Vector2(-1, -1);
public bool IsMinimumSizeSet => MinimumSize != new Vector2(-1, -1); public bool IsMinimumSizeSet => MinimumSize != new Vector2(-1, -1);
public Dictionary<string, object> Userdata { get; } = new Dictionary<string, object>(); public Dictionary<string, object> Attributes { get; } = new Dictionary<string, object>();
public virtual void NotifyEvent(object? sender, EventArgs args) public virtual void NotifyEvent(object? sender, EventArgs args)
{ {
@ -85,17 +85,23 @@ namespace Dashboard.Controls
} }
public event EventHandler<ResizedEventArgs>? Resized; public event EventHandler<ResizedEventArgs>? Resized;
public event EventHandler<AdoptedEventArgs>? Adopted;
public virtual void OnResized(object sender, ResizedEventArgs ea) public virtual void OnResized(object sender, ResizedEventArgs ea)
{ {
Resized?.Invoke(sender, ea); Resized?.Invoke(sender, ea);
} }
protected virtual void OnAdopted(UIBase? parent)
{
Adopted?.Invoke(this, new AdoptedEventArgs(parent));
}
public bool IsDisposed { get; private set; } = false; public bool IsDisposed { get; private set; } = false;
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
foreach (object userdata in Userdata.Values) foreach (object userdata in Attributes.Values)
{ {
if (userdata is IDisposable disposable) if (userdata is IDisposable disposable)
disposable.Dispose(); disposable.Dispose();
@ -113,6 +119,12 @@ namespace Dashboard.Controls
} }
public void Dispose() => DisposeInvoker(true); public void Dispose() => DisposeInvoker(true);
protected static void Adopt(UIBase child, UIBase? parent)
{
child.Parent = parent;
child.OnAdopted(parent);
}
} }
public class ResizedEventArgs : EventArgs public class ResizedEventArgs : EventArgs
@ -126,4 +138,14 @@ namespace Dashboard.Controls
OldSize = oldSize; OldSize = oldSize;
} }
} }
public class AdoptedEventArgs : EventArgs
{
public UIBase? Parent { get; }
public AdoptedEventArgs(UIBase? parent = null)
{
Parent = parent;
}
}
} }

@ -5,17 +5,17 @@ using System.Collections.Generic;
namespace Dashboard namespace Dashboard
{ {
/// <summary> /// <summary>
/// Common interface for Dashboard objects that accept userdata. /// Common interface for Dashboard objects that accept attributes.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Dashboard will call dispose on any and all objects which implement IDisposable. /// Dashboard will call dispose on any and all objects which implement IDisposable.
/// If this is an issue, please guard your object against this using a wrapper. /// If this is an issue, please guard your object against this using a wrapper.
/// </remarks /// </remarks
public interface IDbUserdata : IDisposable public interface IDbAttribute : IDisposable
{ {
/// <summary> /// <summary>
/// Userdata dictionary. /// Attribute dictionary.
/// </summary> /// </summary>
public Dictionary<string, object> Userdata { get; } public Dictionary<string, object> Attributes { get; }
} }
} }

@ -0,0 +1,116 @@
using OpenTK.Mathematics;
using Dashboard.Controls;
using System.Reflection.Metadata;
namespace Dashboard.Layout
{
/// <summary>
/// Control attributes for grid layout.
/// </summary>
public class GridLayoutAttribute
{
/// <summary>
/// An anchor will keep the relative distance of a control's edges the same during resizes.
/// </summary>
/// <remarks><see cref="Dock"/> has higher precedence.</remarks>
public Anchor Anchor { get; set; } = Anchor.Left | Anchor.Top;
/// <summary>
/// Dock will strongly attach a control to its container or its edges.
/// </summary>
/// <remarks>Has more precedence than <see cref="Anchor"/></remarks>
public Dock Dock { get; set; } = Dock.None;
public void Evaluate(
ResizedEventArgs parentResizeEvent,
in Rectangle oldBounds,
out Rectangle newBounds
)
{
switch (Dock)
{
default:
case Dock.None:
break;
case Dock.Top:
newBounds = new Rectangle(
parentResizeEvent.NewSize.X,
oldBounds.Size.Y,
0,
0
);
return;
case Dock.Bottom:
newBounds = new Rectangle(
parentResizeEvent.NewSize.X,
parentResizeEvent.NewSize.Y,
0,
oldBounds.Size.Y
);
return;
case Dock.Left:
newBounds = new Rectangle(
oldBounds.Size.X,
parentResizeEvent.NewSize.Y,
0,
0
);
return;
case Dock.Right:
newBounds = new Rectangle(
parentResizeEvent.NewSize.X,
parentResizeEvent.NewSize.Y,
parentResizeEvent.NewSize.Y - oldBounds.Size.Y,
0
);
return;
}
Vector2 scale = parentResizeEvent.NewSize / parentResizeEvent.OldSize;
newBounds = oldBounds;
if (Anchor.HasFlag(Anchor.Top))
{
newBounds.Top = scale.Y * oldBounds.Top;
}
if (Anchor.HasFlag(Anchor.Left))
{
newBounds.Left = scale.X * oldBounds.Left;
}
if (Anchor.HasFlag(Anchor.Bottom))
{
float margin = scale.Y * (parentResizeEvent.OldSize.Y - newBounds.Bottom);
newBounds.Bottom = parentResizeEvent.NewSize.Y - margin;
}
if (Anchor.HasFlag(Anchor.Right))
{
float margin = scale.X * (parentResizeEvent.OldSize.X - newBounds.Right);
newBounds.Right = parentResizeEvent.NewSize.X - margin;
}
}
/// <summary>
/// Get the grid layout attribute associated with the given UI-base.
/// </summary>
/// <param name="uiBase">The UI-base to query.</param>
/// <returns>It's grid layout attribute, if any.</returns>
public static GridLayoutAttribute GetGridLayout(UIBase uiBase)
{
const string GRID_LAYOUT_ATTRIBUTE_KEY = "486ddf8c-b75f-4ad4-a51d-5ba20db9bd0e";
if (
!uiBase.Attributes.TryGetValue(GRID_LAYOUT_ATTRIBUTE_KEY, out object? attribute)
|| attribute is not GridLayoutAttribute)
{
attribute = new GridLayoutAttribute();
uiBase.Attributes[GRID_LAYOUT_ATTRIBUTE_KEY] = attribute;
}
return (GridLayoutAttribute)attribute;
}
}
}