Compare commits

...

3 Commits

36 changed files with 1956 additions and 412 deletions

View File

@@ -41,35 +41,6 @@ namespace Dashboard.BlurgText.OpenGL
Dispose(false);
}
public BlurgFontProxy InternFont(IFont font)
{
BlurgTextExtension appExtension = Application.ExtensionRequire<BlurgTextExtension>();
if (font is FontInfo fontInfo)
{
return (BlurgFontProxy)appExtension.Load(fontInfo);
}
else if (font is BlurgFontProxy blurgFont)
{
if (blurgFont.Owner != Blurg)
{
if (blurgFont.Path == null)
return (BlurgFontProxy)appExtension.Load(new FontInfo(blurgFont.Family, blurgFont.Weight,
blurgFont.Slant,
blurgFont.Stretch));
else
return (BlurgFontProxy)appExtension.Load(blurgFont.Path);
}
else
{
return blurgFont;
}
}
else
{
throw new Exception("Unsupported font resource.");
}
}
private void UseProgram()
{
if (_program != 0)
@@ -150,7 +121,7 @@ namespace Dashboard.BlurgText.OpenGL
if (result.Count == 0)
return;
Matrix4x4 view = Matrix4x4.CreateTranslation(position) * Context.ExtensionRequire<IDeviceContextBase>().Transforms;
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
List<DrawCall> drawCalls = new List<DrawCall>();
List<Vertex> vertices = new List<Vertex>();

View File

@@ -11,7 +11,7 @@ namespace Dashboard.BlurgText
public BlurgDcExtension CreateExtension(BlurgTextExtension appExtension, DeviceContext dc);
}
public class BlurgTextExtension(IBlurgDcExtensionFactory dcExtensionFactory) : IApplicationExtension, IFontLoader
public class BlurgTextExtension(IBlurgDcExtensionFactory dcExtensionFactory) : IFontLoader
{
private readonly Blurg _blurg = new Blurg(GlobalTextureAllocation, GlobalTextureUpdate);
@@ -27,6 +27,7 @@ namespace Dashboard.BlurgText
{
Context = context;
context.DeviceContextCreated += OnDeviceContextCreated;
_blurg.EnableSystemFonts();
}
void IContextExtensionBase.Require(IContextBase context) => Require((Application)context);
@@ -130,7 +131,7 @@ namespace Dashboard.BlurgText
}
}
public abstract class BlurgDcExtension : IDeviceContextExtension
public abstract class BlurgDcExtension : IDeviceContextExtension, ITextRenderer
{
public abstract Blurg Blurg { get; }
public abstract string DriverName { get; }
@@ -151,6 +152,35 @@ namespace Dashboard.BlurgText
DrawBlurgResult(result, position);
}
protected BlurgFontProxy InternFont(IFont font)
{
BlurgTextExtension appExtension = Application.ExtensionRequire<BlurgTextExtension>();
if (font is FontInfo fontInfo)
{
return (BlurgFontProxy)appExtension.Load(fontInfo);
}
else if (font is BlurgFontProxy blurgFont)
{
if (blurgFont.Owner != Blurg)
{
if (blurgFont.Path == null)
return (BlurgFontProxy)appExtension.Load(new FontInfo(blurgFont.Family, blurgFont.Weight,
blurgFont.Slant,
blurgFont.Stretch));
else
return (BlurgFontProxy)appExtension.Load(blurgFont.Path);
}
else
{
return blurgFont;
}
}
else
{
throw new Exception("Unsupported font resource.");
}
}
public abstract void Dispose();
IContextBase IContextExtensionBase.Context => Context;
@@ -161,5 +191,20 @@ namespace Dashboard.BlurgText
}
void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context);
public Box2d MeasureText(IFont font, float size, string text)
{
BlurgFontProxy proxy = InternFont(font);
Vector2 sz = Blurg.MeasureString(proxy.Font, size * Context.ExtensionRequire<IDeviceContextBase>().Scale, text);
return new Box2d(0, 0, sz.X, sz.Y);
}
public void DrawText(Vector2 position, Vector4 color, float size, IFont font, string text)
{
BlurgFontProxy proxy = InternFont(font);
BlurgResult? result = Blurg.BuildString(proxy.Font, size * Context.ExtensionRequire<IDeviceContextBase>().Scale,
new BlurgColor((byte)(color.X * 255), (byte)(color.Y * 255), (byte)(color.Z * 255), (byte)(color.W * 255)), text);
DrawBlurgResult(result, new Vector3(position, 0));
}
}
}

View File

@@ -82,7 +82,7 @@ namespace Dashboard.Collections
public void Clear() => _objects.Clear();
public IEnumerator<T> GetEnumerator() => _objects.Values.GetEnumerator();
public IEnumerator<T> GetEnumerator() => _objects.Values.Distinct().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@@ -0,0 +1,13 @@
using System.Drawing;
namespace Dashboard.Drawing
{
public abstract class Brush
{
}
public class SolidColorBrush(Color color) : Brush
{
public Color Color { get; } = color;
}
}

View File

@@ -6,15 +6,28 @@ namespace Dashboard.Drawing
{
public interface IDeviceContextBase : IDeviceContextExtension
{
RectangleF ClipRegion { get; }
Box2d ClipRegion { get; }
Box2d ScissorRegion { get; }
Matrix4x4 Transforms { get; }
float Scale { get; }
float ScaleOverride { get; set; }
void ResetClip();
void PushClip(RectangleF clipRegion);
void PushClip(Box2d clipRegion);
void PopClip();
void ResetScissor();
void PushScissor(Box2d scissorRegion);
void PopScissor();
void ResetTransforms();
void PushTransforms(in Matrix4x4 matrix);
void PopTransforms();
void ClearColor(Color color);
void ClearDepth();
int IncrementZ();
int DecrementZ();
}
}

View File

@@ -1,3 +1,5 @@
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public interface IFont : IDisposable
@@ -16,7 +18,7 @@ namespace Dashboard.Drawing
}
}
public interface IFontLoader
public interface IFontLoader : IApplicationExtension
{
IFont Load(FontInfo info);
IFont Load(string path);

View File

@@ -1,17 +1,17 @@
using System.Drawing;
using System.Numerics;
using Dashboard.Layout;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public record struct RectangleDrawInfo(Vector2 Position, ComputedBox Box, Brush Fill, Brush? Border = null);
public interface IImmediateMode : IDeviceContextExtension
{
void ClearColor(Color color);
void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color);
void Rectangle(Box2d rectangle, float depth, Vector4 color);
void Rectangle(in RectangleDrawInfo rectangle);
void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture);
}
}

View File

@@ -44,4 +44,16 @@ namespace Dashboard.Events
public ScanCode ScanCode { get; } = scanCode;
public ModifierKeys ModifierKeys { get; } = modifierKeys;
}
public class TextInputEventArgs(string text) : UiEventArgs(UiEventType.TextEdit)
{
public string Text { get; } = text;
}
public class TextEditEventArgs(string candidate, int cursor, int length) : UiEventArgs(UiEventType.TextEdit)
{
public string Candidate { get; } = candidate;
public int Cursor { get; } = cursor;
public int Length { get; } = length;
}
}

View File

@@ -76,8 +76,7 @@ namespace Dashboard.Events
}
}
public class ControlResizedEventArgs
public class ResizeEventArgs() : UiEventArgs(UiEventType.ControlResized)
{
}
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
using System.Numerics;
namespace Dashboard.Layout
{
public interface ILayoutItem : INotifyPropertyChanged
{
public LayoutInfo Layout { get; }
public Vector2 CalculateIntrinsicSize();
public Vector2 CalculateSize(Vector2 limits);
}
public interface ILayoutContainer : ILayoutItem, IEnumerable<ILayoutItem>
{
public ContainerLayoutInfo ContainerLayout { get; }
}
}

View File

