Skip to content

Commit e638ebb

Browse files
committed
First attempt at a sprite editor
1 parent 3992e62 commit e638ebb

File tree

8 files changed

+268
-67
lines changed

8 files changed

+268
-67
lines changed

games/Orbit.Studio/Orbit.Studio/AppShell.xaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
x:Class="Orbit.Studio.AppShell"
44
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
55
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6-
xmlns:local="clr-namespace:Orbit.Studio"
7-
Shell.FlyoutBehavior="Disabled"
6+
xmlns:sprites="clr-namespace:Orbit.Studio.Sprites"
7+
FlyoutBehavior="Disabled"
88
Title="Orbit.Studio">
99

1010
<ShellContent
11-
Title="Home"
12-
ContentTemplate="{DataTemplate local:MainPage}"
11+
ContentTemplate="{DataTemplate sprites:SpriteEditorPage}"
1312
Route="MainPage" />
1413

1514
</Shell>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<Grid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5+
x:Class="Orbit.Studio.Controls.ColorPicker"
6+
RowDefinitions="*,*,*,*,*"
7+
ColumnDefinitions="*,Auto">
8+
9+
<Slider Minimum="0" Maximum="255" Value="255" x:Name="Red" ValueChanged="OnColorSliderValueChanged" />
10+
<Entry Text="{Binding Value, Source={x:Reference Red}}" Grid.Column="1" />
11+
12+
<Slider Minimum="0" Maximum="255" Value="0" x:Name="Blue" ValueChanged="OnColorSliderValueChanged" Grid.Row="1" />
13+
<Entry Text="{Binding Value, Source={x:Reference Blue}}" Grid.Column="1" Grid.Row="1" />
14+
15+
<Slider Minimum="0" Maximum="255" Value="0" x:Name="Green" ValueChanged="OnColorSliderValueChanged" Grid.Row="2" />
16+
<Entry Text="{Binding Value, Source={x:Reference Green}}" Grid.Column="1" Grid.Row="2" />
17+
18+
<Slider Minimum="0" Maximum="255" Value="255" x:Name="Alpha" ValueChanged="OnColorSliderValueChanged" Grid.Row="3" />
19+
<Entry Text="{Binding Value, Source={x:Reference Alpha}}" Grid.Column="1" Grid.Row="3" />
20+
21+
<BoxView x:Name="ColorPreview" WidthRequest="50" HeightRequest="50" Grid.Row="4" />
22+
23+
</Grid>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
namespace Orbit.Studio.Controls;
2+
3+
public partial class ColorPicker : Grid
4+
{
5+
public ColorPicker()
6+
{
7+
InitializeComponent();
8+
}
9+
10+
private void OnColorSliderValueChanged(object? sender, ValueChangedEventArgs e)
11+
{
12+
SelectedColor = Color.FromRgba(
13+
Red.Value / 255d,
14+
Blue.Value / 255d,
15+
Green.Value / 255d,
16+
Alpha.Value / 255d);
17+
}
18+
19+
public static readonly BindableProperty SelectedColorProperty =
20+
BindableProperty.Create(
21+
nameof(SelectedColor),
22+
typeof(Color),
23+
typeof(ColorPicker),
24+
Colors.Black,
25+
propertyChanged: OnSelectedColorPropertyChanged);
26+
27+
private static void OnSelectedColorPropertyChanged(BindableObject sender, object oldValue, object newValue)
28+
{
29+
((ColorPicker)sender).UpdatePreviewColor();
30+
}
31+
32+
private void UpdatePreviewColor()
33+
{
34+
ColorPreview.BackgroundColor = SelectedColor;
35+
}
36+
37+
public Color SelectedColor
38+
{
39+
get => (Color)GetValue(SelectedColorProperty);
40+
set => SetValue(SelectedColorProperty, value);
41+
}
42+
}

games/Orbit.Studio/Orbit.Studio/MainPage.xaml

Lines changed: 0 additions & 36 deletions
This file was deleted.

games/Orbit.Studio/Orbit.Studio/MainPage.xaml.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.

games/Orbit.Studio/Orbit.Studio/Orbit.Studio.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
<MauiImage Include="Resources\Images\*"/>
5050
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185"/>
5151

