Skip to content

Commit 355705a

Browse files
Merge pull request #628 from TheTrackerCouncil/add-custom-sprite-functionality
Add button to sprite window to upload sprites
2 parents 6b760fa + c755856 commit 355705a

File tree

15 files changed

+426
-86
lines changed

15 files changed

+426
-86
lines changed

src/TrackerCouncil.Smz3.Data/Options/Sprite.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public Sprite(string name, string author, string filePath, SpriteType spriteType
3939
SpriteType = spriteType;
4040
PreviewPath = previewPath;
4141
SpriteOption = spriteOption;
42+
IsUserSprite = filePath.StartsWith(RandomizerDirectories.UserSpritePath);
43+
4244
if (string.IsNullOrEmpty(filePath))
4345
IsDefault = true;
4446
}
@@ -50,7 +52,7 @@ private Sprite(string name, SpriteType spriteType, bool isDefault, bool isRandom
5052
SpriteType = spriteType;
5153
IsDefault = isDefault;
5254
IsRandomSprite = isRandomSprite;
53-
PreviewPath = Path.Combine(SpritePath, s_folderNames[spriteType], sprite);
55+
PreviewPath = Path.Combine(RandomizerDirectories.SpritePath, s_folderNames[spriteType], sprite);
5456
}
5557

5658
[YamlIgnore]
@@ -59,9 +61,9 @@ private Sprite(string name, SpriteType spriteType, bool isDefault, bool isRandom
5961
[YamlIgnore]
6062
public string Author { get; set; }
6163

62-
public string FilePath { get; set; } = "";
64+
public string FilePath { get; } = "";
6365

64-
public SpriteType SpriteType { get; set; }
66+
public SpriteType SpriteType { get; }
6567

6668
[YamlIgnore]
6769
public string PreviewPath { get; set; }
@@ -73,12 +75,15 @@ private Sprite(string name, SpriteType spriteType, bool isDefault, bool isRandom
7375
[YamlIgnore]
7476
public SpriteOptions SpriteOption { get; set; }
7577

78+
public bool IsUserSprite { get; set; }
79+
7680
public bool MatchesFilter(string searchTerm, SpriteFilter spriteFilter) => (string.IsNullOrEmpty(searchTerm) ||
7781
Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) ||
7882
Author.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) &&
7983
((spriteFilter == SpriteFilter.Default && SpriteOption != SpriteOptions.Hide) ||
8084
(spriteFilter == SpriteFilter.Favorited && SpriteOption == SpriteOptions.Favorite) ||
8185
(spriteFilter == SpriteFilter.Hidden && SpriteOption == SpriteOptions.Hide) ||
86+
(spriteFilter == SpriteFilter.User && IsUserSprite) ||
8287
spriteFilter == SpriteFilter.All);
8388

8489
public static bool operator ==(Sprite? a, Sprite? b)
@@ -119,5 +124,4 @@ public override string ToString()
119124
return string.IsNullOrEmpty(Author) ? Name : $"{Name} by {Author}";
120125
}
121126

122-
public static string SpritePath => RandomizerDirectories.SpritePath;
123127
}

src/TrackerCouncil.Smz3.Data/Options/SpriteFilter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ public enum SpriteFilter
55
Default,
66
Favorited,
77
Hidden,
8+
User,
89
All,
910
}

src/TrackerCouncil.Smz3.Data/RandomizerDirectories.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public static string SpritePath
6767
}
6868
}
6969

70+
public static string UserSpritePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
71+
"SMZ3CasRandomizer", "Sprites");
72+
7073
#if DEBUG
7174
public static string SpriteHashYamlFilePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SMZ3CasRandomizer", "sprite-hashes-debug.yml");
7275
#else
@@ -101,5 +104,5 @@ public static string TrackerSpritePath
101104
public static string TrackerSpriteHashYamlFilePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SMZ3CasRandomizer", "tracker-sprite-hashes.yml");
102105
#endif
103106

104-
public static string TrackerSpriteInitialJsonFilePath => Path.Combine(SpritePath, "tracker-sprites.json");
107+
public static string TrackerSpriteInitialJsonFilePath => Path.Combine(SpritePath, "tracker-sprites.json");
105108
}

src/TrackerCouncil.Smz3.Data/Services/SpriteService.cs

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public SpriteService(ILogger<SpriteService> logger, OptionsFactory optionsFactor
2323
_options = optionsFactory.Create();
2424
}
2525