@@ -0,0 +1,435 @@
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Dashboard.Layout
{
public readonly record struct ComputedBox(Vector4 Margin, Vector4 Padding, Vector4 Border, Vector2 Size)
{
public float MarginLeft => Margin.X;
public float MarginTop => Margin.Y;
public float MarginRight => Margin.Z;
public float MarginBottom => Margin.W;
public float PaddingLeft => Padding.X;
public float PaddingTop => Padding.Y;
public float PaddingRight => Padding.Z;
public float PaddingBottom => Padding.W;
public float BorderLeft => Border.X;
public float BorderTop => Border.Y;
public float BorderRight => Border.Z;
public float BorderBottom => Border.W;
public float Width => Size.X;
public float Height => Size.Y;
public Vector2 BoundingSize => new Vector2(
MarginLeft + BorderLeft + Width + BorderRight + MarginRight,
MarginTop + BorderTop + Height + BorderBottom + MarginBottom);
public Vector2 ContentSize => new Vector2(
Width - PaddingLeft - PaddingRight,
Height - PaddingTop - PaddingBottom);
public Vector4 ContentExtents => new Vector4(
PaddingLeft,
PaddingTop,
PaddingLeft + PaddingRight + Width,
PaddingTop + PaddingBottom + Height);
public Vector4 CornerRadii { get; init; } = Vector4.Zero;
}
public class LayoutBox : INotifyPropertyChanged
{
private Vector4 _margin = Vector4.Zero;
private Vector4 _padding = Vector4.Zero;
private Vector4 _border = Vector4.Zero;
private Vector2 _size = Vector2.Zero;
private Vector2 _minimumSize = -Vector2.One;
private Vector2 _maximumSize = -Vector2.One;
private Vector4 _cornerRadii = Vector4.Zero;
private LayoutUnit _marginLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _marginTopUnit = LayoutUnit.Pixel;
private LayoutUnit _marginRightUnit = LayoutUnit.Pixel;
private LayoutUnit _marginBottomUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingTopUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingRightUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingBottomUnit = LayoutUnit.Pixel;
private LayoutUnit _borderLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _borderTopUnit = LayoutUnit.Pixel;
private LayoutUnit _borderRightUnit = LayoutUnit.Pixel;
private LayoutUnit _borderBottomUnit = LayoutUnit.Pixel;
private LayoutUnit _widthUnit = LayoutUnit.Pixel;
private LayoutUnit _heightUnit = LayoutUnit.Pixel;
private LayoutUnit _minimumWidthUnit = LayoutUnit.Pixel;
private LayoutUnit _minimumHeightUnit = LayoutUnit.Pixel;
private LayoutUnit _maximumWidthUnit = LayoutUnit.Pixel;
private LayoutUnit _maximumHeightUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusTopLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusBottomLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusBottomRightUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusTopRightUnit = LayoutUnit.Pixel;
private ComputedBox _computedBox = new ComputedBox();
public bool UpdateRequired { get; private set; } = true;
public Vector4 Margin
{
get => _margin;
set => SetField(ref _margin, value);
}
public Vector4 Padding
{
get => _padding;
set => SetField(ref _padding, value);
}
public Vector4 Border
{
get => _border;
set => SetField(ref _border, value);
}
public Vector2 Size
{
get => _size;
set => SetField(ref _size, value);
}
public Vector2 MinimumSize
{
get => _minimumSize;
set => SetField(ref _minimumSize, value);
}
public Vector2 MaximumSize
{
get => _maximumSize;
set => SetField(ref _maximumSize, value);
}
public Vector4 CornerRadii
{
get => _cornerRadii;
set => SetField(ref _cornerRadii, value);
}
public float MarginLeft
{
get => Margin.X;
set => Margin = Margin with { X = value };
}
public float MarginTop
{
get => Margin.Y;
set => Margin = Margin with { Y = value };
}
public float MarginRight
{
get => Margin.Z;
set => Margin = Margin with { Z = value };
}
public float MarginBottom
{
get => Margin.W;
set => Margin = Margin with { W = value };
}
public float PaddingLeft
{
get => Padding.X;
set => Padding = Padding with { X = value };
}
public float PaddingTop
{
get => Padding.Y;
set => Padding = Padding with { Y = value };
}
public float PaddingRight
{
get => Padding.Z;
set => Padding = Padding with { Z = value };
}
public float PaddingBottom
{
get => Padding.W;
set => Padding = Padding with { W = value };
}
public float BorderLeft
{
get => Border.X;
set => Border = Border with { X = value };
}
public float BorderTop
{
get => Border.Y;
set => Border = Border with { Y = value };
}
public float BorderRight
{
get => Border.Z;
set => Border = Border with { Z = value };
}
public float BorderBottom
{
get => Border.W;
set => Border = Border with { W = value };
}
public float CornerRadiusTopLeft
{
get => CornerRadii.X;
set => CornerRadii = CornerRadii with { X = value };
}
public float CornerRadiusBottomLeft
{
get => CornerRadii.Y;
set => CornerRadii = CornerRadii with { Y = value };
}
public float CornerRadiusBottomRight
{
get => CornerRadii.Z;
set => CornerRadii = CornerRadii with { Z = value };
}
public float CornerRadiusTopRight
{
get => CornerRadii.W;
set => CornerRadii = CornerRadii with { W = value };
}
public LayoutUnit MarginLeftUnit
{
get => _marginLeftUnit;
set => SetField(ref _marginLeftUnit, value);
}
public LayoutUnit MarginTopUnit
{
get => _marginTopUnit;
set => SetField(ref _marginTopUnit, value);
}
public LayoutUnit MarginRightUnit
{
get => _marginRightUnit;
set => SetField(ref _marginRightUnit, value);
}
public LayoutUnit MarginBottomUnit
{
get => _marginBottomUnit;
set => SetField(ref _marginBottomUnit, value);
}
public LayoutUnit PaddingLeftUnit
{
get => _paddingLeftUnit;
set => SetField(ref _paddingLeftUnit, value);
}
public LayoutUnit PaddingTopUnit
{
get => _paddingTopUnit;
set => SetField(ref _paddingTopUnit, value);
}
public LayoutUnit PaddingRightUnit
{
get => _paddingRightUnit;
set => SetField(ref _paddingRightUnit, value);
}
public LayoutUnit PaddingBottomUnit
{
get => _paddingBottomUnit;
set => SetField(ref _paddingBottomUnit, value);
}
public LayoutUnit BorderLeftUnit
{
get => _borderLeftUnit;
set => SetField(ref _borderLeftUnit, value);
}
public LayoutUnit BorderTopUnit
{
get => _borderTopUnit;
set => SetField(ref _borderTopUnit, value);
}
public LayoutUnit BorderRightUnit
{
get => _borderRightUnit;
set => SetField(ref _borderRightUnit, value);
}
public LayoutUnit BorderBottomUnit
{
get => _borderBottomUnit;
set => SetField(ref _borderBottomUnit, value);
}
public LayoutUnit WidthUnit
{
get => _widthUnit;
set => SetField(ref _widthUnit, value);
}
public LayoutUnit HeightUnit
{
get => _heightUnit;
set => SetField(ref _heightUnit, value);
}
public LayoutUnit MinimumWidthUnit
{
get => _minimumWidthUnit;
set => SetField(ref _minimumWidthUnit, value);
}
public LayoutUnit MinimumHeightUnit
{
get => _minimumHeightUnit;
set => SetField(ref _minimumHeightUnit, value);
}
public LayoutUnit MaximumWidthUnit
{
get => _maximumWidthUnit;
set => SetField(ref _maximumWidthUnit, value);
}
public LayoutUnit MaximumHeightUnit
{
get => _maximumHeightUnit;
set => SetField(ref _maximumHeightUnit, value);
}
public LayoutUnit CornerRadiusTopLeftUnit
{
get => _cornerRadiusTopLeftUnit;
set => SetField(ref _cornerRadiusTopLeftUnit, value);
}
public LayoutUnit CornerRadiusBottomLeftUnit
{
get => _cornerRadiusBottomLeftUnit;
set => SetField(ref _cornerRadiusBottomLeftUnit, value);
}
public LayoutUnit CornerRadiusBottomRightUnit
{
get => _cornerRadiusBottomRightUnit;
set => SetField(ref _cornerRadiusBottomRightUnit, value);
}
public LayoutUnit CornerRadiusTopRightUnit
{
get => _cornerRadiusTopRightUnit;
set => SetField(ref _cornerRadiusTopRightUnit, value);
}
public ComputedBox ComputedBox
{
get => _computedBox;
private set => SetField(ref _computedBox, value, false);
}
public event PropertyChangedEventHandler? PropertyChanged;
public ComputedBox ComputeLayout(Vector2 intrinsic, Vector2 dpi, Vector2 area, Vector2 star)
{
// TODO: take intrinsic into account.
Vector4 margin = Compute(_margin, dpi, area, star, _marginLeftUnit, _marginTopUnit, _marginRightUnit, _marginBottomUnit);
Vector4 padding = Compute(_padding, dpi, area, star, _paddingLeftUnit, _paddingTopUnit, _paddingRightUnit, _paddingBottomUnit);
Vector4 border = Compute(_border, dpi, area, star, _borderLeftUnit, _borderTopUnit, _borderRightUnit, _borderBottomUnit);
Vector2 size = Compute(_size, dpi, area, star, _widthUnit, _heightUnit);
Vector2 minimumSize = Compute(_minimumSize, dpi, area, star, _minimumWidthUnit, _minimumHeightUnit);
Vector2 maximumSize = Compute(_maximumSize, dpi, area, star, _maximumWidthUnit, _maximumHeightUnit);
Vector4 cornerRadii = Compute(_cornerRadii, dpi, area, star, _cornerRadiusTopLeftUnit,
_cornerRadiusBottomLeftUnit, _cornerRadiusBottomRightUnit, _cornerRadiusTopRightUnit);
size = Vector2.Clamp(size, minimumSize, maximumSize);
ComputedBox = new ComputedBox(margin, padding, border, size)
{
CornerRadii = cornerRadii,
};
UpdateRequired = false;
return ComputedBox;
}
private static float Compute(float value, float dpi, float length, float star, LayoutUnit unit)
{
const float dpiToMm = 0f;
const float dpiToPt = 0f;
return unit switch
{
LayoutUnit.Pixel => value,
LayoutUnit.Millimeter => value * dpi * dpiToMm,
LayoutUnit.Percent => value * length,
LayoutUnit.Point => value * dpi * dpiToPt,
LayoutUnit.Star => value * star,
_ => throw new ArgumentException(nameof(unit)),
};
}
private static Vector2 Compute(Vector2 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit)
{
return new Vector2(
Compute(value.X, dpi.X, size.X, star.X, xUnit),
Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit));
}
private static Vector4 Compute(Vector4 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit, LayoutUnit zUnit, LayoutUnit wUnit)
{
return new Vector4(
Compute(value.X, dpi.X, size.X, star.X, xUnit),
Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit),
Compute(value.Z, dpi.X, size.X, star.X, zUnit),
Compute(value.W, dpi.Y, size.Y, star.Y, wUnit));
}
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool SetField<T>(ref T field, T value, bool updateRequired = true, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
UpdateRequired |= updateRequired;
OnPropertyChanged(propertyName);
return true;
}
}
}

View File

