Skip to content

Commit 0a3184e

Browse files
committed
Big Update: Graphics (Part 2)
1 parent 8e44ce0 commit 0a3184e

24 files changed

Lines changed: 2093 additions & 909 deletions

Junk/Files.txt

Lines changed: 276 additions & 547 deletions
Large diffs are not rendered by default.

Junk/JunkTextFile.txt

Lines changed: 1176 additions & 307 deletions
Large diffs are not rendered by default.

Junk/Sprite plan.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-

1+
Sprite Plan.txt
22

33
Ok so let's build the definition of a sprite before we acually build a sprite.
44

@@ -13,7 +13,7 @@ should be a items folder. When opening a sprite in the editor this is what the e
1313
the root of the items folder in the sprite folder, no longer the "items" folder in the root of
1414
the project.
1515

16-
So when a sprite is exported, it's just a zip file containing everthing (*.pSprite).
16+
So when a sprite is exported, it's just a zip file containing everything (*.pSprite).
1717

1818
Just to be clear, what I mean by opening a sprite is selecting a sprite in the sprite manager area.
1919
When you do that, the explorer and editor should then show, and the workspace directory will change

src/Display/DisplayIntergration/DIPlay.cs

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,88 @@
11
using System;
22
using System.Collections.Generic;
3+
using Avalonia;
4+
using Avalonia.Media.Imaging;
5+
using Avalonia.Platform;
36
using PaintPower.Tools;
47
using PaintPower.Tools.Graphics;
8+
using Point = PaintPower.Tools.Point;
59

610
namespace 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)
913
public 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+
}

src/EditorPart.axaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@
6868
<GridSplitter Grid.Row="1" Height="5" Background="#505050" ResizeDirection="Rows" ResizeBehavior="PreviousAndNext" />
6969
<!-- SPRITE PROPERTIES -->
7070
<Border Grid.Row="2" Background="#252525">
71-
<TextBlock Text="Sprite Properties" Foreground="White" Margin="8" />
72-
<!-- TODO: Replace with your SpriteProperties control -->
71+
<Border Grid.Row="2" Background="#252525">
72+
<sprite:SpritePropertiesView x:Name="SpriteProperties" />
73+
</Border>
7374
</Border>
7475
</Grid>
7576
<!-- SPLITTER between LEFT and CENTER -->

src/EditorPart.axaml.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public partial class EditorPart : EditorBase
2626

2727
MainWindow window;
2828

29-
#pragma warning enable
29+
#pragma warning enable
3030

3131
public EditorPart() : base()
3232
{
@@ -36,6 +36,12 @@ public EditorPart() : base()
3636

3737
// Display PaintPower version:
3838
VersionInfoTextBlock.Text = PaintPower_Engine.version;
39+
40+
SpriteManager.SpriteSelected += sprite =>
41+
{
42+
SpriteProperties.LoadSprite(sprite);
43+
};
44+
3945
}
4046

4147
public EditorPart attachPaintPower(PaintPower_Engine paintPower_Engine)
@@ -131,7 +137,7 @@ private async void OnLogout(object? sender, RoutedEventArgs e)
131137
private void OnMakeConnection(object? sender, RoutedEventArgs e)
132138
{
133139
SoundEffects.Click.Play();
134-
#pragma warning disable
140+
#pragma warning disable
135141
// Enable connection to the server
136142
App.server.checkConnection();
137143
}

src/PaintPower_Engine.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ public virtual async void RefreshSession(bool makeNew = true)
287287

288288
// Set up translation
289289
setupTranslation();
290+
291+
vm = new();
290292
}
291293

292294
public virtual async void Start()
@@ -313,7 +315,8 @@ public virtual async void Start()
313315

314316
// Set up translation
315317
setupTranslation();
316-
//vm = new Vm.Vm();
318+
319+
vm = new();
317320
}
318321