26-
public IEnumerable<Sprite> Sprites { get; set; } = new List<Sprite>();
26+
public List<Sprite> Sprites { get; set; } = [];
2727
public IEnumerable<Sprite> LinkSprites => Sprites.Where(x => x.SpriteType == SpriteType.Link);
2828
public IEnumerable<Sprite> SamusSprites => Sprites.Where(x => x.SpriteType == SpriteType.Samus);
2929
public IEnumerable<Sprite> ShipSprites => Sprites.Where(x => x.SpriteType == SpriteType.Ship);
@@ -33,23 +33,21 @@ public SpriteService(ILogger<SpriteService> logger, OptionsFactory optionsFactor
3333
/// </summary>
3434
public Task LoadSpritesAsync()
3535
{
36-
if (Sprites.Any() || !Directory.Exists(Sprite.SpritePath)) return Task.CompletedTask;
36+
if (Sprites.Any() || !Directory.Exists(RandomizerDirectories.SpritePath)) return Task.CompletedTask;
3737

3838
return Task.Run(() =>
3939
{
4040
var defaults = new List<Sprite>() { Sprite.DefaultSamus, Sprite.DefaultLink, Sprite.DefaultShip, Sprite.RandomSamus, Sprite.RandomLink, Sprite.RandomShip };
4141

42-
var playerSprites = Directory.EnumerateFiles(Sprite.SpritePath, "*.rdc", SearchOption.AllDirectories)
42+
var playerSprites = Directory.EnumerateFiles(RandomizerDirectories.SpritePath, "*.rdc", SearchOption.AllDirectories)
4343
.Select(LoadRdcSprite);
4444

45-
var shipSprites = Directory.EnumerateFiles(Path.Combine(Sprite.SpritePath, "Ships"), "*.ips", SearchOption.AllDirectories)
45+
var shipSprites = Directory.EnumerateFiles(Path.Combine(RandomizerDirectories.SpritePath, "Ships"), "*.ips", SearchOption.AllDirectories)
4646
.Select(LoadIpsSprite);
4747

4848
var sprites = playerSprites.Concat(shipSprites).Concat(defaults).OrderBy(x => x.Name).ToList();
4949

50-
var extraSpriteDirectory =
51-
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
52-
"SMZ3CasRandomizer", "Sprites");
50+
var extraSpriteDirectory = RandomizerDirectories.UserSpritePath;
5351

5452
if (Directory.Exists(extraSpriteDirectory))
5553
{
@@ -65,6 +63,50 @@ public Task LoadSpritesAsync()
6563
});
6664
}
6765

66+
public Sprite? AddCustomSprite(string spritePath, string? previewImagePath)
67+
{
68+
var userSpritePath = RandomizerDirectories.UserSpritePath;
69+
70+
if (spritePath.StartsWith(RandomizerDirectories.SpritePath) ||
71+
spritePath.StartsWith(userSpritePath) ||
72+
!File.Exists(spritePath))
73+
{
74+
return null;
75+
}
76+
77+
var destinationSpritePath = Path.Combine(userSpritePath, Path.GetFileName(spritePath));
78+
File.Copy(spritePath, destinationSpritePath, true);
79+
if (File.Exists(previewImagePath))
80+
{
81+
var baseFileName = Path.GetFileNameWithoutExtension(spritePath);
82+
File.Copy(previewImagePath, Path.Combine(userSpritePath, $"{baseFileName}.png"), true);
83+
}
84+
85+
var sprite = new Sprite();
86+
try
87+
{
88+
sprite = ".rdc".Equals(Path.GetExtension(destinationSpritePath), StringComparison.OrdinalIgnoreCase)
89+
? LoadRdcSprite(destinationSpritePath)
90+
: LoadIpsSprite(destinationSpritePath);
91+
}
92+
catch (Exception ex)
93+
{
94+
_logger.LogError(ex, "Failed to load Sprite {SpritePath}", destinationSpritePath);
95+
}
96+
97+
if (Sprites.Any(x => x.FilePath == destinationSpritePath))
98+
{
99+
var oldSprite = Sprites.First(x => x.FilePath == destinationSpritePath);
100+
oldSprite.Name = sprite.Name;
101+
oldSprite.Author = sprite.Author;
102+
oldSprite.PreviewPath = sprite.PreviewPath;
103+
return oldSprite;
104+
}
105+
106+
Sprites.Add(sprite);
107+
return sprite;
108+
}
109+
68110
/// <summary>
69111
/// Retrieves the random sprite image for the given sprite type
70112
/// </summary>
@@ -73,7 +115,7 @@ public Task LoadSpritesAsync()
73115
public string GetRandomPreviewImage(SpriteType type)
74116
{
75117
var spriteFolder = type == SpriteType.Ship ? "Ships" : type.ToString();
76-
return Path.Combine(Sprite.SpritePath, spriteFolder, "random.png");
118+
return Path.Combine(RandomizerDirectories.SpritePath, spriteFolder, "random.png");
77119
}
78120

79121
/// <summary>
@@ -232,4 +274,40 @@ public Sprite GetSprite(SpriteType type)
232274
return sprite;
233275
}
234276