@@ -0,0 +1,171 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Dashboard.Layout
{
public enum DisplayMode
{
None,
Inline,
Block,
}
public enum ContainerMode
{
Basic,
Flex,
Grid,
}
public enum FlowDirection
{
Row,
Column,
RowReverse,
ColumnReverse,
}
public enum PositionMode
{
Absolute,
Relative,
}
public enum OverflowMode
{
Hidden,
Overflow,
ScrollHorizontal,
ScrollVertical,
ScrollBoth,
}
public record struct TrackInfo(float Width, LayoutUnit Unit)
{
public static readonly TrackInfo Default = new TrackInfo(0, LayoutUnit.Auto);
}
public class ContainerLayoutInfo : INotifyPropertyChanged
{
private ContainerMode _containerMode;
private FlowDirection _flowDirection = FlowDirection.Row;
public ObservableCollection<TrackInfo> Rows { get; } = new ObservableCollection<TrackInfo>() { TrackInfo.Default };
public ObservableCollection<TrackInfo> Columns { get; } =
new ObservableCollection<TrackInfo>() { TrackInfo.Default };
public ContainerMode ContainerMode
{
get => _containerMode;
set => SetField(ref _containerMode, value);
}
public FlowDirection FlowDirection
{
get => _flowDirection;
set => SetField(ref _flowDirection, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public class LayoutInfo : INotifyPropertyChanged
{
private DisplayMode _displayMode = DisplayMode.Inline;
private PositionMode _positionMode = PositionMode.Relative;
private OverflowMode _overflowMode = OverflowMode.Overflow;
private int _row = 0;
private int _column = 0;
/// <summary>
/// Changes the control display.
/// </summary>
public DisplayMode DisplayMode
{
get => _displayMode;
set => SetField(ref _displayMode, value);
}
/// <summary>
/// Changes how the control is positioned.
/// </summary>
public PositionMode PositionMode
{
get => _positionMode;
set => SetField(ref _positionMode, value);
}
/// <summary>
/// Changes how overflows are handled.
/// </summary>
public OverflowMode OverflowMode
{
get => _overflowMode;
set => SetField(ref _overflowMode, value);
}
public LayoutBox Box { get; } = new LayoutBox();
/// <summary>
/// The row of the control in a grid container.
/// </summary>
public int Row
{
get => _row;
set => SetField(ref _row, value);
}
/// <summary>
/// The column of the control in a grid container.
/// </summary>
public int Column
{
get => _column;
set => SetField(ref _column, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
public LayoutInfo()
{
Box.PropertyChanged += BoxOnPropertyChanged;
// Rows.CollectionChanged += RowsChanged;
// Columns.CollectionChanged += ColumnsChanged;
}
private void BoxOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(Box));
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}

View File

@@ -0,0 +1,364 @@
using System.Numerics;
namespace Dashboard.Layout
{
public record struct LayoutItemSolution(ILayoutItem Item, ComputedBox Solution);
public class LayoutSolution
{
public ILayoutContainer Container { get; }
public IReadOnlyList<LayoutItemSolution> Items { get; }
private LayoutSolution(ILayoutContainer container, IEnumerable<LayoutItemSolution> itemSolutions)
{
Container = container;
Items = itemSolutions.ToList().AsReadOnly();
}
public static LayoutSolution CalculateLayout<T1>(T1 container, Vector2 limits, int iterations = 3, float absTol = 0.001f, float relTol = 0.01f)
where T1 : ILayoutContainer
{
switch (container.ContainerLayout.ContainerMode)
{
default:
case ContainerMode.Basic:
return SolveForBasicLayout(container, limits, iterations, absTol, relTol);
case ContainerMode.Flex:
return SolveForFlex(container, limits, iterations, absTol, relTol);
case ContainerMode.Grid:
return SolveForGrid(container, limits, iterations, absTol, relTol);
}
}
private static LayoutSolution SolveForGrid<T1>(T1 container, Vector2 limits, int iterations, float absTol, float relTol) where T1 : ILayoutContainer
{
throw new NotImplementedException();
}
private static LayoutSolution SolveForFlex<T1>(T1 container, Vector2 limits,int iterations, float absTol, float relTol) where T1 : ILayoutContainer
{
throw new NotImplementedException();
}
private static LayoutSolution SolveForBasicLayout<T1>(T1 container, Vector2 limits, int iterations, float absTol, float relTol) where T1 : ILayoutContainer
{
int count = container.Count();
LayoutItemSolution[] items = new LayoutItemSolution[count];
int i = 0;
foreach (ILayoutItem item in container)
{
items[i++] = new LayoutItemSolution(item, default);
}
bool limitX = limits.X > 0;
bool limitY = limits.Y > 0;
while (iterations-- > 0)
{
Vector2 pen = Vector2.Zero;
i = 0;
foreach (ILayoutItem item in container)
{
Vector2 size = item.CalculateIntrinsicSize();
}
}
return new LayoutSolution(container, items);
}
private record TrackSolution(TrackInfo Track)
{
public bool Auto => Track.Unit == LayoutUnit.Auto;
public bool Absolute => Track.Unit.IsAbsolute();
public float Requested { get; private set; }
public float Value { get; private set; }
public float Result { get; set; } = 0.0f;
public bool IsFrozen { get; set; } = false;
public void CalculateRequested(float dpi, float rel, float star)
{
Requested = new Metric(Track.Unit, Track.Width).Compute(dpi, rel, star);
}
public void Freeze()
{
if (IsFrozen)
return;
IsFrozen = true;
Result = Value;
}
}
private delegate float GetItemLength<in T1>(float dpi, float rel, float star, T1 item)
where T1 : ILayoutItem;
private TrackSolution[] SolveForGridTracks<T1, T2, T3>(
float limit,
T1 tracks,
T2 container,
int iterations,
float absTol,
float relTol,
Func<int, T3> getItemTrack,
GetItemLength<T3> getItemLength)
where T1 : IList<TrackInfo>
where T2 : ILayoutContainer
where T3 : ILayoutItem
{
int itemCount = container.Count();
bool auto = limit < 0;
TrackSolution[] solution = new TrackSolution[tracks.Count];
foreach (TrackSolution track in solution)
{
if (track.Absolute) {
// FIXME: pass DPI here.
track.CalculateRequested(96f, limit, 0);
track.Freeze();
}
}
while (iterations-- > 0)
{
}
for (int i = 0; i < tracks.Count; i++)
{
solution[i].Freeze();
}
return solution;
}
// private static void GetIntrinsicGridSizes<T1, T2>(Span<float> cols, Span<float> rows, T1 parent, T2 items)
// where T1 : ILayoutItem
// where T2 : IEnumerable<ILayoutItem>
// {
// CopyToSpan(rows, parent.Layout.Rows);
// CopyToSpan(cols, parent.Layout.Columns);
//
// foreach (ILayoutItem item in items)
// {
// int col = Math.Clamp(item.Layout.Column, 0, cols.Length - 1);
// int row = Math.Clamp(item.Layout.Row, 0, rows.Length - 1);
//
// bool autoCols = parent.Layout.Columns[col] < 0;
// bool autoRows = parent.Layout.Rows[row] < 0;
//
// if (!autoRows && !autoCols)
// continue;
//
// Vector2 size = item.CalculateIntrinsicSize();
// cols[col] = autoCols ? Math.Max(size.X, cols[col]) : cols[col];
// rows[row] = autoRows ? Math.Max(size.Y, rows[row]) : rows[row];
// }
// }
//
// public static Vector2 CalculateIntrinsicSize<T1, T2>(T1 parent, T2 items)
// where T1 : ILayoutItem
// where T2 : IEnumerable<ILayoutItem>
// {
// // Copy layout details.
// Span<float> cols = stackalloc float[parent.Layout.Columns.Count];
// Span<float> rows = stackalloc float[parent.Layout.Rows.Count];
//
// GetIntrinsicGridSizes(cols, rows, parent, items);
//
// float width = parent.Layout.Margin.X + parent.Layout.Margin.Z + parent.Layout.Padding.X + parent.Layout.Padding.Z + SumSpan<float>(cols);
// float height = parent.Layout.Margin.Y + parent.Layout.Margin.W + parent.Layout.Padding.Y + parent.Layout.Padding.W + SumSpan<float>(rows);
//
// return new Vector2(width, height);
// }
//
// public static GridResult Layout<T1, T2>(T1 parent, T2 items, Vector2 limits, int iterations = 3, float abstol = 0.0001f, float reltol = 0.01f)
// where T1 : ILayoutItem
// where T2 : IEnumerable<ILayoutItem>
// {
// Vector4 contentSpace = parent.Layout.Margin + parent.Layout.Padding;
// Vector2 contentLimits = new Vector2(
// limits.X > 0 ? limits.X - contentSpace.X - contentSpace.Z : -1,
// limits.Y > 0 ? limits.Y - contentSpace.Y - contentSpace.W : -1);
//
// // Get rows and columns for now.
// Span<Track> cols = stackalloc Track[parent.Layout.Columns.Count];
// Span<Track> rows = stackalloc Track[parent.Layout.Rows.Count];
//
// for (int i = 0; i < cols.Length; i++)
// {
// cols[i] = new Track(parent.Layout.Columns[i]);
//
// if (!cols[i].Auto)
// {
// cols[i].Freeze();
// }
// }
//
// for (int i = 0; i < rows.Length; i++)
// {
// rows[i] = new Track(parent.Layout.Rows[i]);
//
// if (!rows[i].Auto)
// {
// rows[i].Freeze();
// }
// }
//
// int freeRows = 0;
// int freeCols = 0;
// while (iterations-- > 0 && ((freeRows = CountFree(rows)) > 0 || (freeCols = CountFree(cols)) > 0))
// {
// // Calculate the remaining size.
// Vector2 remaining = contentLimits;
//
// for (int i = 0; contentLimits.X > 0 && i < cols.Length; i++)
// {
// if (cols[i].IsFrozen)
// remaining.X -= cols[i].Value;
// }
//
// for (int i = 0; contentLimits.Y > 0 && i < rows.Length; i++)
// {
// if (rows[i].IsFrozen)
// remaining.Y -= rows[i].Value;
// }
//
// Vector2 childLimits = remaining / new Vector2(Math.Max(freeCols, 1), Math.Max(freeRows, 1));
//
//
// // Calculate the size of each free track.
// foreach (ILayoutItem child in items)
// {
// int c = Math.Clamp(child.Layout.Column, 0, cols.Length - 1);
// int r = Math.Clamp(child.Layout.Row, 0, rows.Length - 1);
//
// bool autoRow = rows[r].Auto;
// bool autoCol = cols[c].Auto;
//
// if (!autoRow && !autoCol)
// continue;
//
// Vector2 childSize = child.CalculateSize(childLimits);
//
// if (autoCol)
// cols[c].Value = Math.Max(childLimits.X, childSize.X);
//
// if (autoRow)
// rows[r].Value = Math.Max(childLimits.Y, childSize.Y);
// }
//
// // Calculate for errors and decide to freeze them.
//
// for (int i = 0; limits.X > 0 && i < cols.Length; i++)
// {
// if (WithinTolerance(cols[i].Value, childLimits.X, abstol, reltol))
// {
// cols[i].Freeze();
// }
// }
//
// for (int i = 0; limits.Y > 0 && i < rows.Length; i++)
// {
// if (WithinTolerance(rows[i].Value, childLimits.Y, abstol, reltol))
// {
// rows[i].Freeze();
// }
// }
// }
//
// Vector2 size = new Vector2(
// parent.Layout.Margin.X + parent.Layout.Margin.Z + parent.Layout.Padding.X + parent.Layout.Padding.Z,
// parent.Layout.Margin.Y + parent.Layout.Margin.W + parent.Layout.Padding.Y + parent.Layout.Padding.W);
//
// foreach (ref Track col in cols)
// {
// col.Freeze();
// size.X += col.Result;
// }
//
// foreach (ref Track row in rows)
// {
// row.Freeze();
// size.Y += row.Result;
// }
//
// if (limits.X > 0) size.X = Math.Max(size.X, limits.X);
// if (limits.Y > 0) size.Y = Math.Max(size.Y, limits.Y);
//
// // Temporary solution
// return new GridResult(size, cols.ToArray().Select(x => x.Result).ToArray(), rows.ToArray().Select(x => x.Result).ToArray());
//
// static int CountFree(Span<Track> tracks)
// {
// int i = 0;
// foreach (Track track in tracks)
// {
// if (!track.IsFrozen)
// i++;
// }
//
// return i;
// }
// }
//
// private static void CopyToSpan<T1, T2>(Span<T1> span, T2 items)
// where T2 : IEnumerable<T1>
// {
// using IEnumerator<T1> iterator = items.GetEnumerator();
// for (int i = 0; i < span.Length; i++)
// {
// if (!iterator.MoveNext())
// break;
//
// span[i] = iterator.Current;
// }
// }
//
// private static T1 SumSpan<T1>(ReadOnlySpan<T1> span)
// where T1 : struct, INumber<T1>
// {
// T1 value = default;
//
// foreach (T1 item in span)
// {
// value += item;
// }
//
// return value;
// }
//
// private static bool WithinTolerance<T>(T value, T limit, T abstol, T reltol)
// where T : INumber<T>
// {
// T tol = T.Max(abstol, value * reltol);
// return T.Abs(value - limit) < tol;
// }
//
// public record GridResult(Vector2 Size, float[] Columns, float[] Rows)
// {
//
// }
//
// private record struct Track(float Request)
// {
// public bool Auto => Request < 0;
//
// public bool IsFrozen { get; private set; } = false;
// public float Value { get; set; } = Request;
//
// public float Result { get; private set; }
//
// public void Freeze()
// {
// Result = Value;
// IsFrozen = true;
// }
// }
}
}

View File

@@ -0,0 +1,30 @@
namespace Dashboard
{
public enum LayoutUnit : short
{
/// <summary>
/// Does not specify a unit.
/// </summary>
Auto,
/// <summary>
/// The default unit. A size of a single picture element.
/// </summary>
Pixel = 1,
/// <summary>
/// 1/72th of an inch traditional in graphics design.
/// </summary>
Point = 2,
/// <summary>
/// The universal length unit for small distances.
/// </summary>
Millimeter = 3,
/// <summary>
/// An inverse proportional unit with respect to the container size.
/// </summary>
Star = 4,
/// <summary>
/// A directly proportional unit with respect to the container size.
/// </summary>
Percent = 5,
}
}

View File

@@ -1,48 +1,108 @@
using System.Numerics;
namespace Dashboard
{
public enum MeasurementUnit
public record struct LayoutUnits(LayoutUnit All)
{
/// <summary>
/// The default unit. A size of a single picture element.
/// </summary>
Pixel,
/// <summary>
/// 1/72th of an inch traditional in graphics design.
/// </summary>
Point,
/// <summary>
/// The universal length unit for small distances.
/// </summary>
Millimeter,
/// <summary>
/// An inverse proportional unit with respect to the container size.
/// </summary>
Star,
/// <summary>
/// A directly proportional unit with respect to the container size.
/// </summary>
Percent,
public LayoutUnit XUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 0);
set => All = (LayoutUnit)(((int)All & ~(0xF << 0)) | ((int)value << 0));
}
public LayoutUnit YUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 4);
set => All = (LayoutUnit)(((int)All & ~(0xF << 4)) | ((int)value << 4));
}
public LayoutUnit ZUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 8);
set => All = (LayoutUnit)(((int)All & ~(0xF << 8)) | ((int)value << 8));
}
public LayoutUnit WUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 12);
set => All = (LayoutUnit)(((int)All & ~(0xF << 12)) | ((int)value << 12));
}
}
public record struct AdvancedMetric(MeasurementUnit Unit, float Value)
public record struct Metric(LayoutUnit Units, float Value)
{
public AdvancedMetric Convert(MeasurementUnit target, float dpi, float rel, int stars)
public float Compute(float dpi, float rel, float star)
{
switch (Units)
{
case LayoutUnit.Auto:
return -1;
case LayoutUnit.Millimeter:
float mm2Px = dpi / 25.4f;
return Value * mm2Px;
case LayoutUnit.Pixel:
return Value;
case LayoutUnit.Point:
float pt2Px = 72 / dpi;
return Value * pt2Px;
case LayoutUnit.Percent:
return rel * Value;
case LayoutUnit.Star:
return star * Value;
default:
throw new Exception("Unrecognized unit.");
}
}
}
public record struct Metric2D(LayoutUnits Units, Vector2 Value)
{
public float X
{
get => Value.X;
set => Value = new Vector2(value, Value.Y);
}
public LayoutUnit XUnits
{
get => Units.XUnit;
set => Units = Units with { XUnit = value };
}
public float Y
{
get => Value.Y;
set => Value = new Vector2(Value.X, value);
}
public LayoutUnit YUnits
{
get => Units.YUnit;
set => Units = Units with { YUnit = value };
}
}
public record struct BoxMetric(LayoutUnit Units, Box2d Value);
public record struct AdvancedMetric(LayoutUnit Unit, float Value)
{
public AdvancedMetric Convert(LayoutUnit target, float dpi, float rel, int stars)
{
if (Unit == target)
return this;
float pixels = Unit switch {
MeasurementUnit.Pixel => Value,
MeasurementUnit.Point => Value * (72f / dpi),
MeasurementUnit.Millimeter => Value * (28.3464566929f / dpi),
MeasurementUnit.Star => Value * rel / stars,
MeasurementUnit.Percent => Value * rel / 100,
LayoutUnit.Pixel => Value,
LayoutUnit.Point => Value * (72f / dpi),
LayoutUnit.Millimeter => Value * (28.3464566929f / dpi),
LayoutUnit.Star => Value * rel / stars,
LayoutUnit.Percent => Value * rel / 100,
_ => throw new Exception(),
};
float value = target switch {
MeasurementUnit.Pixel => pixels,
MeasurementUnit.Point => Value * (dpi / 72f),
LayoutUnit.Pixel => pixels,
LayoutUnit.Point => Value * (dpi / 72f),
// MeasurementUnit.Millimeter =>
};
@@ -65,22 +125,4 @@ namespace Dashboard
? metric
: throw new Exception($"Could not parse the value '{str}'.");
}
public static class MeasurementExtensions
{
public static bool IsRelative(this MeasurementUnit unit) => unit switch {
MeasurementUnit.Star or MeasurementUnit.Percent => true,
_ => false,
};
public static bool IsAbsolute(this MeasurementUnit unit) => !IsRelative(unit);
public static string ToShortString(this MeasurementUnit unit) => unit switch {
MeasurementUnit.Pixel => "px",
MeasurementUnit.Point => "pt",
MeasurementUnit.Millimeter => "mm",
MeasurementUnit.Star => "*",
MeasurementUnit.Percent => "%",
_ => throw new Exception("Unknown unit."),
};
}
}

