11using System ;
22using System . Collections . Generic ;
3+ using Avalonia ;
4+ using Avalonia . Media . Imaging ;
5+ using Avalonia . Platform ;
36using PaintPower . Tools ;
47using PaintPower . Tools . Graphics ;
8+ using Point = PaintPower . Tools . Point ;
59
610namespace PaintPower . Display . DisplayIntegration ;
711
812// DIPlay or Diplay. You know, like Dip-lay, like "Display" with out the 's'. (Inspired by a typo while learning CSS)
913public class DIPlay
1014{
15+ // The off-screen rendering surface (your custom pixel renderer)
1116 public GfxPane gfxPane ;
12- public static Point stageSize = new ( PaintPower_Engine . App . _project ? . Metadata ? . StageWidth , PaintPower_Engine . App . _project ? . Metadata ? . StageHeight ) ;
1317
18+ // Stage size based on project metadata
19+ public static Point stageSize = new (
20+ PaintPower_Engine . App . _project ? . Metadata ? . StageWidth ,
21+ PaintPower_Engine . App . _project ? . Metadata ? . StageHeight
22+ ) ;
23+
24+ // All renderable items (sprites, UI elements, etc.)
1425 public List < DIItem > items = new ( ) ;
1526
16- public DIPlay ( )
17- {
18- setStageSize ( ) ;
19- gfxPane = new ( stageSize . x , stageSize . y ) ;
20- }
27+ // Reference to the Stage UserControl so we can push rendered frames into it
28+ private readonly VMPanel . Stage _stage ;
29+
30+ // Animation timer (must persist across frames)
31+ private int _t = 0 ;
2132
22- public void setStageSize ( )
33+ public DIPlay ( int width , int height , VMPanel . Stage stage )
2334 {
24- stageSize = new ( PaintPower_Engine . App . _project ? . Metadata ? . StageWidth , PaintPower_Engine . App . _project ? . Metadata ? . StageHeight ) ;
35+ _stage = stage ;
36+
37+ // Create the pixel buffer with the given size
38+ gfxPane = new GfxPane ( width , height ) ;
2539 }
2640
41+ // Starts the 60 FPS render loop
2742 public void Start ( )
2843 {
2944 var timer = new System . Timers . Timer ( 1000.0 / 60.0 ) ;
3045 timer . Elapsed += async ( _ , __ ) => Tick ( ) ;
3146 timer . Start ( ) ;
3247 }
3348
49+ // Main render loop — runs every frame
3450 public async void Tick ( )
3551 {
3652 var pane = gfxPane ;
37- int t = 0 ;
3853
39- await PaintPower_Engine . App . vm . Tick ( ) ;
54+ if ( PaintPower_Engine . App . vm != null )
55+ {
56+
57+ // Update VM logic before rendering
58+ await PaintPower_Engine . App . vm . Tick ( ) ;
4059
60+ }
61+
62+ // Build a list of draw commands for batching
4163 List < DrawCommand > batch = new ( ) ;
4264
4365 foreach ( DIItem item in items )
4466 {
67+ // Skip invisible items
4568 if ( ! item . IsVisible )
4669 continue ;
4770
71+ // Ask the item what graphic it wants to draw
4872 var g = item . DrawAs ( ) ;
4973
74+ // Handle animated graphics
5075 if ( g is GraphicAnimation anim )
5176 {
52- int frame = ResolveAnimationFrame ( anim , t * 16 ) ;
77+ // Pick the correct animation frame based on time
78+ int frame = ResolveAnimationFrame ( anim , _t * 16 ) ;
5379 Graphic frameGraphic = anim . Frames [ frame ] ;
5480
81+ // Skip if off-screen
5582 if ( IsCulled ( frameGraphic , ( float ) item . x , ( float ) item . y , item . ScaleX , item . ScaleY ) )
5683 continue ;
5784
85+ // Add to batch
5886 batch . Add ( new DrawCommand (
5987 frameGraphic ,
6088 ( float ) item . x ,
@@ -64,8 +92,8 @@ public async void Tick()
6492 item . ScaleY ,
6593 item . Z
6694 ) ) ;
67-
6895 }
96+ // Handle static images
6997 else if ( g is Graphic img )
7098 {
7199 if ( IsCulled ( img , ( float ) item . x , ( float ) item . y , item . ScaleX , item . ScaleY ) )
@@ -83,17 +111,27 @@ public async void Tick()
83111 }
84112 }
85113
86- // Sort for Z-Layering
114+ // Sort by Z so items draw in the correct order
87115 batch . Sort ( ( a , b ) => a . Z . CompareTo ( b . Z ) ) ;
88116
117+ // Clear the frame buffer
89118 pane . Renderer . Clear ( 0xFF202020 ) ;
119+
120+ // Draw all items in one batch
90121 pane . Renderer . DrawBatch ( batch ) ;
91122
92- Avalonia . Threading . Dispatcher . UIThread . Post ( pane . Present ) ;
123+ // Push the rendered bitmap into the Stage UI
124+ Avalonia . Threading . Dispatcher . UIThread . Post ( ( ) =>
125+ {
126+ var bmp = CreateBitmapFromPane ( ) ;
127+ _stage . SetBitmap ( bmp ) ;
128+ } ) ;
93129
94- t ++ ;
130+ // Advance animation timer
131+ _t ++ ;
95132 }
96133
134+ // Determines whether a sprite is off-screen and can be skipped
97135 private bool IsCulled ( Graphic g , float x , float y , float scaleX , float scaleY )
98136 {
99137 float halfW = g . Width * scaleX / 2f ;
@@ -110,6 +148,7 @@ private bool IsCulled(Graphic g, float x, float y, float scaleX, float scaleY)
110148 top > stageSize . y ;
111149 }
112150
151+ // Chooses the correct animation frame based on time
113152 private int ResolveAnimationFrame ( GraphicAnimation anim , int timeMs )
114153 {
115154 int total = 0 ;
@@ -123,4 +162,17 @@ private int ResolveAnimationFrame(GraphicAnimation anim, int timeMs)
123162
124163 return 0 ;
125164 }
126- }
165+
166+ // Converts the raw BGRA pixel buffer into an Avalonia Bitmap
167+ private Bitmap CreateBitmapFromPane ( )
168+ {
169+ return new Bitmap (
170+ PixelFormat . Bgra8888 ,
171+ AlphaFormat . Premul ,
172+ gfxPane . BufferPtr , // pointer to raw BGRA data
173+ new PixelSize ( ( int ) gfxPane . Width , ( int ) gfxPane . Height ) ,
174+ new Vector ( 96 , 96 ) , // DPI
175+ gfxPane . Stride
176+ ) ;
177+ }
178+ }
0 commit comments