Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
DirectRectangle.cs
1using System.Drawing;
2using System.Runtime.CompilerServices;
9using SkiaSharp;
10
12
68public class DirectRectangle : DirectDrawingMovableBase
69{
73 public enum ImageFillMode
74 {
77
80
83
86
89
92 }
93
94 private readonly SKPaint _fillPaint; // cached fill
95 private readonly SKPaint _strokePaint; // cached stroke
96 private readonly SKPaint _imagePaint = new()
97 {
98 IsAntialias = true,
99 FilterQuality = SKFilterQuality.Medium,
100 BlendMode = SKBlendMode.SrcOver
101 };
102
103 private SKColor? _borderColor; // optional distinct border color
104 private SKShader? _fillShader; // optional fill shader
105 private SKImage? _fillImage;
106 private SKBitmap? _fillBitmap;
107 private ImageFillMode _imageFillMode = ImageFillMode.Stretch;
108 private float _imageScale = 1f;
109 private SKPoint _imageOffsetPx;
110 private bool _resourcesDisposed;
111
112 private bool _isFilled;
113 private float _cornerRadius;
114 private float[]? _dashPattern;
115 private StrokeAlign _strokeAlign = StrokeAlign.Center;
116 private bool _needsRebuildPaints = true; // mark when properties change
117
118 // --- Pulse settings ---
119 private bool _pulseFillEnabled;
120 private SKColor _pulseFillFrom, _pulseFillTo;
121 private float _pulseFillPeriodSec = 1f;
122
123 private bool _pulseBorderEnabled;
124 private SKColor _pulseBorderFrom, _pulseBorderTo;
125 private float _pulseBorderPeriodSec = 1f;
126
127 private enum PulseWave
128 {
132 Sine,
136 Triangle
137 }
138 private PulseWave _pulseFillWave = PulseWave.Sine;
139 private PulseWave _pulseBorderWave = PulseWave.Sine;
140
141 // --- Time keeping for Update(tick) ---
142 private long _pulseLastTick = 0;
143 private float _timeSec; // accumulated seconds
144
145 private DirectRectangle(Color color,
146 RenderSurfaceHostBase renderSurfaceHost,
148 SceneLayer? sceneLayer,
149 View? view,
150 Rectangle? screenBounds,
151 Rectangle? worldBounds,
152 string? nickname = null)
153 : base(renderSurfaceHost, mode, sceneLayer, view, screenBounds, worldBounds, nickname)
154 {
155 // initialize with defaults; actual paints built lazily
156 _fillPaint = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Fill };
157 _strokePaint = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Stroke, StrokeWidth = 1 };
158 SetColor(color); // sets base color and marks rebuild
159 SetBlendMode(SKBlendMode.SrcOver);
160 SetFilled(false);
161 }
162
183 public DirectRectangle(Color color,
184 RenderSurfaceHostBase renderSurfaceHost,
185 SceneLayer sceneLayer,
186 Rectangle worldBounds,
187 string? nickname = null)
188 : this(color, renderSurfaceHost, DirectDrawingMode.SceneLayer, sceneLayer, null, null, worldBounds, nickname) { }
189
210 public DirectRectangle(Color color,
211 RenderSurfaceHostBase renderSurfaceHost,
212 View view,
213 Rectangle screenBounds,
214 string? nickname = null)
215 : this(color, renderSurfaceHost, DirectDrawingMode.View, null, view, screenBounds, null, nickname) { }
216
233 public DirectRectangle SetColor(Color color)
234 {
235 // store as stroke/ fill base via rebuild
236 var sk = color.ToSKColor();
237 // Set on one paint; rebuild will propagate
238 _fillPaint.Color = sk;
239 _strokePaint.Color = sk;
240 _needsRebuildPaints = true;
241 return this;
242 }
243
266 public DirectRectangle SetBorderColor(Color color)
267 {
268 _borderColor = color.ToSKColor();
269 _needsRebuildPaints = true;
270 return this;
271 }
272
293 public DirectRectangle SetAlpha(int alpha)
294 {
295 var c = _fillPaint.Color;
296 var withA = new SKColor(c.Red, c.Green, c.Blue, (byte)alpha);
297 _fillPaint.Color = withA;
298 // Only change stroke when it's not using a distinct border color
299 if (_borderColor is null)
300 _strokePaint.Color = withA;
301 _needsRebuildPaints = true;
302 return this;
303 }
304
325 public DirectRectangle SetFilled(bool isFilled)
326 {
327 _isFilled = isFilled;
328 _needsRebuildPaints = true;
329 return this;
330 }
331
351 public DirectRectangle SetStrokeWidth(float width)
352 {
353 _strokePaint.StrokeWidth = width;
354 _needsRebuildPaints = true;
355 return this;
356 }
357
382 public DirectRectangle SetCornerRadius(float radius)
383 {
384 _cornerRadius = radius;
385 return this;
386 }
387
411 public DirectRectangle SetDashPattern(float dashLength, float gapLength)
412 {
413 _dashPattern = new[] { dashLength, gapLength };
414 // path effect is applied on draw; no rebuild needed
415 return this;
416 }
417
432 public DirectRectangle ClearDashPattern()
433 {
434 _dashPattern = null;
435 return this;
436 }
437
463 public DirectRectangle SetBlendMode(SKBlendMode mode)
464 {
465 // apply to both paints
466 _fillPaint.BlendMode = mode;
467 _strokePaint.BlendMode = mode;
468 _imagePaint.BlendMode = mode;
469 // no rebuild needed
470 return this;
471 }
472
506 public DirectRectangle SetStrokeAlign(StrokeAlign align)
507 {
508 _strokeAlign = align;
509 return this;
510 }
511
542 public DirectRectangle PulseFill(Color from, Color to, float periodSec, bool enabled = true, bool triangle = false)
543 {
544 _pulseFillEnabled = enabled;
545 _pulseFillFrom = from.ToSKColor();
546 _pulseFillTo = to.ToSKColor();
547 _pulseFillPeriodSec = MathF.Max(0.0001f, periodSec);
548 _pulseFillWave = triangle ? PulseWave.Triangle : PulseWave.Sine;
549 return this;
550 }
551
582 public DirectRectangle PulseBorder(Color from, Color to, float periodSec, bool enabled = true, bool triangle = false)
583 {
584 _pulseBorderEnabled = enabled;
585 _pulseBorderFrom = from.ToSKColor();
586 _pulseBorderTo = to.ToSKColor();
587 _pulseBorderPeriodSec = MathF.Max(0.0001f, periodSec);
588 _pulseBorderWave = triangle ? PulseWave.Triangle : PulseWave.Sine;
589 return this;
590 }
591
607 public DirectRectangle StopPulses()
608 {
609 _pulseFillEnabled = _pulseBorderEnabled = false;
610 RebuildPaints(); // restore fill/border to SetColor/SetBorderColor values
611 ForceRefresh();
612 return this;
613 }
614
652 public DirectRectangle SetFillPattern(SKBitmap bitmap,
653 SKShaderTileMode tileX = SKShaderTileMode.Repeat,
654 SKShaderTileMode tileY = SKShaderTileMode.Repeat,
655 float scale = 1f,
656 SKPoint? offsetPx = null,
657 SKFilterQuality filterQuality = SKFilterQuality.None)
658 {
659 ArgumentNullException.ThrowIfNull(bitmap);
660
661 scale = float.IsFinite(scale) && scale > 0f
662 ? scale
663 : throw new ArgumentOutOfRangeException(nameof(scale), "Pattern scale must be finite and greater than zero.");
664
665 var m = SKMatrix.CreateScale(scale, scale);
666 if (offsetPx is { } o)
667 m = m.PostConcat(SKMatrix.CreateTranslation(o.X, o.Y));
668
669 SKShader newShader = SKShader.CreateBitmap(bitmap, tileX, tileY, m);
670
671 _fillPaint.Shader = null;
672 _fillShader?.Dispose();
673 _fillShader = newShader;
674
675 _fillImage = null;
676 _fillBitmap = null;
677
678 _fillPaint.Shader = _fillShader;
679 _fillPaint.FilterQuality = filterQuality; // or Low/Medium/High
680
681 // Ensure we're in filled mode for visibility
682 _isFilled = true;
683 ForceRefresh();
684 return this;
685 }
686
703 public DirectRectangle SetFillImage(
704 SKBitmap bitmap,
705 ImageFillMode mode = ImageFillMode.Stretch,
706 float scale = 1f,
707 SKPoint? offsetPx = null,
708 SKFilterQuality filterQuality = SKFilterQuality.Medium)
709 {
710 ArgumentNullException.ThrowIfNull(bitmap);
711
712 SetFillImageCore(
713 image: null,
714 bitmap: bitmap,
715 mode: mode,
716 scale: scale,
717 offsetPx: offsetPx,
718 filterQuality: filterQuality);
719
720 return this;
721 }
722
739 public DirectRectangle SetFillImage(
740 SKImage image,
741 ImageFillMode mode = ImageFillMode.Stretch,
742 float scale = 1f,
743 SKPoint? offsetPx = null,
744 SKFilterQuality filterQuality = SKFilterQuality.Medium)
745 {
746 ArgumentNullException.ThrowIfNull(image);
747
748 SetFillImageCore(
749 image: image,
750 bitmap: null,
751 mode: mode,
752 scale: scale,
753 offsetPx: offsetPx,
754 filterQuality: filterQuality);
755
756 return this;
757 }
758
763 public DirectRectangle ClearFillImage()
764 {
765 _fillImage = null;
766 _fillBitmap = null;
767 ForceRefresh();
768 return this;
769 }
770
784 public DirectRectangle ClearFillPattern()
785 {
786 _fillPaint.Shader = null;
787 _fillShader?.Dispose();
788 _fillShader = null;
789 ForceRefresh();
790 return this;
791 }
792
820 public override void Update(long tick)
821 {
822 if (tick <= _lastTick)
823 return;
824
825 // no previous tick assume first frame, so skip dt-based updates
826 if (_pulseLastTick > 0)
827 {
828 float dt = HighResTimer.GetDuration(_pulseLastTick, tick);
829 _timeSec += dt;
830 }
831
832 _pulseLastTick = tick;
833
834 bool changed = false;
835
836 if (_pulseFillEnabled)
837 {
838 float t = PulseT(_timeSec, _pulseFillPeriodSec, _pulseFillWave);
839 var c = LerpColor(_pulseFillFrom, _pulseFillTo, t);
840 if (_fillPaint.Color != c) { _fillPaint.Color = c; changed = true; }
841 }
842
843 if (_pulseBorderEnabled)
844 {
845 float t = PulseT(_timeSec, _pulseBorderPeriodSec, _pulseBorderWave);
846 var c = LerpColor(_pulseBorderFrom, _pulseBorderTo, t);
847
848 if (_strokePaint.Color != c)
849 {
850 _strokePaint.Color = c; // paint-only override
851 ForceRefresh();
852 }
853 }
854
855 if (changed)
856 {
857 _needsRebuildPaints = false; // we already set paints directly
858 ForceRefresh(); // request redraw
859 }
860
861 base.Update(tick);
862 }
863
891 protected override void OnDraw(BackbufferBase backbuffer, RectangleF destRectScreen)
892 {
893 if (backbuffer is RecordingBackbuffer && _fillImage?.IsTextureBacked == true)
894 throw new NotSupportedException("Desktop frame recording requires a CPU image, not a context-bound texture.");
895
896 var canvas = backbuffer.Canvas;
897
898 if (_needsRebuildPaints)
899 RebuildPaints();
900
901 var fillRect = destRectScreen.ToSKRect();
902 var strokeRect = fillRect;
903
904 bool willDrawStroke = !_isFilled || _borderColor.HasValue || _strokePaint.StrokeWidth > 0.01f;
905 float half = _strokePaint.StrokeWidth * 0.5f;
906
907 // 1) APPLY STROKE ALIGNMENT (use HALF the width; path is centered)
908 if (willDrawStroke && _strokeAlign != StrokeAlign.Center)
909 {
910 if (_strokeAlign == StrokeAlign.Inside)
911 strokeRect.Inflate(-half, -half);
912 else if (_strokeAlign == StrokeAlign.Outside)
913 strokeRect.Inflate(half, half);
914 }
915
916 // 1.5) corner radius for the stroke path, adjusted to keep the inner/outer arcs aligned to the fill
917 float strokeCornerRadius;
918 switch (_strokeAlign)
919 {
920 case StrokeAlign.Outside:
921 strokeCornerRadius = MathF.Max(0f, _cornerRadius - half);
922 break;
923 case StrokeAlign.Inside:
924 strokeCornerRadius = _cornerRadius + half;
925 break;
926 case StrokeAlign.Center:
927 default:
928 strokeCornerRadius = _cornerRadius;
929 break;
930 }
931
932 // 2) Dash for stroke only
933 _strokePaint.PathEffect = _dashPattern is { Length: > 0 }
934 ? SKPathEffect.CreateDash(_dashPattern, 0)
935 : null;
936
937 // 3) Draw fill (unmodified rect)
938 if (_isFilled)
939 {
940 if (_fillImage is not null || _fillBitmap is not null)
941 {
942 DrawImageFill(canvas, fillRect);
943 }
944 else if (_cornerRadius > 0)
945 {
946 canvas.DrawRoundRect(fillRect, strokeCornerRadius, strokeCornerRadius, _fillPaint);
947 }
948 else
949 {
950 canvas.DrawRect(fillRect, _fillPaint);
951 }
952 }
953
954 // 4) Draw stroke on its aligned rect (pure/opaque so it stays white)
955 if (willDrawStroke)
956 {
957 var prevBlend = _strokePaint.BlendMode;
958 var prevColor = _strokePaint.Color;
959 var prevAA = _strokePaint.IsAntialias;
960
961 _strokePaint.IsAntialias = false;
962 _strokePaint.BlendMode = SKBlendMode.Src;
963
964 // If pulsing, keep color from Update(),
965 // else use base border color (or previous).
966 var strokeColor = _pulseBorderEnabled ? _strokePaint.Color
967 : (_borderColor ?? prevColor);
968
969 _strokePaint.Color = strokeColor.WithAlpha(255);
970
971 if (_cornerRadius > 0)
972 canvas.DrawRoundRect(strokeRect, _cornerRadius, _cornerRadius, _strokePaint);
973 else
974 canvas.DrawRect(strokeRect, _strokePaint);
975
976 _strokePaint.IsAntialias = prevAA;
977 _strokePaint.BlendMode = prevBlend;
978 _strokePaint.Color = prevColor;
979 _strokePaint.StrokeJoin = SKStrokeJoin.Round;
980 _strokePaint.StrokeCap = SKStrokeCap.Round;
981 }
982 }
983
985 protected override void Dispose(bool disposing)
986 {
987 if (disposing && !_resourcesDisposed)
988 {
989 _resourcesDisposed = true;
990 _fillPaint.Shader = null;
991 _fillShader?.Dispose();
992 _fillShader = null;
993 _fillPaint.Dispose();
994 _strokePaint.Dispose();
995 _imagePaint.Dispose();
996 }
997
998 base.Dispose(disposing);
999 }
1000
1001 private void SetFillImageCore(
1002 SKImage? image,
1003 SKBitmap? bitmap,
1004 ImageFillMode mode,
1005 float scale,
1006 SKPoint? offsetPx,
1007 SKFilterQuality filterQuality)
1008 {
1009 if (!float.IsFinite(scale) || scale <= 0f)
1010 throw new ArgumentOutOfRangeException(nameof(scale), "Image scale must be finite and greater than zero.");
1011
1012 if (!Enum.IsDefined(mode))
1013 throw new ArgumentOutOfRangeException(nameof(mode));
1014
1015 _fillPaint.Shader = null;
1016 _fillShader?.Dispose();
1017 _fillShader = null;
1018
1019 _fillImage = image;
1020 _fillBitmap = bitmap;
1021 _imageFillMode = mode;
1022 _imageScale = scale;
1023 _imageOffsetPx = offsetPx ?? SKPoint.Empty;
1024 _imagePaint.FilterQuality = filterQuality;
1025 _isFilled = true;
1026
1027 ForceRefresh();
1028 }
1029
1030 private void DrawImageFill(
1031 SKCanvas canvas,
1032 SKRect fillRect)
1033 {
1034 SKImage? image = _fillImage;
1035 SKBitmap? bitmap = _fillBitmap;
1036
1037 int sourceWidth = image?.Width ?? bitmap?.Width ?? 0;
1038 int sourceHeight = image?.Height ?? bitmap?.Height ?? 0;
1039
1040 if (sourceWidth <= 0 || sourceHeight <= 0)
1041 return;
1042
1043 canvas.Save();
1044
1045 try
1046 {
1047 using var clipPath = new SKPath();
1048
1049 if (_cornerRadius > 0f)
1050 clipPath.AddRoundRect(fillRect, _cornerRadius, _cornerRadius);
1051 else
1052 clipPath.AddRect(fillRect);
1053
1054 canvas.ClipPath(
1055 clipPath,
1056 SKClipOperation.Intersect,
1057 antialias: _cornerRadius > 0f);
1058
1059 var source = new SKRect(
1060 0f,
1061 0f,
1062 sourceWidth,
1063 sourceHeight);
1064
1065 if (_imageFillMode == ImageFillMode.Repeat)
1066 {
1067 if (image is not null)
1068 {
1069 DrawRepeatedImage(
1070 canvas,
1071 image,
1072 source,
1073 fillRect);
1074 }
1075 else
1076 {
1077 DrawRepeatedBitmap(
1078 canvas,
1079 bitmap!,
1080 source,
1081 fillRect);
1082 }
1083 }
1084 else
1085 {
1086 SKRect destination = ComputeImageDestination(
1087 fillRect,
1088 source,
1089 _imageFillMode);
1090
1091 if (image is not null)
1092 {
1093 canvas.DrawImage(
1094 image,
1095 source,
1096 destination,
1097 _imagePaint);
1098 }
1099 else
1100 {
1101 canvas.DrawBitmap(
1102 bitmap!,
1103 source,
1104 destination,
1105 _imagePaint);
1106 }
1107 }
1108 }
1109 finally
1110 {
1111 canvas.Restore();
1112 }
1113 }
1114
1115 private void DrawRepeatedImage(
1116 SKCanvas canvas,
1117 SKImage image,
1118 SKRect source,
1119 SKRect fillRect)
1120 {
1121 float tileWidth = image.Width * _imageScale;
1122 float tileHeight = image.Height * _imageScale;
1123
1124 float offsetX = PositiveModulo(_imageOffsetPx.X, tileWidth);
1125 float offsetY = PositiveModulo(_imageOffsetPx.Y, tileHeight);
1126
1127 float startX = fillRect.Left + offsetX;
1128 float startY = fillRect.Top + offsetY;
1129
1130 if (startX > fillRect.Left)
1131 startX -= tileWidth;
1132
1133 if (startY > fillRect.Top)
1134 startY -= tileHeight;
1135
1136 for (float y = startY; y < fillRect.Bottom; y += tileHeight)
1137 {
1138 for (float x = startX; x < fillRect.Right; x += tileWidth)
1139 {
1140 canvas.DrawImage(
1141 image,
1142 source,
1143 new SKRect(
1144 x,
1145 y,
1146 x + tileWidth,
1147 y + tileHeight),
1148 _imagePaint);
1149 }
1150 }
1151 }
1152
1153 private void DrawRepeatedBitmap(
1154 SKCanvas canvas,
1155 SKBitmap bitmap,
1156 SKRect source,
1157 SKRect fillRect)
1158 {
1159 float tileWidth = bitmap.Width * _imageScale;
1160 float tileHeight = bitmap.Height * _imageScale;
1161
1162 float offsetX = PositiveModulo(_imageOffsetPx.X, tileWidth);
1163 float offsetY = PositiveModulo(_imageOffsetPx.Y, tileHeight);
1164
1165 float startX = fillRect.Left + offsetX;
1166 float startY = fillRect.Top + offsetY;
1167
1168 if (startX > fillRect.Left)
1169 startX -= tileWidth;
1170
1171 if (startY > fillRect.Top)
1172 startY -= tileHeight;
1173
1174 for (float y = startY; y < fillRect.Bottom; y += tileHeight)
1175 {
1176 for (float x = startX; x < fillRect.Right; x += tileWidth)
1177 {
1178 canvas.DrawBitmap(
1179 bitmap,
1180 source,
1181 new SKRect(
1182 x,
1183 y,
1184 x + tileWidth,
1185 y + tileHeight),
1186 _imagePaint);
1187 }
1188 }
1189 }
1190
1191 private static SKRect ComputeImageDestination(
1192 SKRect bounds,
1193 SKRect source,
1194 ImageFillMode mode)
1195 {
1196 if (mode == ImageFillMode.Stretch)
1197 return bounds;
1198
1199 if (mode == ImageFillMode.Center)
1200 {
1201 float x = bounds.MidX - source.Width * 0.5f;
1202 float y = bounds.MidY - source.Height * 0.5f;
1203
1204 return new SKRect(
1205 x,
1206 y,
1207 x + source.Width,
1208 y + source.Height);
1209 }
1210
1211 float scaleX = bounds.Width / source.Width;
1212 float scaleY = bounds.Height / source.Height;
1213
1214 float scale = mode switch
1215 {
1216 ImageFillMode.Fit => MathF.Min(scaleX, scaleY),
1217 ImageFillMode.Fill => MathF.Max(scaleX, scaleY),
1218 ImageFillMode.PixelPerfect => MathF.Max(
1219 1f,
1220 MathF.Floor(MathF.Min(scaleX, scaleY))),
1221 _ => 1f
1222 };
1223
1224 float width = source.Width * scale;
1225 float height = source.Height * scale;
1226 float left = bounds.MidX - width * 0.5f;
1227 float top = bounds.MidY - height * 0.5f;
1228
1229 return new SKRect(
1230 left,
1231 top,
1232 left + width,
1233 top + height);
1234 }
1235
1236 private static float PositiveModulo(float value, float modulus)
1237 {
1238 float remainder = value % modulus;
1239 return remainder < 0f
1240 ? remainder + modulus
1241 : remainder;
1242 }
1243
1247 private void RebuildPaints()
1248 {
1249 // Ensure styles/AA set
1250 _fillPaint.IsAntialias = true;
1251 _fillPaint.Style = SKPaintStyle.Fill;
1252
1253 _strokePaint.IsAntialias = true;
1254 _strokePaint.Style = SKPaintStyle.Stroke;
1255
1256 // Only rebuild stroke color if not pulsing border
1257 if (!_pulseBorderEnabled)
1258 {
1259 // If border color set, use it for stroke; else match fill/base color
1260 if (_borderColor.HasValue)
1261 {
1262 _ = _strokePaint.Color; // preserve alpha if you want; otherwise:
1263 _strokePaint.Color = _borderColor.Value;
1264 }
1265 else
1266 {
1267 _strokePaint.Color = _fillPaint.Color; // same as fill if no explicit border
1268 }
1269
1270 _needsRebuildPaints = false;
1271 }
1272 }
1273
1281 public enum StrokeAlign
1282 {
1288
1294
1300 }
1301
1302 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1303 private static float PulseT(float timeSec, float periodSec, PulseWave wave)
1304 {
1305 float phase = (timeSec / periodSec) % 1f;
1306 if (wave == PulseWave.Sine)
1307 {
1308 // (sin(2πx)+1)/2 in [0..1]
1309 return 0.5f * (1f + MathF.Sin(phase * MathF.PI * 2f));
1310 }
1311 else // Triangle: ramp up 0..1 then down 1..0
1312 {
1313 return phase < 0.5f ? (phase * 2f) : (1f - ((phase - 0.5f) * 2f));
1314 }
1315 }
1316
1317 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1318 private static SKColor LerpColor(SKColor a, SKColor b, float t01)
1319 {
1320 t01 = Math.Clamp(t01, 0f, 1f);
1321
1322 byte r = (byte)(a.Red + (b.Red - a.Red) * t01);
1323 byte g = (byte)(a.Green + (b.Green - a.Green) * t01);
1324 byte bch = (byte)(a.Blue + (b.Blue - a.Blue) * t01);
1325 byte aA = (byte)(a.Alpha + (b.Alpha - a.Alpha) * t01);
1326
1327 return new SKColor(r, g, bch, aA);
1328 }
1329}
View? View
Gets the view to which this direct drawing is attached, or null if the drawing is in scene-layer mode...
long _lastTick
The last engine tick value used for delta-time calculations in Update.
SceneLayer? SceneLayer
Gets the scene layer to which this direct drawing is attached, or null if the drawing is in view mode...
DirectDrawingMovableBase(RenderSurfaceHostBase renderSurfaceHost, DirectDrawingMode mode, SceneLayer? sceneLayer, View? view, Rectangle? screenBounds, Rectangle? worldBounds, string? name=null)
Initializes a new instance of the DirectDrawingMovableBase class with integrated movement capabilitie...
override void OnDraw(BackbufferBase backbuffer, RectangleF destRectScreen)
Renders the rectangle to the backbuffer with all configured properties and effects applied.
DirectRectangle SetFilled(bool isFilled)
Sets whether the rectangle interior is filled with the base color (or pattern).
DirectRectangle StopPulses()
Stops all active color pulsing animations (fill and border), reverting to static colors.
DirectRectangle ClearDashPattern()
Removes any existing dash pattern, reverting to a solid stroke.
DirectRectangle SetBorderColor(Color color)
Sets a distinct border (stroke) color, independent of the fill color.
StrokeAlign
Defines how the stroke (outline) is positioned relative to the rectangle's boundary.
@ Outside
Draws the stroke entirely outside the rectangle's bounds. The inner edge of the stroke aligns with th...
@ Inside
Draws the stroke entirely inside the rectangle's bounds. The outer edge of the stroke aligns with the...
DirectRectangle SetDashPattern(float dashLength, float gapLength)
Sets a simple repeating dash pattern using dash and gap lengths, in pixels.
DirectRectangle SetColor(Color color)
Sets the base color used for both fill and stroke (unless a distinct border color is specified).
DirectRectangle SetStrokeWidth(float width)
Sets the width (thickness) of the rectangle's stroke (outline) in pixels.
DirectRectangle SetFillPattern(SKBitmap bitmap, SKShaderTileMode tileX=SKShaderTileMode.Repeat, SKShaderTileMode tileY=SKShaderTileMode.Repeat, float scale=1f, SKPoint? offsetPx=null, SKFilterQuality filterQuality=SKFilterQuality.None)
Sets the rectangle's fill to use a tiled bitmap pattern instead of a solid color.
DirectRectangle SetAlpha(int alpha)
Sets the alpha (transparency) channel of the base color.
DirectRectangle SetFillImage(SKImage image, ImageFillMode mode=ImageFillMode.Stretch, float scale=1f, SKPoint? offsetPx=null, SKFilterQuality filterQuality=SKFilterQuality.Medium)
Fills the rectangle with an image using the requested scaling or repeat behavior.
DirectRectangle SetCornerRadius(float radius)
Sets the radius for rounded corners in pixels.
DirectRectangle(Color color, RenderSurfaceHostBase renderSurfaceHost, SceneLayer sceneLayer, Rectangle worldBounds, string? nickname=null)
Initializes a new instance of the DirectRectangle class for scene-layer (world-space) rendering.
override void Update(long tick)
Performs per-frame update logic, including pulse animation advancement and physics integration.
DirectRectangle PulseFill(Color from, Color to, float periodSec, bool enabled=true, bool triangle=false)
Initiates an animated color transition (pulse) for the rectangle's fill between two colors.
DirectRectangle SetBlendMode(SKBlendMode mode)
Sets the blend mode used when rendering this rectangle onto the canvas.
ImageFillMode
Defines how an image fills the rectangle interior.
@ Fit
Fits the entire image inside the rectangle while preserving aspect ratio.
@ PixelPerfect
Uses the largest whole-number scale that fits, never scaling below native size.
@ Center
Draws the image at native size, centered and clipped to the rectangle.
@ Repeat
Repeats the image as tiles across the rectangle.
@ Fill
Covers the rectangle while preserving aspect ratio and clips any overflow.
@ Stretch
Stretches the image to the full rectangle without preserving aspect ratio.
override void Dispose(bool disposing)
Releases resources used by this DirectDrawingBase instance.When disposing is true,...
DirectRectangle SetFillImage(SKBitmap bitmap, ImageFillMode mode=ImageFillMode.Stretch, float scale=1f, SKPoint? offsetPx=null, SKFilterQuality filterQuality=SKFilterQuality.Medium)
Fills the rectangle with a bitmap using the requested scaling or repeat behavior.
DirectRectangle(Color color, RenderSurfaceHostBase renderSurfaceHost, View view, Rectangle screenBounds, string? nickname=null)
Initializes a new instance of the DirectRectangle class for view (screen-space) rendering.
DirectRectangle PulseBorder(Color from, Color to, float periodSec, bool enabled=true, bool triangle=false)
Initiates an animated color transition (pulse) for the rectangle's border between two colors.
DirectRectangle ClearFillPattern()
Removes the current pattern fill and reverts to solid color rendering.
DirectRectangle SetStrokeAlign(StrokeAlign align)
Sets how the rectangle's stroke is positioned relative to its bounds.
DirectRectangle ClearFillImage()
Removes the current bitmap or image fill and returns to solid-color filling.
Represents a base class for managing a graphical backbuffer, and is the in-memory surface where rende...
SKCanvas Canvas
Gets the SkiaSharp canvas used for drawing operations on this backbuffer.
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
Represents a rendered view of a scene, combining a camera position with a viewport configuration to c...
Definition View.cs:14
Represents a single layer within a Scene, containing a grid of tiles and supporting various coordinat...
Definition SceneLayer.cs:40
DirectDrawingMode
Specifies where direct drawing operations are applied within the rendering pipeline.