View File

@@ -0,0 +1,30 @@
namespace Dashboard
{
public static class MeasurementExtensions
{
public static bool IsRelative(this LayoutUnit unit) => unit switch {
LayoutUnit.Star or LayoutUnit.Percent => true,
_ => false,
};
public static bool IsAbsolute(this LayoutUnit unit) => !IsRelative(unit);
public static string ToShortString(this LayoutUnit unit) => unit switch {
LayoutUnit.Pixel => "px",
LayoutUnit.Point => "pt",
LayoutUnit.Millimeter => "mm",
LayoutUnit.Star => "*",
LayoutUnit.Percent => "%",
_ => throw new Exception("Unknown unit."),
};
public static bool WithinTolerance(this float value, float reference, float absTol, float relTol)
=> value.CompareTolerance(reference, absTol, relTol) == 0;
public static int CompareTolerance(this float value, float reference, float absTol, float relTol)
{
float tolerance = Math.Max(absTol, Math.Abs(reference) * relTol);
float difference = value - reference;
return difference < -tolerance ? -1 : difference > tolerance ? 1 : 0;
}
}
}

View File

@@ -16,6 +16,9 @@ namespace Dashboard.Pal
public bool IsDisposed { get; private set; } = false;
public IContextDebugger? Debugger { get; set; }
protected CancellationToken? CancellationToken { get; private set; }
protected bool Quit { get; set; } = false;
private readonly TypeDictionary<IApplicationExtension> _extensions =
new TypeDictionary<IApplicationExtension>(true);
private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
@@ -58,15 +61,18 @@ namespace Dashboard.Pal
Initialize();
}
public void Run() => Run(true, CancellationToken.None);
public void Run() => Run(true, System.Threading.CancellationToken.None);
public void Run(bool wait) => Run(wait, CancellationToken.None);
public void Run(bool wait) => Run(wait, System.Threading.CancellationToken.None);
public void Run(bool waitForEvents, CancellationToken token)
{
CancellationToken = token;
CancellationToken.Value.Register(() => Quit = true);
InitializeInternal();
while (!token.IsCancellationRequested)
while (!Quit && !token.IsCancellationRequested)
{
RunEvents(waitForEvents);
}
@@ -167,6 +173,7 @@ namespace Dashboard.Pal
{
if (!isDisposing) return;
Quit = true;
foreach (IApplicationExtension extension in _extensions)
{
extension.Dispose();

View File

@@ -1,4 +1,5 @@
using System.Drawing;
using System.Numerics;
namespace Dashboard.Windowing
{
@@ -15,6 +16,6 @@ namespace Dashboard.Windowing
/// <summary>
/// The size of the window framebuffer in pixels.
/// </summary>
Size FramebufferSize { get; }
Vector2 FramebufferSize { get; }
}
}

View File

@@ -2,24 +2,33 @@ using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics.Wgl;
using OpenTK.Mathematics;
using ColorBuffer = OpenTK.Graphics.OpenGL.ColorBuffer;
using Vector2 = System.Numerics.Vector2;
namespace Dashboard.OpenGL.Drawing
{
public class DeviceContextBase : IDeviceContextBase
{
private readonly Stack<Matrix4x4> _transforms = new Stack<Matrix4x4>();
private readonly Stack<RectangleF> _clipRegions = new Stack<RectangleF>();
private readonly Stack<Box2d> _clipRegions = new Stack<Box2d>();
private readonly Stack<Box2d> _scissorRegions = new Stack<Box2d>();
private int _z = 0;
public DeviceContext Context { get; private set; } = null!;
IContextBase IContextExtensionBase.Context => Context;
public string DriverName => "Dashboard OpenGL Device Context";
public string DriverVendor => "Dashboard";
public Version DriverVersion => new Version(0, 1);
public RectangleF ClipRegion => _clipRegions.Peek();
public Box2d ClipRegion => _clipRegions.Peek();
public Box2d ScissorRegion => _scissorRegions.Peek();
public Matrix4x4 Transforms => _transforms.Peek();
public float Scale => ScaleOverride > 0 ? ScaleOverride : (Context.Window as IDpiAwareWindow)?.Scale ?? 1;
public float ScaleOverride { get; set; } = -1f;
public void Dispose()
@@ -32,6 +41,7 @@ namespace Dashboard.OpenGL.Drawing
Context = context;
ResetClip();
ResetScissor();
ResetTransforms();
}
@@ -42,17 +52,17 @@ namespace Dashboard.OpenGL.Drawing
{
_clipRegions.Clear();
SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
_clipRegions.Push(new RectangleF(0,0, size.Width, size.Height));
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
_clipRegions.Push(new Box2d(Vector2.Zero, size));
SetClip(ClipRegion);
}
public void PushClip(RectangleF clipRegion)
public void PushClip(Box2d clipRegion)
{
clipRegion = new RectangleF(ClipRegion.X + clipRegion.X, ClipRegion.Y + clipRegion.Y,
Math.Min(ClipRegion.Right - clipRegion.X, clipRegion.Width),
Math.Min(ClipRegion.Bottom - clipRegion.Y, clipRegion.Height));
clipRegion = new Box2d(ClipRegion.Min.X + clipRegion.Min.X, ClipRegion.Min.Y + clipRegion.Min.Y,
Math.Min(ClipRegion.Max.X, ClipRegion.Min.X + clipRegion.Max.X),
Math.Min(ClipRegion.Max.Y, ClipRegion.Max.Y + clipRegion.Max.Y));
_clipRegions.Push(clipRegion);
SetClip(clipRegion);
@@ -64,20 +74,59 @@ namespace Dashboard.OpenGL.Drawing
SetClip(ClipRegion);
}
private void SetClip(RectangleF rect)
public void ResetScissor()
{
SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
GL.Disable(EnableCap.ScissorTest);
_scissorRegions.Clear();
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
_scissorRegions.Push(new Box2d(Vector2.Zero, size));
}
public void PushScissor(Box2d scissorRegion)
{
GL.Enable(EnableCap.ScissorTest);
// scissorRegion = new RectangleF(scissorRegion.X + scissorRegion.X, scissorRegion.Y + scissorRegion.Y,
// Math.Min(ScissorRegion.Right - scissorRegion.X, scissorRegion.Width),
// Math.Min(ScissorRegion.Bottom - scissorRegion.Y, scissorRegion.Height));
_scissorRegions.Push(scissorRegion);
SetScissor(scissorRegion);
}
public void PopScissor()
{
if (_scissorRegions.Count == 1)
GL.Disable(EnableCap.ScissorTest);
_scissorRegions.Pop();
SetScissor(ClipRegion);
}
private void SetClip(Box2d rect)
{
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
GL.Viewport(
(int)Math.Round(rect.X),
(int)Math.Round(size.Height - rect.Y - rect.Height),
(int)Math.Round(rect.Width),
(int)Math.Round(rect.Height));
(int)Math.Round(rect.Min.X),
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
(int)Math.Round(rect.Size.X),
(int)Math.Round(rect.Size.Y));
}
void SetScissor(Box2d rect)
{
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
GL.Scissor(
(int)Math.Round(rect.Min.X),
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
(int)Math.Round(rect.Size.X),
(int)Math.Round(rect.Size.Y));
}
public void ResetTransforms()
{
SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 m = Matrix4x4.CreateOrthographicOffCenterLeftHanded(0, size.Width, size.Height, 0, 1, -1);
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 m = Matrix4x4.CreateOrthographicOffCenterLeftHanded(0, size.X, size.Y, 0, 1, -1);
_transforms.Clear();
_transforms.Push(m);
@@ -93,5 +142,26 @@ namespace Dashboard.OpenGL.Drawing
{
_transforms.Pop();
}
public void ClearColor(Color color)
{
GL.ClearColor(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
GL.Clear(ClearBufferMask.ColorBufferBit);
}
public void ClearDepth()
{
GL.Clear(ClearBufferMask.DepthBufferBit);
}
public int IncrementZ()
{
return ++_z;
}
public int DecrementZ()
{
return --_z;
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Drawing;
using System.Numerics;
using System.Runtime;
using System.Runtime.InteropServices;
using Dashboard.Drawing;
using Dashboard.Pal;
@@ -110,7 +111,6 @@ namespace Dashboard.OpenGL.Drawing
GL.VertexAttribPointer(_program_acolor, 4, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.ColorOffset);
GL.EnableVertexAttribArray(_program_acolor);
Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program);
@@ -150,7 +150,6 @@ namespace Dashboard.OpenGL.Drawing
GL.VertexAttribPointer(_program_acolor, 4, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.ColorOffset);
GL.EnableVertexAttribArray(_program_acolor);
Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program);
@@ -166,6 +165,20 @@ namespace Dashboard.OpenGL.Drawing
GL.DeleteBuffer(buffer);
}
public void Rectangle(in RectangleDrawInfo rectangle)
{
// TODO: implement this better.
int z = Context.ExtensionRequire<IDeviceContextBase>().IncrementZ();
Color color = (rectangle.Fill as SolidColorBrush)?.Color ?? Color.LightGray;
Vector4 colorV = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
Vector4 margin = rectangle.Box.Margin;
Vector4 border = rectangle.Box.Border;
Vector2 size = rectangle.Box.Size + new Vector2(border.X + border.Z, border.Y = border.W);
Box2d box = Box2d.FromPositionAndSize(rectangle.Position + new Vector2(margin.X + border.X, margin.Y + border.Y), size);
Rectangle(box, z, colorV);
}
public void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture)
{
Span<ImmediateVertex> vertices =
@@ -189,7 +202,6 @@ namespace Dashboard.OpenGL.Drawing
GL.EnableVertexAttribArray(_program_atexcoord);
GL.VertexAttribPointer(_program_acolor, 4, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.ColorOffset);
GL.EnableVertexAttribArray(_program_acolor);
Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program);

View File

@@ -22,7 +22,6 @@ namespace Dashboard.OpenGL
public void Dispose()
{
throw new NotImplementedException();
}

View File

@@ -1,4 +1,4 @@
using System.Drawing;
using System.Numerics;
using Dashboard.Windowing;
namespace Dashboard.OpenGL
@@ -17,7 +17,7 @@ namespace Dashboard.OpenGL
/// <summary>
/// The size of the framebuffer in pixels.
/// </summary>
public Size FramebufferSize { get; }
public Vector2 FramebufferSize { get; }
/// <summary>
/// Called when the context is disposed.

View File

@@ -44,12 +44,27 @@ namespace Dashboard.OpenTK.PAL2
protected override void InitializeInternal()
{
base.InitializeInternal();
CancellationToken?.Register(() =>
{
TK.Window.PostUserEvent(new ApplicationQuitEventArgs());
});
EventQueue.EventRaised += OnEventRaised;
}
internal void RemoveWindow(PhysicalWindow window)
{
_windows.Remove(window);
}
public override void RunEvents(bool wait)
{
if (_windows.Count == 0)
{
Dispose();
return;
}
TK.Window.ProcessEvents(wait);
long tock = Stopwatch.GetTimestamp();
@@ -85,7 +100,7 @@ namespace Dashboard.OpenTK.PAL2
}
else
{
System.Diagnostics.Debugger.Break();
// System.Diagnostics.Debugger.Break();
}
}
@@ -99,6 +114,14 @@ namespace Dashboard.OpenTK.PAL2
switch (type)
{
case PlatformEventType.UserMessage:
if (args is ApplicationQuitEventArgs)
{
Quit = true;
return;
}
break;
// Mouse Events
case PlatformEventType.MouseDown:
{
MouseButtonDownEventArgs down = (MouseButtonDownEventArgs)args;
@@ -139,6 +162,8 @@ namespace Dashboard.OpenTK.PAL2
info.Window.SendEvent(this, scroll2);
break;
}
// Keyboard & Text Events
case PlatformEventType.KeyDown:
{
KeyDownEventArgs down = (KeyDownEventArgs)args;
@@ -163,11 +188,43 @@ namespace Dashboard.OpenTK.PAL2
info.Window.SendEvent(this, up2);
break;
}
case PlatformEventType.TextInput:
{
OPENTK.TextInputEventArgs textInput = (OPENTK.TextInputEventArgs)args;
DB.TextInputEventArgs textInput2 = new DB.TextInputEventArgs(textInput.Text);
info.Window.SendEvent(this, textInput2);
break;
}
case PlatformEventType.TextEditing:
{
TextEditingEventArgs textEditing = (TextEditingEventArgs)args;
TextEditEventArgs textEditing2 = new TextEditEventArgs(textEditing.Candidate, textEditing.Cursor, textEditing.Length);
info.Window.SendEvent(this, textEditing2);
break;
}
// Window/Surface related events.
case PlatformEventType.Close:
{
info.Window.SendEvent(this, new WindowCloseEvent());
break;
}
case PlatformEventType.WindowFramebufferResize:
{
var resize = (WindowFramebufferResizeEventArgs)args;
info.Window.SendEvent(this, new ResizeEventArgs());
info.Window.SendEvent(this, new PaintEventArgs(info.Window.DeviceContext));
break;
}
case PlatformEventType.WindowResize:
{
var resize = (WindowResizeEventArgs)args;
info.Window.SendEvent(this, new ResizeEventArgs());
info.Window.SendEvent(this, new PaintEventArgs(info.Window.DeviceContext));
break;
}
default:
Debugger?.LogDebug($"Unknown event type {type} with \"{args}\".");
break;
@@ -221,4 +278,8 @@ namespace Dashboard.OpenTK.PAL2
_ => (ScanCode)0,
};
}
internal class ApplicationQuitEventArgs() : EventArgs
{
}
}