319322
/*public void OpenEditor(EditorBase editor)

src/ProjectSystem/PaintProject.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ public void LoadSprites()
190190
SpriteFolder = dir
191191
};
192192

193+
// Load sprite skins.
194+
sprite.LoadSkins();
195+
193196
Sprites.Add(sprite);
194197
}
195198
}

src/ProjectSystem/PaintSprite.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
using PaintPower.Accessibility.Translation;
2+
using PaintPower.Display.DisplayIntegration;
23
using PaintPower.Logging;
4+
using PaintPower.Sprites;
35
using System;
6+
using System.Collections.Generic;
47
using System.IO;
58
using System.Linq;
9+
using System.Xml.Linq;
610

711
namespace PaintPower.ProjectSystem;
812

@@ -13,12 +17,87 @@ public class PaintSprite
1317

1418
public string JsonPath => Path.Combine(SpriteFolder, "Sprite.json");
1519
public string AnimationPath => Path.Combine(SpriteFolder, "Sprite.wxa");
20+
21+
// Thumbnail.
1622
public string ThumbnailPath => Path.Combine(SpriteFolder, "Sprite.png");
1723
public string ScriptPath => Path.Combine(SpriteFolder, "Sprite.pss");
24+
25+
// NEW: Skins.xml, I guess we will link skins to files in the items folder or it's subdirectories.
26+
// Please not JSON, i've been waiting for an excuse to use xml!
27+
public string SkinsPath => Path.Combine(SpriteFolder, "Skins.xml");
28+
29+
// Files.
1830
public string ItemsFolder => Path.Combine(SpriteFolder, "items");
1931

2032
public override string ToString() => Name;
2133

34+
// Skins //
35+
public List<SkinDefinition> Skins { get; private set; } = new();
36+
37+
public void LoadSkins()
38+
{
39+
Skins.Clear();
40+
41+
if (!File.Exists(SkinsPath))
42+
return;
43+
44+
var doc = XDocument.Load(SkinsPath);
45+
46+
foreach (var node in doc.Root.Elements("Skin"))
47+
{
48+
Skins.Add(new SkinDefinition
49+
{
50+
Name = (string)node.Attribute("name") ?? "Unnamed",
51+
File = (string)node.Attribute("file") ?? ""
52+
});
53+
}
54+
}
55+
56+
public void SaveSkins()
57+
{
58+
var doc = new XDocument(
59+
new XElement("Skins",
60+
Skins.Select(s =>
61+
new XElement("Skin",
62+
new XAttribute("name", s.Name),
63+
new XAttribute("file", s.File)
64+
)
65+
)
66+
)
67+
);
68+
69+
doc.Save(SkinsPath);
70+
}
71+
72+
// -----------
73+
// The Bridge
74+
// (Oh yeah!)
75+
// -----------
76+
77+
public Sprite ToRuntimeSprite()
78+
{
79+
var runtime = new Sprite();
80+
81+
// Load skins
82+
foreach (var skinDef in Skins)
83+
{
84+
string fullPath = Path.Combine(SpriteFolder, skinDef.File);
85+
runtime.Skins.Add(new Skin(skinDef.Name, fullPath));
86+
}
87+
88+
// Default to first skin
89+
if (runtime.Skins.Count > 0)
90+
runtime.CurrentSkinIndex = 0;
91+
92+
// Default position (center stage)
93+
runtime.x = DIPlay.stageSize.x / 2;
94+
runtime.y = DIPlay.stageSize.y / 2;
95+
96+
return runtime;
97+
}
98+
99+
100+
22101
// ---------------------------------------------------------
23102
// STATIC OPERATIONS (SpriteManagerView passes the sprite)
24103
// ---------------------------------------------------------
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace PaintPower.ProjectSystem;
4+
5+
public class SkinDefinition
6+
{
7+
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
8+
public string Name { get; set; }
9+
public string File { get; set; }
10+
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
11+
12+
public override string ToString()
13+
{
14+
return Name;
15+
}
16+
}

0 commit comments

Comments
 (0)