52+
<EmbeddedResource Include="Resources\EmbeddedResources\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
53+
5254
<!-- Custom Fonts -->
5355
<MauiFont Include="Resources\Fonts\*"/>
5456

@@ -60,10 +62,7 @@
6062
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)"/>
6163
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)"/>
6264
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0"/>
63-
</ItemGroup>
64-
65-
<ItemGroup>
66-
<ProjectReference Include="..\..\..\engine\Orbit.Engine\Orbit.Engine.csproj" />
65+
<PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="$(MauiVersion)" />
6766
</ItemGroup>
6867

6968
</Project>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5+
xmlns:controls="clr-namespace:Orbit.Studio.Controls"
6+
x:Class="Orbit.Studio.Sprites.SpriteEditorPage"
7+
Title="Sprite Editor">
8+
9+
<Grid ColumnDefinitions="2*,*">
10+
<VerticalStackLayout Grid.Column="1" Spacing="10">
11+
<HorizontalStackLayout>
12+
<Label Text="Width" VerticalOptions="Center"/>
13+
<Entry Keyboard="Numeric" Text="16" TextChanged="WidthEntry_OnTextChanged" />
14+
</HorizontalStackLayout>
15+
<HorizontalStackLayout>
16+
<Label Text="Height" VerticalOptions="Center"/>
17+
<Entry Keyboard="Numeric" Text="16" TextChanged="HeightEntry_OnTextChanged" />
18+
</HorizontalStackLayout>
19+
20+
<Label Text="Zoom" />
21+
<Slider
22+
x:Name="Zoom"
23+
Minimum="1"
24+
Maximum="50"
25+
Value="1"
26+
ValueChanged="Zoom_OnValueChanged"/>
27+
28+
<HorizontalStackLayout>
29+
<Label Text="Show grid lines" VerticalOptions="Center"/>
30+
<CheckBox x:Name="ShowGridLines" CheckedChanged="ShowGridLines_OnCheckedChanged" />
31+
</HorizontalStackLayout>
32+
33+
<HorizontalStackLayout>
34+
<Label Text="Show chessboard" VerticalOptions="Center"/>
35+
<CheckBox x:Name="ShowChessboard" CheckedChanged="ShowChessboard_OnCheckedChanged" />
36+
</HorizontalStackLayout>
37+
38+
<Button Text="Undo" Clicked="OnUndoClicked" />
39+
40+
<controls:ColorPicker x:Name="ColorPicker" />
41+
42+
<Button Clicked="Button_OnClicked" Text="Export" />
43+
</VerticalStackLayout>
44+
45+
<GraphicsView
46+
x:Name="Canvas"
47+
Drawable="{Binding}"
48+
MoveHoverInteraction="Canvas_OnMoveHoverInteraction"
49+
EndInteraction="GraphicsView_OnEndInteraction"/>
50+
</Grid>
51+
52+
</ContentPage>
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using Microsoft.Maui.Graphics.Skia;
2+
3+
using SkiaSharp;
4+
5+
namespace Orbit.Studio.Sprites;
6+
7+
public partial class SpriteEditorPage : ContentPage, IDrawable
8+
{
9+
public SpriteEditorPage()
10+
{
11+
InitializeComponent();
12+
BindingContext = this;
13+
}
14+
15+
private int width = 16;
16+
private int height = 16;
17+
private IList<Pixel> pixels = [];
18+
private float mouseX;
19+
private float mouseY;
20+
21+
private void GraphicsView_OnEndInteraction(object? sender, TouchEventArgs e)
22+
{
23+
pixels.Add(new Pixel { Color = ColorPicker.SelectedColor, Location = new PointF(mouseX, mouseY) });
24+
}
25+
26+
public void Draw(ICanvas canvas, RectF dirtyRect)
27+
{
28+
Render(canvas, dirtyRect, (float)Zoom.Value, ShowGridLines.IsChecked, ShowChessboard.IsChecked);
29+
}
30+
31+
private void Render(ICanvas canvas, RectF bounds, float zoomFactor, bool showGridLines, bool showChessboard)
32+
{
33+
var renderedWidth = width * zoomFactor;
34+
var renderedHeight = height * zoomFactor;
35+
36+
if (showChessboard)
37+
{
38+
for (int x = 0; x < width; x++)
39+
{
40+
for (int y = 0; y < height; y++)
41+
{
42+
var number = y % 2 == 0 ? 1 : 0;
43+
canvas.FillColor = x % 2 == number ? Colors.White : Color.FromRgb(192 / 255d, 192 / 255d, 192 / 255d);
44+
canvas.FillRectangle(x * zoomFactor, y * zoomFactor, 1 * zoomFactor, 1 * zoomFactor);
45+
}
46+
}
47+
}
48+
49+
if (showGridLines)
50+
{
51+
var lineColor = Color.FromRgb(192 / 255d, 192 / 255d, 192 / 255d);
52+
53+
for (int x = 1; x < width; x++)
54+
{
55+
canvas.StrokeColor = lineColor;
56+
canvas.DrawLine(x * zoomFactor, 0, x * zoomFactor, height * zoomFactor);
57+
}
58+
59+
for (int y = 1; y < height; y++)
60+
{
61+
canvas.StrokeColor = lineColor;
62+
canvas.DrawLine(0, y * zoomFactor, width * zoomFactor, y * zoomFactor);
63+
}
64+
}
65+
66+
foreach (var tile in pixels)
67+
{
68+
canvas.FillColor = tile.Color;
69+
canvas.FillRectangle(tile.Location.X * zoomFactor, tile.Location.Y * zoomFactor, zoomFactor, zoomFactor);
70+
}
71+
72+
canvas.FillColor = ColorPicker.SelectedColor;
73+
canvas.FillRectangle(mouseX * zoomFactor, mouseY * zoomFactor, zoomFactor, zoomFactor);
74+
}
75+
76+
private void Zoom_OnValueChanged(object? sender, ValueChangedEventArgs e)
77+
{
78+
Canvas.Invalidate();
79+
}
80+
81+
private void Canvas_OnMoveHoverInteraction(object? sender, TouchEventArgs e)
82+
{
83+
var touch = e.Touches.First();
84+
85+
var zoomFactor = (float)Zoom.Value;
86+
87+
mouseX = MathF.Floor(touch.X / zoomFactor);
88+
mouseY = MathF.Floor(touch.Y / zoomFactor);
89+
90+
Canvas.Invalidate();
91+
}
92+
93+
private void Export()
94+
{
95+
using var canvas = new SkiaCanvas();
96+
using var bitmap = new SKBitmap(new SKImageInfo(width, height));
97+
canvas.Canvas = new SKCanvas(bitmap);
98+
99+
Render(canvas, new RectF(0, 0, width, height), 1f, false, false);
100+
101+
var path = Path.Combine(FileSystem.AppDataDirectory, @"sprite.png");
102+
using var stream = File.Create(path);
103+
bitmap.Encode(stream, SKEncodedImageFormat.Png, 100);
104+
}
105+
106+
private void Button_OnClicked(object? sender, EventArgs e)
107+
{
108+
Export();
109+
}
110+
111+
private void ShowGridLines_OnCheckedChanged(object? sender, CheckedChangedEventArgs e)
112+
{
113+
Canvas.Invalidate();
114+
}
115+
116+
private void ShowChessboard_OnCheckedChanged(object? sender, CheckedChangedEventArgs e)
117+
{
118+
Canvas.Invalidate();
119+
}
120+
121+
private void OnUndoClicked(object? sender, EventArgs e)
122+
{
123+
pixels.RemoveAt(pixels.Count - 1);
124+
Canvas.Invalidate();
125+
}
126+
127+
private void WidthEntry_OnTextChanged(object? sender, TextChangedEventArgs e)
128+
{
129+
int.TryParse(e.NewTextValue, out width);
130+
Canvas.Invalidate();
131+
}
132+
133+
private void HeightEntry_OnTextChanged(object? sender, TextChangedEventArgs e)
134+
{
135+
int.TryParse(e.NewTextValue, out height);
136+
Canvas.Invalidate();
137+
}
138+
}
139+
140+
public class Pixel
141+
{
142+
public Color Color { get; init; }
143+
144+
public PointF Location { get; init; }
145+
}

0 commit comments

Comments
 (0)