277+
/// <summary>
278+
/// Deletes a user added sprite
279+
/// </summary>
280+
/// <param name="sprite">The sprite to delete</param>
281+
public bool DeleteSprite(Sprite sprite)
282+
{
283+
if (!sprite.IsUserSprite)
284+
{
285+
return false;
286+
}
287+
288+
try
289+
{
290+
if (!string.IsNullOrEmpty(sprite.PreviewPath) && File.Exists(sprite.PreviewPath))
291+
{
292+
File.Delete(sprite.PreviewPath);
293+
}
294+
}
295+
catch (Exception e)
296+
{
297+
_logger.LogError(e, "Failed to delete sprite preview file");
298+
}
299+
300+
try
301+
{
302+
File.Delete(sprite.FilePath);
303+
Sprites.Remove(sprite);
304+
return true;
305+
}
306+
catch (Exception e)
307+
{
308+
_logger.LogError(e, "Failed to delete sprite preview file");
309+
return false;
310+
}
311+
}
312+
235313
}

src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ namespace TrackerCouncil.Smz3.Data.Services;
99
/// <summary>
1010
/// Service for loading tracker speech sprites
1111
/// </summary>
12-
/// <param name="logger"></param>
1312
/// <param name="optionsFactory"></param>
14-
public class TrackerSpriteService(ILogger<TrackerSpriteService> logger, OptionsFactory optionsFactory)
13+
public class TrackerSpriteService(OptionsFactory optionsFactory)
1514
{
1615
private List<TrackerSpeechImagePack> _packs = [];
1716

src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
</ItemGroup>
2323

2424
<ItemGroup>
25+
<PackageReference Include="Avalonia" Version="11.0.11" />
2526
<PackageReference Include="MattEqualsCoder.DynamicForms.Core" Version="1.0.1" />
2627
<PackageReference Include="MattEqualsCoder.GitHubReleaseChecker" Version="1.1.2" />
2728
<PackageReference Include="MattEqualsCoder.MSURandomizer.Library" Version="3.0.0-rc.5" />

src/TrackerCouncil.Smz3.Data/ViewModels/SpriteViewModel.cs

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
using System.Collections.Generic;
22
using System.ComponentModel;
33
using System.Runtime.CompilerServices;
4+
using Avalonia.Media;
45
using TrackerCouncil.Smz3.Data.Options;
56

67
namespace TrackerCouncil.Smz3.Data.ViewModels;
78

89
public class SpriteViewModel : INotifyPropertyChanged
910
{
1011
private bool _display;
12+
private static readonly IBrush s_defaultIconBrush = Brushes.Silver;
13+
private static readonly IBrush s_starredIconBrush = Brushes.Goldenrod;
14+
private static readonly IBrush s_hiddenIconBrush = Brushes.IndianRed;
1115

12-
private static Dictionary<SpriteType, (int, int)> s_ImageDimensions = new()
16+
private static readonly Dictionary<SpriteType, (int, int)> s_imageDimensions = new()
1317
{
1418
{ SpriteType.Link, (64, 96) }, { SpriteType.Samus, (64, 106) }, { SpriteType.Ship, (248, 92) },
1519
};
1620

17-
private static Dictionary<SpriteType, int> s_Widths = new()
21+
private static readonly Dictionary<SpriteType, int> s_widths = new()
1822
{
19-
{ SpriteType.Link, 250 }, { SpriteType.Samus, 250 }, { SpriteType.Ship, 450 },
23+
{ SpriteType.Link, 235 }, { SpriteType.Samus, 235 }, { SpriteType.Ship, 450 },
2024
};
2125

2226
public SpriteViewModel(Sprite sprite)
@@ -25,22 +29,68 @@ public SpriteViewModel(Sprite sprite)
2529
Name = sprite.Name;
2630
Author = sprite.Author;
2731
PreviewPath = sprite.PreviewPath;
28-
SpriteOption = sprite.SpriteOption;
2932
Display = sprite.SpriteOption != SpriteOptions.Hide;
30-
PanelWidth = s_Widths[sprite.SpriteType];
31-
ImageWidth = s_ImageDimensions[sprite.SpriteType].Item1;
32-
ImageHeight = s_ImageDimensions[sprite.SpriteType].Item2;
33-
CanFavoriteAndHide = !sprite.IsRandomSprite;
33+
PanelWidth = s_widths[sprite.SpriteType];
34+
ImageWidth = s_imageDimensions[sprite.SpriteType].Item1;
35+
ImageHeight = s_imageDimensions[sprite.SpriteType].Item2;
36+
CanFavorite = !sprite.IsRandomSprite;
37+
CanHide = sprite is { IsUserSprite: false };
38+
CanDelete = sprite is { IsRandomSprite: false, IsUserSprite: true };
39+
IconOpacity = CanFavorite ? 1f : 0.3f;
40+
41+
SetSpriteOption(sprite.SpriteOption);
3442
}
3543

3644
public Sprite Sprite { get; }
3745
public string Name { get; set; }
3846
public string Author { get; set; }
39-
public string PreviewPath { get; set; }
40-
public bool CanFavoriteAndHide { get; set; }
47+
public bool CanFavorite { get; set; }
4148
public int PanelWidth { get; }
4249
public int ImageWidth { get; }
4350
public int ImageHeight { get; }
51+
public float IconOpacity { get; }
52+
53+
private string? _previewPath;
54+
public string? PreviewPath
55+
{
56+
get => _previewPath;
57+
set => SetField(ref _previewPath, value);
58+
}
59+
60+
private bool _canHide;
61+
public bool CanHide
62+
{
63+
get => _canHide;
64+
set => SetField(ref _canHide, value);
65+
}
66+
67+
private bool _canDelete;
68+
public bool CanDelete
69+
{
70+
get => _canDelete;
71+
set => SetField(ref _canDelete, value);
72+
}
73+
74+
private IBrush? _starBrush = s_defaultIconBrush;
75+
public IBrush? StarBrush
76+
{
77+
get => _starBrush;
78+
set => SetField(ref _starBrush, value);
79+
}
80+
81+
private IBrush? _hideBrush = s_defaultIconBrush;
82+
public IBrush? HideBrush
83+
{
84+
get => _hideBrush;
85+
set => SetField(ref _hideBrush, value);
86+
}
87+
88+
private IBrush? _deleteBrush = s_defaultIconBrush;
89+
public IBrush? DeleteBrush
90+
{
91+
get => _deleteBrush;
92+
set => SetField(ref _deleteBrush, value);
93+
}
4494

4595
public SpriteOptions SpriteOption
4696
{
@@ -50,7 +100,7 @@ public SpriteOptions SpriteOption
50100
Sprite.SpriteOption = value;
51101
OnPropertyChanged();
52102
OnPropertyChanged(nameof(IsFavorite));
53-
OnPropertyChanged(nameof(IsNotFavorite));
103+
OnPropertyChanged(nameof(IsHidden));
54104
}
55105
}
56106