View File

@@ -15,14 +15,15 @@ namespace Dashboard.OpenTK.PAL2
public WindowHandle WindowHandle { get; }
public ISwapGroup SwapGroup { get; }
public int ContextGroup { get; }
public Size FramebufferSize
public System.Numerics.Vector2 FramebufferSize
{
get
{
TK.Window.GetFramebufferSize(WindowHandle, out Vector2i size);
return new Size(size.X, size.Y);
return new System.Numerics.Vector2(size.X, size.Y);
}
}
@@ -64,6 +65,7 @@ namespace Dashboard.OpenTK.PAL2
private static int _contextGroupId = 0;
private static ConcurrentDictionary<OpenGLContextHandle, int> _contextGroupRootContexts = new ConcurrentDictionary<OpenGLContextHandle, int>();
private Size _framebufferSize;
private static int GetContextGroup(OpenGLContextHandle handle)
{

View File

@@ -58,7 +58,6 @@ namespace Dashboard.OpenTK.PAL2
Application = app;
WindowHandle = window;
DeviceContext = CreateDeviceContext(app, this, new OpenGLGraphicsApiHints());
AddWindow(this);
}
public PhysicalWindow(Application app, WindowHandle window, OpenGLContextHandle context)
@@ -66,7 +65,6 @@ namespace Dashboard.OpenTK.PAL2
Application = app;
WindowHandle = window;
DeviceContext = new GLDeviceContext(app, this, new Pal2GLContext(window, context));
AddWindow(this);
}
public PhysicalWindow(Application app, GraphicsApiHints hints)
@@ -74,7 +72,6 @@ namespace Dashboard.OpenTK.PAL2
Application = app;
WindowHandle = TK.Window.Create(hints);
DeviceContext = CreateDeviceContext(app, this, hints);
AddWindow(this);
}
private static DeviceContext CreateDeviceContext(Application app, PhysicalWindow window, GraphicsApiHints hints)
@@ -96,27 +93,18 @@ namespace Dashboard.OpenTK.PAL2
if (IsDisposed) return;
IsDisposed = true;
RemoveWindow(this);
(DeviceContext as IDisposable)?.Dispose();
((Pal2Application)Application).RemoveWindow(this);
TK.Window.Destroy(WindowHandle);
}
public virtual void SendEvent(object? sender, EventArgs args)
{
switch (args)
{
case UiEventArgs ui:
switch (ui.Type)
{
case UiEventType.ControlInvalidateVisual:
break;
}
break;
}
args = TransformEvent(sender, args);
Form?.SendEvent(this, args);
EventRaised?.Invoke(this, args);
lock (_listeners)
@@ -148,42 +136,6 @@ namespace Dashboard.OpenTK.PAL2
}
}
private static readonly ConcurrentDictionary<WindowHandle, PhysicalWindow> _windows =
new ConcurrentDictionary<WindowHandle, PhysicalWindow>();
static PhysicalWindow()
{
EventQueue.EventRaised += EventQueueOnEventRaised;
}
private static void AddWindow(PhysicalWindow window)
{
_windows.TryAdd(window.WindowHandle, window);
}
private static void RemoveWindow(PhysicalWindow window)
{
_windows.TryRemove(window.WindowHandle, out _);
}
private static void EventQueueOnEventRaised(PalHandle? handle, PlatformEventType type, EventArgs args)
{
if (handle is not WindowHandle windowHandle)
return;
if (!_windows.TryGetValue(windowHandle, out PhysicalWindow? window))
return;
switch (type)
{
case PlatformEventType.MouseMove:
case PlatformEventType.Scroll:
case PlatformEventType.MouseUp:
case PlatformEventType.MouseDown:
break;
}
}
public float Dpi => Scale * 96f;
public float Scale

