Create basic controls without much regards to layout rules.

This commit is contained in:
H. Utku Maden 2025-11-21 23:04:37 +03:00
parent 5cba1ab7db
commit 4f67e0fb75
16 changed files with 290 additions and 59 deletions

View File

@ -6,18 +6,18 @@ namespace Dashboard.Drawing
{
public interface IDeviceContextBase : IDeviceContextExtension
{
RectangleF ClipRegion { get; }
RectangleF ScissorRegion { 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(RectangleF scissorRegion);
void PushScissor(Box2d scissorRegion);
void PopScissor();
void ResetTransforms();

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

@ -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

@ -7,14 +7,15 @@ 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<RectangleF> _scissorRegions = new Stack<RectangleF>();
private readonly Stack<Box2d> _clipRegions = new Stack<Box2d>();
private readonly Stack<Box2d> _scissorRegions = new Stack<Box2d>();
public DeviceContext Context { get; private set; } = null!;
IContextBase IContextExtensionBase.Context => Context;
@ -22,8 +23,8 @@ namespace Dashboard.OpenGL.Drawing
public string DriverVendor => "Dashboard";
public Version DriverVersion => new Version(0, 1);
public RectangleF ClipRegion => _clipRegions.Peek();
public RectangleF ScissorRegion => _scissorRegions.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;
@ -50,17 +51,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);
@ -76,11 +77,11 @@ namespace Dashboard.OpenGL.Drawing
{
GL.Disable(EnableCap.ScissorTest);
_scissorRegions.Clear();
SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
_scissorRegions.Push(new RectangleF(0,0, size.Width, size.Height));
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
_scissorRegions.Push(new Box2d(Vector2.Zero, size));
}
public void PushScissor(RectangleF scissorRegion)
public void PushScissor(Box2d scissorRegion)
{
GL.Enable(EnableCap.ScissorTest);
@ -101,30 +102,30 @@ namespace Dashboard.OpenGL.Drawing
SetScissor(ClipRegion);
}
private void SetClip(RectangleF rect)
private void SetClip(Box2d rect)
{
SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
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(RectangleF rect)
void SetScissor(Box2d rect)
{
SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
GL.Scissor(
(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));
}
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);

View File

@ -110,7 +110,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 +149,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);
@ -189,7 +187,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

@ -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

@ -121,6 +121,7 @@ namespace Dashboard.OpenTK.PAL2
return;
}
break;
// Mouse Events
case PlatformEventType.MouseDown:
{
MouseButtonDownEventArgs down = (MouseButtonDownEventArgs)args;
@ -161,6 +162,8 @@ namespace Dashboard.OpenTK.PAL2
info.Window.SendEvent(this, scroll2);
break;
}
// Keyboard & Text Events
case PlatformEventType.KeyDown:
{
KeyDownEventArgs down = (KeyDownEventArgs)args;
@ -185,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;

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

@ -0,0 +1,67 @@
using System;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
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;
Size = box.Size;
ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Size);
}
public override void OnPaint(DeviceContext dc)
{
base.OnPaint(dc);
if (AutoSize)
CalculateSize(dc);
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
if (HideOverflow)
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 (HideOverflow)
dcb.PopScissor();
dcb.PopTransforms();
}
}
}

View File

@ -41,7 +41,7 @@ namespace Dashboard.Controls
base.OnPaint(dc);
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
dcb.PushClip(new RectangleF(ClientArea.Left, ClientArea.Bottom, ClientArea.Size.X, ClientArea.Size.Y));
dcb.PushClip(ClientArea);
// dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Bottom, 0));
LayoutChildren();

View File

@ -72,6 +72,11 @@ namespace Dashboard.Controls
public event EventHandler? Disposing;
public event EventHandler? Resized;
public virtual Vector2 CalculateIntrinsicSize()
{
return Vector2.Max(Vector2.Zero, Vector2.Max(Size, MinimumSize));
}
public virtual void OnPaint(DeviceContext dc)
{
Painting?.Invoke(this, dc);

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;
Size = CalculateIntrinsicSize();
dc.ExtensionRequire<IImmediateMode>().Image(new Box2d(ClientArea.Min, ClientArea.Min + Size), new Box2d(0, 0, 1, 1), 0, Image.InternTexture(dc));
base.OnPaint(dc);
}
}
}

View File

@ -8,6 +8,8 @@ namespace Dashboard.Controls
{
public class Label : Control
{
private Vector2 _intrinsicSize = Vector2.Zero;
public bool AutoSize { get; set; } = true;
public string Text { get; set; } = "";
@ -18,9 +20,15 @@ namespace Dashboard.Controls
public float TextSize { get; set; } = 12f;
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
public override Vector2 CalculateIntrinsicSize()
{
return _intrinsicSize;
}
protected void CalculateSize(DeviceContext dc)
{
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
_intrinsicSize = box.Size;
Size = box.Size;
ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Size);
}
@ -34,7 +42,7 @@ namespace Dashboard.Controls
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
if (HideOverflow)
dcb.PushScissor(new RectangleF(ClientArea.Left, ClientArea.Top, ClientArea.Size.X, ClientArea.Size.Y));
dcb.PushScissor(ClientArea);
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));

View File

@ -2,6 +2,7 @@ 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;
@ -35,12 +36,47 @@ 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 readonly List<Button> _buttonControls = new List<Button>();
public MessageBoxIcon Icon { get; set; }
public Image? CustomImage { 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
{
@ -48,13 +84,62 @@ namespace Dashboard.Controls
set => _label.Text = value ?? String.Empty;
}
public MessageBoxButtons Buttons { get; set; }
public MessageBoxButtons Buttons
{
get => _buttons;
set
{
_buttons = value;
UpdateButtons();
}
}
public ObservableCollection<string> CustomButtons { get; } = new ObservableCollection<string>();
public int Result { get; private set; }
public MessageBox(IWindow window) : base(window)
{
Add(_label);
Add(_iconBox);
CustomButtons.CollectionChanged += (sender, ea) => UpdateButtons();
}
private void UpdateButtons()
{
foreach (Button button in _buttonControls)
{
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,
};
_buttonControls.Clear();
for (int i = 0; i < list.Count; i++)
{
string str = list[i];
Button button = new Button() { Text = str };
button.Clicked += (sender, ea) => ButtonClicked(sender, ea, i);
_buttonControls.Add(button);
Add(button);
}
}
private void ButtonClicked(object? sender, EventArgs ea, int i)
{
Result = i;
Dispose();
}
public static readonly Image s_questionIcon;

View File

@ -78,19 +78,11 @@ TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
BlurgFont font = context.ExtensionRequire<BlurgDcExtension>().Blurg
.QueryFont("Rec Mono Linear", FontWeight.Regular, false) ?? throw new Exception("Font not found");
window.DeviceContext.ExtensionRequire<IDeviceContextBase>().ScaleOverride = 1.5f;
window.EventRaised += (sender, ea) => {
if (ea is not PaintEventArgs paint)
return;
paint.DeviceContext.ExtensionRequire<IDeviceContextBase>().ScaleOverride = 1.5f;
ITexture texture = MessageBox.s_questionIcon.InternTexture(context);
context.Begin();
BlurgDcExtension blurg = context.ExtensionRequire<BlurgDcExtension>();
blurg.DrawBlurgFormattedText(new BlurgFormattedText("Hello world!", font) { DefaultSize = 64f }, new System.Numerics.Vector3(24, 24, 1));
context.End();
};
app.Run(true, source.Token);