@@ -64,7 +114,37 @@ public bool Display
64114

65115
public bool IsFavorite => SpriteOption == SpriteOptions.Favorite;
66116

67-
public bool IsNotFavorite => !IsFavorite;
117+
public bool IsHidden => SpriteOption == SpriteOptions.Hide;
118+
119+
public void SetSpriteOption(SpriteOptions option)
120+
{
121+
SpriteOption = option;
122+
123+
if (Sprite.IsRandomSprite)
124+
{
125+
StarBrush = s_defaultIconBrush;
126+
HideBrush = s_defaultIconBrush;
127+
return;
128+
}
129+
else if (option == SpriteOptions.Default)
130+
{
131+
StarBrush = s_defaultIconBrush;
132+
HideBrush = s_defaultIconBrush;
133+
DeleteBrush = s_defaultIconBrush;
134+
}
135+
else if (option == SpriteOptions.Favorite)
136+
{
137+
StarBrush = s_starredIconBrush;
138+
HideBrush = s_defaultIconBrush;
139+
DeleteBrush = s_defaultIconBrush;
140+
}
141+
else if (option == SpriteOptions.Hide)
142+
{
143+
StarBrush = s_defaultIconBrush;
144+
HideBrush = s_hiddenIconBrush;
145+
DeleteBrush = s_defaultIconBrush;
146+
}
147+
}
68148

69149
public event PropertyChangedEventHandler? PropertyChanged;
70150

0 commit comments

Comments
 (0)