View File

@@ -0,0 +1,69 @@
using System;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Layout;
using Dashboard.Pal;
namespace Dashboard.Controls
{
public class Button : Control
{
private Vector2 _intrinsicSize = Vector2.Zero;
public bool AutoSize { get; set; } = true;
public string Text { get; set; } = "Click!";
public Font Font { get; set; } = Font.Create(new FontInfo("Rec Mono Linear"));
public float TextSize { get; set; } = 12f;
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
public Brush ButtonBrush { get; set; } = new SolidColorBrush(Color.DarkSlateGray);
public Vector2 Padding { get; set; } = new Vector2(4, 4);
public event EventHandler? Clicked;
public override Vector2 CalculateIntrinsicSize()
{
return _intrinsicSize + 2 * Padding;
}
protected void CalculateSize(DeviceContext dc)
{
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
_intrinsicSize = box.Size;
// Layout.Size = box.Size;
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size);
}
public override void OnPaint(DeviceContext dc)
{
base.OnPaint(dc);
if (AutoSize)
CalculateSize(dc);
bool hidden = Layout.OverflowMode == OverflowMode.Hidden;
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
if (hidden)
dcb.PushScissor(ClientArea);
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));
var imm = dc.ExtensionRequire<IImmediateMode>();
Color color = (ButtonBrush as SolidColorBrush)?.Color ?? Color.Black;
Vector4 colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
imm.Rectangle(ClientArea, 0, colorVector);
var text = dc.ExtensionRequire<ITextRenderer>();
color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black;
colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
text.DrawText(Vector2.Zero, colorVector, TextSize, Font.Base, Text);
if (hidden)
dcb.PopScissor();
dcb.PopTransforms();
}
}
}

View File

@@ -1,77 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Dashboard.Controls
{
public enum ClassChangeType
{
Added,
Removed,
}
public record struct ClassChanged(object? Owner, string ClassName, ClassChangeType Type);
public class ClassSet : ICollection<string>
{
public int Count => _classes.Count;
public bool IsReadOnly => false;
public object? Owner { get; }
public event EventHandler<ClassChanged>? ClassChanged;
private readonly HashSet<string> _classes = new HashSet<string>();
public ClassSet(object? owner)
{
Owner = owner;
}
public IEnumerator<string> GetEnumerator() => _classes.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(string item)
{
if (_classes.Add(item))
{
OnClassAdded(item);
}
}
public void Clear()
{
foreach (string @class in _classes)
OnClassRemoved(@class);
_classes.Clear();
}
public bool Contains(string item) => _classes.Contains(item);
public void CopyTo(string[] array, int arrayIndex) => _classes.CopyTo(array, arrayIndex);
public bool Remove(string item)
{
if (_classes.Remove(item))
{
OnClassRemoved(item);
return true;
}
return false;
}
private void OnClassAdded(string @class)
{
ClassChanged?.Invoke(this, new ClassChanged(Owner, @class, ClassChangeType.Added));
}
private void OnClassRemoved(string @class)
{
ClassChanged?.Invoke(this, new ClassChanged(Owner, @class, ClassChangeType.Removed));
}
}
}

View File

