diff --git a/assets/Shaders/rectangle.frag b/assets/Shaders/rectangle.frag
index 722f3e2f2..04ccf1fdd 100644
--- a/assets/Shaders/rectangle.frag
+++ b/assets/Shaders/rectangle.frag
@@ -1,17 +1,39 @@
#version 150 core
in vec2 textureCoord;
+in vec2 fragOffset;
uniform vec2 uAlphaTest;
uniform bool uRectangleHasColour;
uniform vec4 uColor;
uniform sampler2D uTexture;
+uniform vec2 uSize;
+uniform float uCornerRadius;
out vec4 fragColor;
+float roundSDF(vec2 p, vec2 b, float r)
+{
+ vec2 q = abs(p) - b + r;
+ return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
+}
+
void main(void)
{
vec4 textureColour = texture(uTexture, textureCoord);
-
vec4 finalColor = textureColour * uColor;
+ if (uCornerRadius > 0.0)
+ {
+ vec2 halfSize = uSize * 0.5;
+ vec2 pos = (fragOffset - 0.5) * uSize;
+ float dist = roundSDF(pos, halfSize, uCornerRadius);
+
+ float alpha = 1.0 - smoothstep(-0.5, 0.5, dist);
+ if (alpha <= 0.0)
+ {
+ discard;
+ }
+ finalColor.a *= alpha;
+ }
+
/*
* NOTES:
* Unused alpha functions must not be added to the shader
diff --git a/assets/Shaders/rectangle.vert b/assets/Shaders/rectangle.vert
index 49ece19d9..5bf4d2221 100644
--- a/assets/Shaders/rectangle.vert
+++ b/assets/Shaders/rectangle.vert
@@ -5,40 +5,24 @@ uniform vec2 uPoint;
uniform vec2 uSize;
uniform vec2 uCoordinates;
out vec2 textureCoord;
-vec4 viewPos = vec4(0,0,0,0);
+out vec2 fragOffset;
+
+// Offset lookup table to map gl_VertexID to quad corners without branching
+const vec2 offsets[6] = vec2[](
+ vec2(0.0, 0.0), // Top-left
+ vec2(1.0, 0.0), // Top-right
+ vec2(1.0, 1.0), // Bottom-right
+ vec2(0.0, 0.0), // Top-left (second triangle)
+ vec2(0.0, 1.0), // Bottom-left
+ vec2(1.0, 1.0) // Bottom-right
+);
void main()
{
- if(gl_VertexID == 0)
- {
- viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y, 0), 1.0);
- textureCoord = vec2(0,0);
- }
- else if (gl_VertexID == 1)
- {
- viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y, 0), 1.0);
- textureCoord = vec2(uCoordinates.x,0);
- }
- else if (gl_VertexID == 2)
- {
- viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y + uSize.y, 0), 1.0);
- textureCoord = uCoordinates;
- }
- else if (gl_VertexID == 3)
- {
- viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y, 0), 1.0);
- textureCoord = vec2(0,0);
- }
- else if (gl_VertexID == 4)
- {
- viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y + uSize.y, 0), 1.0);
- textureCoord = vec2(0, uCoordinates.y);
- }
- else if (gl_VertexID == 5)
- {
- viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y + uSize.y, 0), 1.0);
- textureCoord = uCoordinates;
- }
-
+ // Calculate vertex position and texture coordinates based on current vertex ID offsets
+ vec2 offset = offsets[gl_VertexID];
+ vec4 viewPos = uCurrentModelViewMatrix * vec4(uPoint + offset * uSize, 0.0, 1.0);
+ textureCoord = offset * uCoordinates;
+ fragOffset = offset;
gl_Position = uCurrentProjectionMatrix * viewPos;
}
diff --git a/source/LibRender2/BaseRenderer.cs b/source/LibRender2/BaseRenderer.cs
index 57f4ca951..a11e781e5 100644
--- a/source/LibRender2/BaseRenderer.cs
+++ b/source/LibRender2/BaseRenderer.cs
@@ -27,6 +27,7 @@
using OpenBveApi.FileSystem;
using OpenBveApi.Hosts;
using OpenBveApi.Interface;
+using OpenBveApi.Graphics;
using OpenBveApi.Math;
using OpenBveApi.Objects;
using OpenBveApi.Routes;
@@ -42,7 +43,7 @@
namespace LibRender2
{
- public abstract class BaseRenderer
+ public abstract class BaseRenderer : OpenBveApi.Graphics.IGLRenderer
{
// constants
protected const float inv255 = 1.0f / 255.0f;
@@ -1873,5 +1874,29 @@ public void RunInRenderThread(ThreadStart job, int timeout)
Monitor.Wait(job, timeout);
}
}
+
+ ///
+ public bool LoadTexture(ref Texture texture, OpenGlTextureWrapMode wrapMode)
+ {
+ return currentHost.LoadTexture(ref texture, wrapMode);
+ }
+
+ ///
+ public void DrawRectangle(Texture texture, Vector2 location, Vector2 size, Color128 color, OpenGlTextureWrapMode? wrapMode = null, float cornerRadius = 0f)
+ {
+ Rectangle.Draw(texture, location, size, color, null, wrapMode, cornerRadius);
+ }
+
+ ///
+ public void DrawRectangleAlpha(Texture texture, Vector2 location, Vector2 size, Color128 color, Vector2 scale, OpenGlTextureWrapMode? wrapMode = null, float cornerRadius = 0f)
+ {
+ Rectangle.DrawAlpha(texture, location, size, color, scale, wrapMode, cornerRadius);
+ }
+
+ ///
+ public void DrawText(string text, Vector2 location, Color128 color)
+ {
+ OpenGlString.Draw(Fonts.NormalFont, text, location, TextAlignment.TopLeft, color);
+ }
}
}
diff --git a/source/LibRender2/LibRender2.csproj b/source/LibRender2/LibRender2.csproj
index dcea6d70e..482595deb 100644
--- a/source/LibRender2/LibRender2.csproj
+++ b/source/LibRender2/LibRender2.csproj
@@ -100,6 +100,7 @@
+
diff --git a/source/LibRender2/Primitives/Button.cs b/source/LibRender2/Primitives/Button.cs
index cff490b7d..2d4946ce0 100644
--- a/source/LibRender2/Primitives/Button.cs
+++ b/source/LibRender2/Primitives/Button.cs
@@ -1,4 +1,4 @@
-//Simplified BSD License (BSD-2-Clause)
+//Simplified BSD License (BSD-2-Clause)
//
//Copyright (c) 2023, Christopher Lees, The OpenBVE Project
//
@@ -38,7 +38,7 @@ public string Text
set
{
_text = value;
- Size = Font.MeasureString(Text) * 1.5 * Renderer.currentOptions.UserInterfaceScaleFactor;
+ Size = Font.MeasureString(Text) * 1.5 * baseRenderer.currentOptions.UserInterfaceScaleFactor;
}
}
@@ -57,9 +57,11 @@ public string Text
/// The font for the button
public OpenGlFont Font;
- public Button(BaseRenderer renderer, string text) : base(renderer)
+ private BaseRenderer baseRenderer => Renderer as BaseRenderer;
+
+ public Button(IGLRenderer renderer, string text) : base(renderer)
{
- Font = Renderer.Fonts.LargeFont;
+ Font = baseRenderer.Fonts.LargeFont;
Text = text;
Enabled = true;
// default colors to match GLMenu
@@ -75,12 +77,12 @@ public override void Draw()
{
return;
}
- Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor);
+ baseRenderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor, null, null, CornerRadius);
if (CurrentlySelected && Enabled)
{
- Renderer.Rectangle.Draw(Texture, Location + Size * 0.1, Size - (Size * 0.2), HighlightColor);
+ baseRenderer.Rectangle.Draw(Texture, Location + Size * 0.1, Size - (Size * 0.2), HighlightColor, null, null, CornerRadius * 0.8f);
}
- Renderer.OpenGlString.Draw(Font, Text, Location + (Size * 0.15), TextAlignment.TopLeft, Enabled ? EnabledTextColor : DisabledTextColor);
+ baseRenderer.OpenGlString.Draw(Font, Text, Location + (Size * 0.15), TextAlignment.TopLeft, Enabled ? EnabledTextColor : DisabledTextColor);
}
public override void MouseMove(int x, int y)
diff --git a/source/LibRender2/Primitives/GLControl.cs b/source/LibRender2/Primitives/GLControl.cs
index 43ff49f02..cb691484a 100644
--- a/source/LibRender2/Primitives/GLControl.cs
+++ b/source/LibRender2/Primitives/GLControl.cs
@@ -1,4 +1,4 @@
-//Simplified BSD License (BSD-2-Clause)
+//Simplified BSD License (BSD-2-Clause)
//
//Copyright (c) 2023, Christopher Lees, The OpenBVE Project
//
@@ -24,6 +24,7 @@
using System;
using OpenBveApi.Colors;
+using OpenBveApi.Graphics;
using OpenBveApi.Math;
using OpenBveApi.Textures;
@@ -32,8 +33,8 @@ namespace LibRender2.Primitives
/// An abstract OpenGL based control
public abstract class GLControl
{
- /// Holds a reference to the base renderer
- internal readonly BaseRenderer Renderer;
+ /// Holds a reference to the base renderer interface
+ public readonly IGLRenderer Renderer;
/// The background color for the control
public Color128 BackgroundColor;
/// The texture for the control
@@ -42,6 +43,8 @@ public abstract class GLControl
public Vector2 Location;
/// The stored size for the control
public Vector2 Size;
+ /// The corner radius for rounded corners (in pixels)
+ public float CornerRadius;
/// Whether the control is currently selected by the mouse
public bool CurrentlySelected;
/// The event handler for the OnClick event
@@ -49,7 +52,7 @@ public abstract class GLControl
/// Whether the control is currently visible
public bool IsVisible;
- protected GLControl(BaseRenderer renderer)
+ protected GLControl(IGLRenderer renderer)
{
Renderer = renderer;
}
diff --git a/source/LibRender2/Primitives/Label.cs b/source/LibRender2/Primitives/Label.cs
index 710fbc4a6..8601267d2 100644
--- a/source/LibRender2/Primitives/Label.cs
+++ b/source/LibRender2/Primitives/Label.cs
@@ -1,4 +1,4 @@
-//Simplified BSD License (BSD-2-Clause)
+//Simplified BSD License (BSD-2-Clause)
//
//Copyright (c) 2022, Christopher Lees, The OpenBVE Project
//
@@ -37,10 +37,12 @@ public class Label : GLControl
/// The font for the label
public OpenGlFont Font;
- public Label(BaseRenderer renderer, string text) : base(renderer)
+ private BaseRenderer baseRenderer => Renderer as BaseRenderer;
+
+ public Label(IGLRenderer renderer, string text) : base(renderer)
{
Text = text;
- Font = Renderer.Fonts.LargeFont;
+ Font = baseRenderer.Fonts.LargeFont;
Size = Font.MeasureString(Text) * 1.5;
// default colors to match GLMenu
BackgroundColor = Color128.Black;
@@ -49,8 +51,8 @@ public Label(BaseRenderer renderer, string text) : base(renderer)
public override void Draw()
{
- Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor);
- Renderer.OpenGlString.Draw(Font, Text, Location + (Size * 0.15), TextAlignment.TopLeft, TextColor);
+ baseRenderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor, null, null, CornerRadius);
+ baseRenderer.OpenGlString.Draw(Font, Text, Location + (Size * 0.15), TextAlignment.TopLeft, TextColor);
}
}
}
diff --git a/source/LibRender2/Primitives/Picturebox.cs b/source/LibRender2/Primitives/Picturebox.cs
index 986db85fe..21fddc4a5 100644
--- a/source/LibRender2/Primitives/Picturebox.cs
+++ b/source/LibRender2/Primitives/Picturebox.cs
@@ -1,4 +1,5 @@
using OpenBveApi.Colors;
+using OpenBveApi.Graphics;
using OpenBveApi.Math;
using OpenBveApi.Textures;
using OpenTK.Graphics.OpenGL;
@@ -13,14 +14,14 @@ public class Picturebox : GLControl
private bool flipX;
private bool flipY;
- public Picturebox(BaseRenderer renderer) : base(renderer)
+ public Picturebox(IGLRenderer renderer) : base(renderer)
{
SizeMode = ImageSizeMode.Zoom;
}
public override void Draw()
{
- if (!Renderer.currentHost.LoadTexture(ref Texture, OpenGlTextureWrapMode.ClampClamp))
+ if (!Renderer.LoadTexture(ref Texture, OpenGlTextureWrapMode.ClampClamp))
{
return;
}
@@ -31,7 +32,7 @@ public override void Draw()
{
case ImageSizeMode.Normal:
//Draw box containing backing color first
- Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor);
+ Renderer.DrawRectangle(Texture, Location, Size, BackgroundColor);
//Calculate the new size
newSize = Texture.Size;
if (newSize.X > Size.X)
@@ -44,11 +45,11 @@ public override void Draw()
newSize.Y = Size.Y;
}
//Two-pass draw the texture in appropriate place
- Renderer.Rectangle.DrawAlpha(Texture, Location, newSize, Color128.White, new Vector2(newSize / Size));
+ Renderer.DrawRectangleAlpha(Texture, Location, newSize, Color128.White, new Vector2(newSize / Size));
break;
case ImageSizeMode.Center:
//Draw box containing backing color first
- Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor);
+ Renderer.DrawRectangle(Texture, Location, Size, BackgroundColor);
//Calculate the new size
newSize = Texture.Size;
if (newSize.X > Size.X)
@@ -61,15 +62,15 @@ public override void Draw()
newSize.Y = Size.Y;
}
//Two-pass draw the texture in appropriate place
- Renderer.Rectangle.DrawAlpha(Texture, Location + new Vector2(newSize - Size) / 2, newSize, Color128.White, new Vector2(newSize / Size));
+ Renderer.DrawRectangleAlpha(Texture, Location + new Vector2(newSize - Size) / 2, newSize, Color128.White, new Vector2(newSize / Size));
break;
case ImageSizeMode.Stretch:
//No need to draw a backing color box as texture covers the whole thing
- Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor);
+ Renderer.DrawRectangle(Texture, Location, Size, BackgroundColor);
break;
case ImageSizeMode.Zoom:
//Draw box containing backing color first
- Renderer.Rectangle.Draw(null, Location, Size, BackgroundColor);
+ Renderer.DrawRectangle(null, Location, Size, BackgroundColor);
//Calculate the new size
Vector2 ratio = Size / Texture.Size;
double newRatio = ratio.X < ratio.Y ? ratio.X : ratio.Y;
@@ -84,7 +85,7 @@ public override void Draw()
{
wrapMode = wrapMode == OpenGlTextureWrapMode.RepeatClamp ? OpenGlTextureWrapMode.RepeatRepeat : OpenGlTextureWrapMode.ClampRepeat;
}
- Renderer.Rectangle.DrawAlpha(Texture, new Vector2(Location.X + (Size.X - newSize.X) / 2,Location.Y + (Size.Y - newSize.Y) / 2), newSize, Color128.White, new Vector2(flipX ? -1 : 1,flipY ? -1 : 1), wrapMode);
+ Renderer.DrawRectangleAlpha(Texture, new Vector2(Location.X + (Size.X - newSize.X) / 2,Location.Y + (Size.Y - newSize.Y) / 2), newSize, Color128.White, new Vector2(flipX ? -1 : 1,flipY ? -1 : 1), wrapMode);
break;
}
}
diff --git a/source/LibRender2/Primitives/Rectangle.cs b/source/LibRender2/Primitives/Rectangle.cs
index 93f096506..80f85b3a6 100644
--- a/source/LibRender2/Primitives/Rectangle.cs
+++ b/source/LibRender2/Primitives/Rectangle.cs
@@ -60,16 +60,17 @@ internal Rectangle(BaseRenderer renderer)
/// The color, or a null reference.
/// The texture coordinates to be applied
/// A wrap mode if overriding that of the texture
- public void DrawAlpha(Texture texture, Vector2 point, Vector2 size, Color128? color = null, Vector2? textureCoordinates = null, OpenGlTextureWrapMode? wrapMode = null)
+ /// Corner radius in pixels for rounded corners
+ public void DrawAlpha(Texture texture, Vector2 point, Vector2 size, Color128? color = null, Vector2? textureCoordinates = null, OpenGlTextureWrapMode? wrapMode = null, float cornerRadius = 0f)
{
renderer.UnsetBlendFunc();
renderer.SetAlphaFunc(AlphaFunction.Equal, 1.0f);
GL.DepthMask(true);
- Draw(texture, point, size, color, textureCoordinates, wrapMode);
+ Draw(texture, point, size, color, textureCoordinates, wrapMode, cornerRadius);
renderer.SetBlendFunc();
renderer.SetAlphaFunc(AlphaFunction.Less, 1.0f);
GL.DepthMask(false);
- Draw(texture, point, size, color, textureCoordinates, wrapMode);
+ Draw(texture, point, size, color, textureCoordinates, wrapMode, cornerRadius);
renderer.SetAlphaFunc(AlphaFunction.Equal, 1.0f);
}
@@ -102,17 +103,18 @@ public void DrawAlpha(Texture texture, Vector2 point, Color128? color = null, Ve
/// The color, or a null reference.
/// The texture coordinates to be applied
/// A wrap mode if overriding that of the texture
- public void Draw(Texture texture, Vector2 point, Vector2 size, Color128? color = null, Vector2? textureCoordinates = null, OpenGlTextureWrapMode? wrapMode = null)
+ /// Corner radius in pixels for rounded corners
+ public void Draw(Texture texture, Vector2 point, Vector2 size, Color128? color = null, Vector2? textureCoordinates = null, OpenGlTextureWrapMode? wrapMode = null, float cornerRadius = 0f)
{
if (renderer.AvailableNewRenderer && Shader != null)
{
if (textureCoordinates == null)
{
- DrawWithShader(texture, point, size, color, Vector2.One, wrapMode);
+ DrawWithShader(texture, point, size, color, Vector2.One, wrapMode, cornerRadius);
}
else
{
- DrawWithShader(texture, point, size, color, (Vector2)textureCoordinates, wrapMode);
+ DrawWithShader(texture, point, size, color, (Vector2)textureCoordinates, wrapMode, cornerRadius);
}
}
else
@@ -234,7 +236,7 @@ private void DrawImmediate(Texture texture, Vector2 point, Vector2 size, Color12
GL.PopMatrix();
}
- private void DrawWithShader(Texture texture, Vector2 point, Vector2 size, Color128? color, Vector2 coordinates, OpenGlTextureWrapMode? wrapMode = null)
+ private void DrawWithShader(Texture texture, Vector2 point, Vector2 size, Color128? color, Vector2 coordinates, OpenGlTextureWrapMode? wrapMode = null, float cornerRadius = 0f)
{
Shader.Activate();
if (wrapMode == null)
@@ -258,6 +260,7 @@ private void DrawWithShader(Texture texture, Vector2 point, Vector2 size, Color1
Shader.SetPoint(point);
Shader.SetSize(size);
Shader.SetCoordinates(coordinates);
+ Shader.SetCornerRadius(cornerRadius);
/*
* In order to call GL.DrawArrays with procedural data within the shader,
* we first need to bind a dummy VAO
diff --git a/source/LibRender2/Primitives/Textbox.cs b/source/LibRender2/Primitives/Textbox.cs
index 0d224d2a0..e12cf9406 100644
--- a/source/LibRender2/Primitives/Textbox.cs
+++ b/source/LibRender2/Primitives/Textbox.cs
@@ -86,7 +86,9 @@ private List WrappedLines(int width)
return wrappedLines;
}
- public Textbox(BaseRenderer Renderer, OpenGlFont Font, Color128 FontColor, Color128 backgroundColor) : base(Renderer)
+ private BaseRenderer baseRenderer => Renderer as BaseRenderer;
+
+ public Textbox(IGLRenderer Renderer, OpenGlFont Font, Color128 FontColor, Color128 backgroundColor) : base(Renderer)
{
myFont = Font;
myFontColor = FontColor;
@@ -108,7 +110,7 @@ public void VerticalScroll(int numberOfLines)
public override void Draw()
{
- Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor); //Draw the backing rectangle first
+ baseRenderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor); //Draw the backing rectangle first
if (string.IsNullOrEmpty(Text))
{
return;
@@ -118,7 +120,7 @@ public override void Draw()
if (splitString.Count == 1)
{
//DRAW SINGLE LINE
- Renderer.OpenGlString.Draw(myFont, Text, new Vector2(Location.X + Border, Location.Y + Border), TextAlignment.TopLeft, myFontColor);
+ baseRenderer.OpenGlString.Draw(myFont, Text, new Vector2(Location.X + Border, Location.Y + Border), TextAlignment.TopLeft, myFontColor);
CanScroll = false;
}
else
@@ -131,14 +133,14 @@ public override void Draw()
CanScroll = maxFittingLines < splitString.Count;
if (CanScroll)
{
- Renderer.Rectangle.Draw(null, new Vector2(Location.X + Size.X - 12, Location.Y + 2), new Vector2(8, Size.Y - 4), Color128.Grey); //Backing rectangle
+ baseRenderer.Rectangle.Draw(null, new Vector2(Location.X + Size.X - 12, Location.Y + 2), new Vector2(8, Size.Y - 4), Color128.Grey); //Backing rectangle
}
//DRAW SPLIT LINES
int currentLine = topLine;
int bottomLine = Math.Min(maxFittingLines, splitString.Count);
for (int i = 0; i < bottomLine; i++)
{
- Renderer.OpenGlString.Draw(myFont, splitString[currentLine], new Vector2(Location.X + Border, Location.Y + Border + myFont.FontSize * i), TextAlignment.TopLeft, myFontColor);
+ baseRenderer.OpenGlString.Draw(myFont, splitString[currentLine], new Vector2(Location.X + Border, Location.Y + Border + myFont.FontSize * i), TextAlignment.TopLeft, myFontColor);
currentLine++;
}
@@ -146,7 +148,7 @@ public override void Draw()
{
double scrollBarHeight = (Size.Y - 4) * maxFittingLines / splitString.Count;
double percentageScroll = topLine / (double)(splitString.Count - maxFittingLines);
- Renderer.Rectangle.Draw(null, new Vector2(Location.X + Size.X - 13, Location.Y + (Size.Y - scrollBarHeight) * percentageScroll), new Vector2(10, scrollBarHeight), myScrollbarColor);
+ baseRenderer.Rectangle.Draw(null, new Vector2(Location.X + Size.X - 13, Location.Y + (Size.Y - scrollBarHeight) * percentageScroll), new Vector2(10, scrollBarHeight), myScrollbarColor);
}
}
@@ -157,11 +159,11 @@ public override void MouseMove(int x, int y)
if (x > Location.X && x < Location.X + Size.X && y > Location.Y && y < Location.Y + Size.Y)
{
CurrentlySelected = true;
- Renderer.SetCursor(CanScroll ? AvailableCursors.ScrollCursor : OpenTK.MouseCursor.Default);
+ baseRenderer.SetCursor(CanScroll ? AvailableCursors.ScrollCursor : OpenTK.MouseCursor.Default);
}
else
{
- Renderer.SetCursor(OpenTK.MouseCursor.Default);
+ baseRenderer.SetCursor(OpenTK.MouseCursor.Default);
CurrentlySelected = false;
}
}
diff --git a/source/LibRender2/Primitives/VerticalLayoutGroup.cs b/source/LibRender2/Primitives/VerticalLayoutGroup.cs
new file mode 100644
index 000000000..50140bf33
--- /dev/null
+++ b/source/LibRender2/Primitives/VerticalLayoutGroup.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+using OpenBveApi.Graphics;
+using OpenBveApi.Math;
+
+namespace LibRender2.Primitives
+{
+ /// A container control that automatically positions its children vertically
+ public class VerticalLayoutGroup : GLControl
+ {
+ /// The child controls of this layout group
+ public readonly List Children = new List();
+
+ /// The vertical spacing between child controls
+ public double Spacing = 10.0;
+
+ /// The padding inside the layout group borders
+ public Vector2 Padding = new Vector2(10.0, 10.0);
+
+ /// Creates a new VerticalLayoutGroup
+ public VerticalLayoutGroup(IGLRenderer renderer) : base(renderer)
+ {
+ }
+
+ /// Draws the layout group and positions/draws all visible children
+ public override void Draw()
+ {
+ if (!IsVisible)
+ {
+ return;
+ }
+
+ // Draw background if background color is set and size is non-zero
+ if (BackgroundColor.A > 0.0f && Size.X > 0f && Size.Y > 0f)
+ {
+ Renderer.DrawRectangle(Texture, Location, Size, BackgroundColor, null, (float)CornerRadius);
+ }
+
+ double currentY = Location.Y + Padding.Y;
+ foreach (var child in Children)
+ {
+ if (!child.IsVisible)
+ {
+ continue;
+ }
+
+ child.Location.X = Location.X + Padding.X;
+ child.Location.Y = currentY;
+ child.Size.X = Size.X - (Padding.X * 2);
+
+ child.Draw();
+
+ currentY += child.Size.Y + Spacing;
+ }
+ }
+
+ /// Passes mouse down events to children
+ public override void MouseDown(int x, int y)
+ {
+ if (!IsVisible)
+ {
+ return;
+ }
+
+ foreach (var child in Children)
+ {
+ if (child.IsVisible)
+ {
+ child.MouseDown(x, y);
+ }
+ }
+ }
+
+ /// Passes mouse move events to children
+ public override void MouseMove(int x, int y)
+ {
+ if (!IsVisible)
+ {
+ return;
+ }
+
+ foreach (var child in Children)
+ {
+ if (child.IsVisible)
+ {
+ child.MouseMove(x, y);
+ }
+ }
+ }
+ }
+}
diff --git a/source/LibRender2/Shaders/Shader.cs b/source/LibRender2/Shaders/Shader.cs
index d2a97bd39..dd59fb947 100644
--- a/source/LibRender2/Shaders/Shader.cs
+++ b/source/LibRender2/Shaders/Shader.cs
@@ -67,6 +67,7 @@ public class Shader : AbstractShader
private readonly int uLightSpaceMatrix3Location;
private readonly int uModelMatrixLocation;
private readonly int uCurrentViewMatrixLocation;
+ private readonly int uCornerRadiusLocation;
///
@@ -103,6 +104,7 @@ public Shader(BaseRenderer Renderer, string vertexShaderName, string fragmentSha
uLightSpaceMatrix3Location = GL.GetUniformLocation(Handle, "uLightSpaceMatrix3");
uModelMatrixLocation = GL.GetUniformLocation(Handle, "uModelMatrix");
uCurrentViewMatrixLocation = GL.GetUniformLocation(Handle, "uCurrentViewMatrix");
+ uCornerRadiusLocation = GL.GetUniformLocation(Handle, "uCornerRadius");
VertexLayout = GetVertexLayout();
UniformLayout = GetUniformLayout();
@@ -399,6 +401,11 @@ public void SetSize(Vector2 size)
GL.ProgramUniform2(Handle, UniformLayout.Size, (float)size.X, (float) size.Y);
}
+ public void SetCornerRadius(float radius)
+ {
+ GL.ProgramUniform1(Handle, uCornerRadiusLocation, radius);
+ }
+
public void SetColor(Color128 color)
{
GL.ProgramUniform4(Handle, UniformLayout.Color, color.R, color.G, color.B, color.A);
diff --git a/source/OpenBveApi/Graphics/IGLRenderer.cs b/source/OpenBveApi/Graphics/IGLRenderer.cs
new file mode 100644
index 000000000..9487790ee
--- /dev/null
+++ b/source/OpenBveApi/Graphics/IGLRenderer.cs
@@ -0,0 +1,22 @@
+using OpenBveApi.Colors;
+using OpenBveApi.Math;
+using OpenBveApi.Textures;
+
+namespace OpenBveApi.Graphics
+{
+ /// Defines a platform-independent interface for basic 2D drawing capabilities
+ public interface IGLRenderer
+ {
+ /// Loads a texture into the graphics device
+ bool LoadTexture(ref Texture texture, OpenGlTextureWrapMode wrapMode);
+
+ /// Draws a simple solid-colored rectangle or textured rectangle
+ void DrawRectangle(Texture texture, Vector2 location, Vector2 size, Color128 color, OpenGlTextureWrapMode? wrapMode = null, float cornerRadius = 0f);
+
+ /// Draws a rectangle with alpha scaling
+ void DrawRectangleAlpha(Texture texture, Vector2 location, Vector2 size, Color128 color, Vector2 scale, OpenGlTextureWrapMode? wrapMode = null, float cornerRadius = 0f);
+
+ /// Draws text on screen
+ void DrawText(string text, Vector2 location, Color128 color);
+ }
+}
diff --git a/source/OpenBveApi/OpenBveApi.csproj b/source/OpenBveApi/OpenBveApi.csproj
index c75e05d79..2a96e8784 100644
--- a/source/OpenBveApi/OpenBveApi.csproj
+++ b/source/OpenBveApi/OpenBveApi.csproj
@@ -118,6 +118,7 @@
+