@@ -0,0 +1,145 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.Layout;
using Dashboard.Pal;
namespace Dashboard.Controls
{
public class Container : Control, IList<Control>, ILayoutContainer
{
private readonly List<Control> _controls = new List<Control>();
public int Count => _controls.Count;
public bool IsReadOnly => false;
public ContainerLayoutInfo ContainerLayout { get; } = new ContainerLayoutInfo();
public event EventHandler<ContainerChildAddedEventArgs>? ChildAdded;
public event EventHandler<ContainerChildRemovedEventArgs>? ChildRemoved;
public Control this[int index]
{
get => _controls[index];
set => _controls[index] = value;
}
protected override void ValidateLayout()
{
if (!IsLayoutEnabled || IsLayoutValid)
return;
// LayoutSolution solution = LayoutSolution.CalculateLayout(this, ClientArea.Size);
base.ValidateLayout();
}
public override void OnPaint(DeviceContext dc)
{
base.OnPaint(dc);
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
dcb.PushClip(ClientArea);
ValidateLayout();
foreach (Control child in _controls)
{
if (child.Layout.DisplayMode == DisplayMode.None)
continue;
child.SendEvent(this, new PaintEventArgs(dc));
}
dcb.PopClip();
}
IEnumerator<ILayoutItem> IEnumerable<ILayoutItem>.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<Control> GetEnumerator()
{
return _controls.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_controls).GetEnumerator();
}
public void Add(Control item)
{
SetParent(this, item);
_controls.Add(item);
ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item));
}
public void Clear()
{
foreach (Control control in this)
{
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, control));
}
_controls.Clear();
}
public bool Contains(Control item)
{
return _controls.Contains(item);
}
public void CopyTo(Control[] array, int arrayIndex)
{
_controls.CopyTo(array, arrayIndex);
}
public bool Remove(Control item)
{
if (!_controls.Remove(item))
return false;
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, item));
return true;
}
public int IndexOf(Control item)
{
return _controls.IndexOf(item);
}
public void Insert(int index, Control item)
{
SetParent(this, item);
_controls.Insert(index, item);
ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item));
}
public void RemoveAt(int index)
{
Control child = _controls[index];
_controls.RemoveAt(index);
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, child));
}
}
public class ContainerChildAddedEventArgs(Container parent, Control child) : EventArgs
{
public Container Parent { get; } = parent;
public Control Child { get; } = child;
}
public class ContainerChildRemovedEventArgs(Container parent, Control child) : EventArgs
{
public Container Parent { get; } = parent;
public Control Child { get; } = child;
}
}

View File

@@ -1,16 +1,22 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.Layout;
using Dashboard.Pal;
using Dashboard.Windowing;
namespace Dashboard.Controls
{
public class Control : IEventListener, IDisposable
public class Control : IEventListener, ILayoutItem, IDisposable
{
private Form? _owner = null;
public string? Id { get; set; }
public ClassSet Classes { get; }
public Form Owner
{
get => _owner ?? throw NoOwnerException;
@@ -26,7 +32,14 @@ namespace Dashboard.Controls
public virtual Box2d ClientArea { get; set; }
public bool IsFocused => _owner?.FocusedControl == this;
public event EventHandler<DeviceContext> Painting;
public Brush Background { get; set; } = new SolidColorBrush(Color.Transparent);
public Brush BorderBrush { get; set; } = new SolidColorBrush(Color.Black);
public LayoutInfo Layout { get; } = new LayoutInfo();
public bool IsLayoutEnabled { get; private set; } = true;
protected bool IsLayoutValid { get; set; } = false;
public event EventHandler<DeviceContext>? Painting;
public event EventHandler<TickEventArgs>? AnimationTick;
public event EventHandler? OwnerChanged;
public event EventHandler? ParentChanged;
@@ -35,9 +48,15 @@ namespace Dashboard.Controls
public event EventHandler? Disposing;
public event EventHandler? Resized;
public Control()
public virtual Vector2 CalculateIntrinsicSize()
{
Classes = new ClassSet(this);
return Vector2.Zero;
// return Vector2.Max(Vector2.Zero, Vector2.Max(Layout.Size, Layout.MinimumSize));
}
public Vector2 CalculateSize(Vector2 limits)
{
return CalculateIntrinsicSize();
}
public virtual void OnPaint(DeviceContext dc)
@@ -93,7 +112,7 @@ namespace Dashboard.Controls
OnEventRaised(sender, args);
}
internal static void SetParent(Control parent, Control child)
internal static void SetParent(Container parent, Control child)
{
child.Parent = parent;
child.ParentChanged?.Invoke(child, EventArgs.Empty);
@@ -127,6 +146,7 @@ namespace Dashboard.Controls
protected virtual void OnResize()
{
Resized?.Invoke(this, EventArgs.Empty);
InvalidateLayout();
}
private void OnOwnerChanged(Form value)
@@ -134,6 +154,28 @@ namespace Dashboard.Controls
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
public void InvalidateLayout()
{
IsLayoutValid = false;
}
protected virtual void ValidateLayout()
{
IsLayoutValid = true;
}
public void ResumeLayout()
{
IsLayoutEnabled = true;
}
public void SuspendLayout()
{
IsLayoutEnabled = false;
IsLayoutValid = false;
}
protected static Exception NoOwnerException => new Exception("No form owns this control");
public event PropertyChangedEventHandler? PropertyChanged;
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Drawing;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.Pal;
@@ -6,13 +7,23 @@ using Dashboard.Windowing;
namespace Dashboard.Controls
{
public class Form : Control, IForm
public class Form : Container, IForm
{
private string? _title = "Untitled Form";
public IWindow Window { get; }
public Image? WindowIcon { get; set; }
public string? Title { get; set; } = "Untitled Form";
public Control? Root { get; set; } = null;
public string? Title
{
get => _title;
set
{
_title = value;
Window.Title = _title ?? "";
}
}
public Brush Background { get; set; } = new SolidColorBrush(Color.SlateGray);
public Control? FocusedControl { get; private set; } = null;
public override Box2d ClientArea
@@ -27,6 +38,8 @@ namespace Dashboard.Controls
{
Window = window;
window.Form = this;
Window.Title = _title;
}
public void Focus(Control control)
@@ -41,7 +54,18 @@ namespace Dashboard.Controls
public override void OnPaint(DeviceContext dc)
{
dc.Begin();
Root?.SendEvent(this, new PaintEventArgs(dc));
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
dcb.ResetClip();
dcb.ResetScissor();
dcb.ResetTransforms();
if (Background is SolidColorBrush solidColorBrush)
dcb.ClearColor(solidColorBrush.Color);
foreach (Control child in this)
child.SendEvent(this, new PaintEventArgs(dc));
dc.End();
}
@@ -55,5 +79,17 @@ namespace Dashboard.Controls
Dispose();
Window.Dispose();
}
protected override void OnEventRaised(object? sender, EventArgs args)
{
base.OnEventRaised(sender, args);
switch (args)
{
case WindowCloseEvent close:
OnClosing(close);
break;
}
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Pal;
namespace Dashboard.Controls
{
public class ImageBox : Control
{
public Image? Image { get; set; }
public override Vector2 CalculateIntrinsicSize()
{
return new Vector2(Image?.Width ?? 0, Image?.Height ?? 0);
}
public override void OnPaint(DeviceContext dc)
{
if (Image == null)
return;
// Layout.Size = CalculateIntrinsicSize();
// dc.ExtensionRequire<IImmediateMode>().Image(new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size), new Box2d(0, 0, 1, 1), 0, Image.InternTexture(dc));
base.OnPaint(dc);
}
}
}

View File

@@ -2,30 +2,61 @@ using System;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Layout;
using Dashboard.Pal;
namespace Dashboard.Controls
{
public class Label : Control
{
private Vector2 _intrinsicSize = Vector2.Zero;
public bool AutoSize { get; set; } = true;
public string Text { get; set; } = "";
public event EventHandler? TextChanged;
// protected IBrush TextBrush => throw new NotImplementedException();
protected IFont Font => throw new NotImplementedException();
public Font Font { get; set; } = Drawing.Font.Create(new FontInfo("Rec Mono Linear"));
public float TextSize { get; set; } = 12f;
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
protected virtual void OnTextChanged(string oldValue, string newValue)
public override Vector2 CalculateIntrinsicSize()
{
if (AutoSize)
CalculateSize();
return _intrinsicSize;
}
protected void CalculateSize()
protected void CalculateSize(DeviceContext dc)
{
// SizeF sz = Typesetter.MeasureString(Font, Text);
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz);
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
// _intrinsicSize = box.Size;
// Layout.Size = box.Size;
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size);
}
public override void OnPaint(DeviceContext dc)
{
base.OnPaint(dc);
if (AutoSize)
CalculateSize(dc);
bool hidden = Layout.OverflowMode == OverflowMode.Hidden;
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
if (hidden)
dcb.PushScissor(ClientArea);
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));
var text = dc.ExtensionRequire<ITextRenderer>();
Color color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black;
Vector4 colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
text.DrawText(Vector2.Zero, colorVector, TextSize, Font.Base, Text);
if (hidden)
dcb.PopScissor();
dcb.PopTransforms();
}
}
}

View File

@@ -2,10 +2,10 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using Dashboard.Drawing;
using Dashboard.Pal;
using Dashboard.Windowing;
namespace Dashboard.Controls
@@ -36,20 +36,128 @@ namespace Dashboard.Controls
/// </summary>
public class MessageBox : Form
{
private Image? _icon;
private MessageBoxIcon _icon;
private MessageBoxButtons _buttons;
private ImageBox _iconBox = new ImageBox();
private Label _label = new Label();
private readonly List<Label> _buttons = new List<Label>();
private Container _main = new Container();
private Container _buttonsContainer = new Container();
public MessageBoxIcon Icon { get; set; }
public Image? CustomImage { get; set; }
public string? Message { get; set; }
public MessageBoxButtons Buttons { get; set; }
private Image? IconImage
{
get => _iconBox.Image;
set => _iconBox.Image = value;
}
public MessageBoxIcon Icon
{
get => _icon;
set
{
IconImage = value switch
{
MessageBoxIcon.Question => s_questionIcon,
MessageBoxIcon.Info => s_infoIcon,
MessageBoxIcon.Warning => s_warningIcon,
MessageBoxIcon.Error => s_errorIcon,
_ => null,
};
_icon = value;
}
}
public Image? CustomImage
{
get => Icon == MessageBoxIcon.Custom ? IconImage : null;
set
{
if (IconImage == null)
return;
Icon = MessageBoxIcon.Custom;
IconImage = value;
}
}
public string? Message
{
get => _label.Text;
set => _label.Text = value ?? String.Empty;
}
public MessageBoxButtons Buttons
{
get => _buttons;
set
{
_buttons = value;
UpdateButtons();
}
}
public ObservableCollection<string> CustomButtons { get; } = new ObservableCollection<string>();
public int Result { get; private set; }
public int Result { get; private set; } = -1;
public MessageBox(IWindow window) : base(window)
{
SetParent(this, _label);
// Layout.Rows.Clear();
// Layout.Rows.Add(-1);
// Layout.Rows.Add(48);
//
// Add(_main);
// _main.Layout.Columns.Clear();
// _main.Layout.Columns.Add(48);
// _main.Layout.Columns.Add(-1);
_main.Add(_iconBox);
_main.Add(_label);
_label.Layout.Column = 1;
Add(_buttonsContainer);
_buttonsContainer.Layout.Row = 1;
CustomButtons.CollectionChanged += (sender, ea) => UpdateButtons();
UpdateButtons();
}
private void UpdateButtons()
{
foreach (Control button in _buttonsContainer)
{
Remove(button);
}
IList<string> list = Buttons switch
{
MessageBoxButtons.Custom => CustomButtons,
MessageBoxButtons.AbortRetryIgnore => s_abortRetryContinue,
MessageBoxButtons.CancelRetryContinue => s_cancelRetryContinue,
MessageBoxButtons.OkCancel => s_okCancel,
MessageBoxButtons.RetryCancel => s_retryCancel,
MessageBoxButtons.YesNo => s_yesNo,
MessageBoxButtons.YesNoCancel => s_yesNoCancel,
_ => s_ok,
};
// _buttonsContainer.Clear();
// _buttonsContainer.Layout.Columns.Clear();
// for (int i = 0; i < list.Count; i++)
// {
// _buttonsContainer.Layout.Columns.Add(-1);
// string str = list[i];
//
// Button button = new Button() { Text = str };
// button.Clicked += (sender, ea) => ButtonClicked(sender, ea, i);
// button.Layout.Column = i;
// _buttonsContainer.Add(button);
// Add(button);
// }
}
private void ButtonClicked(object? sender, EventArgs ea, int i)
{
Result = i;
Dispose();
}
public static readonly Image s_questionIcon;

42
Dashboard/Drawing/Font.cs Normal file
View File

@@ -0,0 +1,42 @@
using System;
using System.IO;
using System.Net.Mime;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public class Font(IFont iFont) : IFont
{
public IFont Base => iFont;
public string Family => iFont.Family;
public FontWeight Weight => iFont.Weight;
public FontSlant Slant => iFont.Slant;
public FontStretch Stretch => iFont.Stretch;
public void Dispose()
{
iFont.Dispose();
}
public static Font Create(Stream stream)
{
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(stream);
return new Font(iFont);
}
public static Font Create(FontInfo info)
{
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(info);
return new Font(iFont);
}
public static Font Create(string path)
{
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(path);
return new Font(iFont);
}
}
}

View File

@@ -67,8 +67,6 @@ namespace Dashboard.Drawing
{
IImageLoader imageLoader = Application.Current.ExtensionRequire<IImageLoader>();
return new Image(imageLoader.LoadImageData(stream));
}
}
}

View File

@@ -1,11 +1,7 @@
using System.Drawing;
using System.Text;
using BlurgText;
using Dashboard.BlurgText;
using Dashboard.BlurgText;
using Dashboard.BlurgText.OpenGL;
using Dashboard.Controls;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.OpenGL;
using Dashboard.OpenTK.PAL2;
using Dashboard.Pal;
@@ -13,10 +9,7 @@ using Dashboard.StbImage;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Platform;
using Image = Dashboard.Drawing.Image;
using MouseMoveEventArgs = OpenTK.Platform.MouseMoveEventArgs;
using TK = OpenTK.Platform.Toolkit;
using Vector4 = System.Numerics.Vector4;
TK.Init(new ToolkitOptions()
{
@@ -51,24 +44,17 @@ Application app = new Pal2Application()
}
};
PhysicalWindow window;
CancellationTokenSource source = new CancellationTokenSource();
// GLEngine engine;
// ContextExecutor executor;
// DimUI dimUI;
Vector2 mousePos = Vector2.Zero;
Random r = new Random();
List<Vector3> points = new List<Vector3>();
// IFont font;
StringBuilder builder = new StringBuilder();
app.Initialize();
app.ExtensionRequire<StbImageLoader>();
app.ExtensionLoad(new BlurgTextExtension(new BlurgTextExtensionFactory()));
window = (PhysicalWindow)app.CreatePhysicalWindow();
PhysicalWindow window = (PhysicalWindow)app.CreatePhysicalWindow();
MessageBox box = MessageBox.Create(window, "Are you sure you want to exit?", "Confirm Exit", MessageBoxIcon.Question,
MessageBoxButtons.YesNo);
window.Title = "DashTerm";
// window.Title = "DashTerm";
TK.Window.SetMinClientSize(window.WindowHandle, 300, 200);
TK.Window.SetClientSize(window.WindowHandle, new Vector2i(320, 240));
TK.Window.SetBorderStyle(window.WindowHandle, WindowBorderStyle.ResizableBorder);
@@ -79,126 +65,14 @@ GLDeviceContext context = (GLDeviceContext)window.DeviceContext;
context.GLContext.MakeCurrent();
context.GLContext.SwapGroup.SwapInterval = 1;
// engine = new GLEngine();
// engine.Initialize();
//
// executor = engine.GetExecutor(context);
//
// dimUI = new DimUI(new DimUIConfig()
// {
// Font = new NamedFont("Noto Sans", 9f),
// });
EventQueue.EventRaised += (handle, type, eventArgs) =>
{
if (handle != window.WindowHandle)
return;
switch (type)
{
case PlatformEventType.Close:
source.Cancel();
break;
case PlatformEventType.MouseMove:
mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition;
break;
}
};
GL.Disable(EnableCap.DepthTest);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.ColorMask(true, true, true, true);
TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
// font = Typesetter.LoadFont("Nimbus Mono", 12f);
foreach (string str in typeof(Image).Assembly.GetManifestResourceNames()) Console.WriteLine(str);
BlurgFont font = context.ExtensionRequire<BlurgDcExtension>().Blurg
.QueryFont("Recursive Mono", FontWeight.Regular, false) ?? throw new Exception("Font not found");
window.EventRaised += (sender, ea) => {
if (ea is not PaintEventArgs)
return;
TK.Window.GetSize(window.WindowHandle, out Vector2i size);
// executor.BeginFrame();
//
// dimUI.Begin(new Box2d(0, 0, size.X, size.Y), window.DrawQueue);
// dimUI.Text("Hello World!");
// dimUI.Button("Cancel"); dimUI.SameLine();
// dimUI.Button("OK");
//
// dimUI.Input("type me!", builder);
//
// dimUI.BeginMenu();
//
// if (dimUI.MenuItem("File"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("New Window");
// dimUI.MenuItem("Preferences");
// dimUI.MenuItem("Exit");
// dimUI.EndMenu();
// }
//
// if (dimUI.MenuItem("Edit"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("Cut");
// dimUI.MenuItem("Copy");
// dimUI.MenuItem("Paste");
//
// if (dimUI.MenuItem("Send Char"))
// {
// dimUI.BeginMenu();
// dimUI.EndMenu();
// }
//
// dimUI.EndMenu();
// }
//
// if (dimUI.MenuItem("View"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("Clear");
//
// if (dimUI.MenuItem("Set Size"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("24 x 40");
// dimUI.MenuItem("25 x 40");
// dimUI.MenuItem("24 x 80");
// dimUI.MenuItem("25 x 80");
// dimUI.MenuItem("25 x 120");
// dimUI.EndMenu();
// }
//
// dimUI.EndMenu();
// }
//
// dimUI.Finish();
GL.Viewport(0, 0, size.X, size.Y);
GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.Disable(EnableCap.DepthTest);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.ColorMask(true, true, true, true);
ITexture texture = MessageBox.s_questionIcon.InternTexture(context);
// executor.Draw(window.DrawQueue, new RectangleF(0, 0, size.X, size.Y), 1.5f /*(window as IDpiAwareWindow)?.Scale ?? 1*/);
// executor.EndFrame();
context.Begin();
IImmediateMode imm = context.ExtensionRequire<IImmediateMode>();
imm.ClearColor(Color.Magenta);
imm.Line(new System.Numerics.Vector2(10, 10), new System.Numerics.Vector2(50, 50), 3, 0, new Vector4(0.2f, 0.2f, 1.0f, 1.0f));
BlurgDcExtension blurg = context.ExtensionRequire<BlurgDcExtension>();
blurg.DrawBlurgFormattedText(new BlurgFormattedText("Hello world!", font) { DefaultSize = 64f }, new System.Numerics.Vector3(24, 24, 1));
context.End();
};
window.DeviceContext.ExtensionRequire<IDeviceContextBase>().ScaleOverride = 1.5f;
app.Run(true, source.Token);