Skip to content

Commit

Permalink
Merge pull request #628 from TheTrackerCouncil/add-custom-sprite-func…
Browse files Browse the repository at this point in the history
…tionality

Add button to sprite window to upload sprites
  • Loading branch information
MattEqualsCoder authored Dec 20, 2024
2 parents 6b760fa + c755856 commit 355705a
Show file tree
Hide file tree
Showing 15 changed files with 426 additions and 86 deletions.
12 changes: 8 additions & 4 deletions src/TrackerCouncil.Smz3.Data/Options/Sprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public Sprite(string name, string author, string filePath, SpriteType spriteType
SpriteType = spriteType;
PreviewPath = previewPath;
SpriteOption = spriteOption;
IsUserSprite = filePath.StartsWith(RandomizerDirectories.UserSpritePath);

if (string.IsNullOrEmpty(filePath))
IsDefault = true;
}
Expand All @@ -50,7 +52,7 @@ private Sprite(string name, SpriteType spriteType, bool isDefault, bool isRandom
SpriteType = spriteType;
IsDefault = isDefault;
IsRandomSprite = isRandomSprite;
PreviewPath = Path.Combine(SpritePath, s_folderNames[spriteType], sprite);
PreviewPath = Path.Combine(RandomizerDirectories.SpritePath, s_folderNames[spriteType], sprite);
}

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

public string FilePath { get; set; } = "";
public string FilePath { get; } = "";

public SpriteType SpriteType { get; set; }
public SpriteType SpriteType { get; }

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

public bool IsUserSprite { get; set; }

public bool MatchesFilter(string searchTerm, SpriteFilter spriteFilter) => (string.IsNullOrEmpty(searchTerm) ||
Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) ||
Author.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) &&
((spriteFilter == SpriteFilter.Default && SpriteOption != SpriteOptions.Hide) ||
(spriteFilter == SpriteFilter.Favorited && SpriteOption == SpriteOptions.Favorite) ||
(spriteFilter == SpriteFilter.Hidden && SpriteOption == SpriteOptions.Hide) ||
(spriteFilter == SpriteFilter.User && IsUserSprite) ||
spriteFilter == SpriteFilter.All);

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

public static string SpritePath => RandomizerDirectories.SpritePath;
}
1 change: 1 addition & 0 deletions src/TrackerCouncil.Smz3.Data/Options/SpriteFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ public enum SpriteFilter
Default,
Favorited,
Hidden,
User,
All,
}
5 changes: 4 additions & 1 deletion src/TrackerCouncil.Smz3.Data/RandomizerDirectories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public static string SpritePath
}
}

public static string UserSpritePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"SMZ3CasRandomizer", "Sprites");

#if DEBUG
public static string SpriteHashYamlFilePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SMZ3CasRandomizer", "sprite-hashes-debug.yml");
#else
Expand Down Expand Up @@ -101,5 +104,5 @@ public static string TrackerSpritePath
public static string TrackerSpriteHashYamlFilePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SMZ3CasRandomizer", "tracker-sprite-hashes.yml");
#endif

public static string TrackerSpriteInitialJsonFilePath => Path.Combine(SpritePath, "tracker-sprites.json");
public static string TrackerSpriteInitialJsonFilePath => Path.Combine(SpritePath, "tracker-sprites.json");
}
94 changes: 86 additions & 8 deletions src/TrackerCouncil.Smz3.Data/Services/SpriteService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public SpriteService(ILogger<SpriteService> logger, OptionsFactory optionsFactor
_options = optionsFactory.Create();
}

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

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

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

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

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

var extraSpriteDirectory =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"SMZ3CasRandomizer", "Sprites");
var extraSpriteDirectory = RandomizerDirectories.UserSpritePath;

if (Directory.Exists(extraSpriteDirectory))
{
Expand All @@ -65,6 +63,50 @@ public Task LoadSpritesAsync()
});
}

public Sprite? AddCustomSprite(string spritePath, string? previewImagePath)
{
var userSpritePath = RandomizerDirectories.UserSpritePath;

if (spritePath.StartsWith(RandomizerDirectories.SpritePath) ||
spritePath.StartsWith(userSpritePath) ||
!File.Exists(spritePath))
{
return null;
}

var destinationSpritePath = Path.Combine(userSpritePath, Path.GetFileName(spritePath));
File.Copy(spritePath, destinationSpritePath, true);
if (File.Exists(previewImagePath))
{
var baseFileName = Path.GetFileNameWithoutExtension(spritePath);
File.Copy(previewImagePath, Path.Combine(userSpritePath, $"{baseFileName}.png"), true);
}

var sprite = new Sprite();
try
{
sprite = ".rdc".Equals(Path.GetExtension(destinationSpritePath), StringComparison.OrdinalIgnoreCase)
? LoadRdcSprite(destinationSpritePath)
: LoadIpsSprite(destinationSpritePath);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load Sprite {SpritePath}", destinationSpritePath);
}

if (Sprites.Any(x => x.FilePath == destinationSpritePath))
{
var oldSprite = Sprites.First(x => x.FilePath == destinationSpritePath);
oldSprite.Name = sprite.Name;
oldSprite.Author = sprite.Author;
oldSprite.PreviewPath = sprite.PreviewPath;
return oldSprite;
}

Sprites.Add(sprite);
return sprite;
}

/// <summary>
/// Retrieves the random sprite image for the given sprite type
/// </summary>
Expand All @@ -73,7 +115,7 @@ public Task LoadSpritesAsync()
public string GetRandomPreviewImage(SpriteType type)
{
var spriteFolder = type == SpriteType.Ship ? "Ships" : type.ToString();
return Path.Combine(Sprite.SpritePath, spriteFolder, "random.png");
return Path.Combine(RandomizerDirectories.SpritePath, spriteFolder, "random.png");
}

/// <summary>
Expand Down Expand Up @@ -232,4 +274,40 @@ public Sprite GetSprite(SpriteType type)
return sprite;
}

/// <summary>
/// Deletes a user added sprite
/// </summary>
/// <param name="sprite">The sprite to delete</param>
public bool DeleteSprite(Sprite sprite)
{
if (!sprite.IsUserSprite)
{
return false;
}

try
{
if (!string.IsNullOrEmpty(sprite.PreviewPath) && File.Exists(sprite.PreviewPath))
{
File.Delete(sprite.PreviewPath);
}
}
catch (Exception e)
{
_logger.LogError(e, "Failed to delete sprite preview file");
}

try
{
File.Delete(sprite.FilePath);
Sprites.Remove(sprite);
return true;
}
catch (Exception e)
{
_logger.LogError(e, "Failed to delete sprite preview file");
return false;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ namespace TrackerCouncil.Smz3.Data.Services;
/// <summary>
/// Service for loading tracker speech sprites
/// </summary>
/// <param name="logger"></param>
/// <param name="optionsFactory"></param>
public class TrackerSpriteService(ILogger<TrackerSpriteService> logger, OptionsFactory optionsFactory)
public class TrackerSpriteService(OptionsFactory optionsFactory)
{
private List<TrackerSpeechImagePack> _packs = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.11" />
<PackageReference Include="MattEqualsCoder.DynamicForms.Core" Version="1.0.1" />
<PackageReference Include="MattEqualsCoder.GitHubReleaseChecker" Version="1.1.2" />
<PackageReference Include="MattEqualsCoder.MSURandomizer.Library" Version="3.0.0-rc.5" />
Expand Down
104 changes: 92 additions & 12 deletions src/TrackerCouncil.Smz3.Data/ViewModels/SpriteViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Media;
using TrackerCouncil.Smz3.Data.Options;

namespace TrackerCouncil.Smz3.Data.ViewModels;

public class SpriteViewModel : INotifyPropertyChanged
{
private bool _display;
private static readonly IBrush s_defaultIconBrush = Brushes.Silver;
private static readonly IBrush s_starredIconBrush = Brushes.Goldenrod;
private static readonly IBrush s_hiddenIconBrush = Brushes.IndianRed;

private static Dictionary<SpriteType, (int, int)> s_ImageDimensions = new()
private static readonly Dictionary<SpriteType, (int, int)> s_imageDimensions = new()
{
{ SpriteType.Link, (64, 96) }, { SpriteType.Samus, (64, 106) }, { SpriteType.Ship, (248, 92) },
};

private static Dictionary<SpriteType, int> s_Widths = new()
private static readonly Dictionary<SpriteType, int> s_widths = new()
{
{ SpriteType.Link, 250 }, { SpriteType.Samus, 250 }, { SpriteType.Ship, 450 },
{ SpriteType.Link, 235 }, { SpriteType.Samus, 235 }, { SpriteType.Ship, 450 },
};

public SpriteViewModel(Sprite sprite)
Expand All @@ -25,22 +29,68 @@ public SpriteViewModel(Sprite sprite)
Name = sprite.Name;
Author = sprite.Author;
PreviewPath = sprite.PreviewPath;
SpriteOption = sprite.SpriteOption;
Display = sprite.SpriteOption != SpriteOptions.Hide;
PanelWidth = s_Widths[sprite.SpriteType];
ImageWidth = s_ImageDimensions[sprite.SpriteType].Item1;
ImageHeight = s_ImageDimensions[sprite.SpriteType].Item2;
CanFavoriteAndHide = !sprite.IsRandomSprite;
PanelWidth = s_widths[sprite.SpriteType];
ImageWidth = s_imageDimensions[sprite.SpriteType].Item1;
ImageHeight = s_imageDimensions[sprite.SpriteType].Item2;
CanFavorite = !sprite.IsRandomSprite;
CanHide = sprite is { IsUserSprite: false };
CanDelete = sprite is { IsRandomSprite: false, IsUserSprite: true };
IconOpacity = CanFavorite ? 1f : 0.3f;

SetSpriteOption(sprite.SpriteOption);
}

public Sprite Sprite { get; }
public string Name { get; set; }
public string Author { get; set; }
public string PreviewPath { get; set; }
public bool CanFavoriteAndHide { get; set; }
public bool CanFavorite { get; set; }
public int PanelWidth { get; }
public int ImageWidth { get; }
public int ImageHeight { get; }
public float IconOpacity { get; }

private string? _previewPath;
public string? PreviewPath
{
get => _previewPath;
set => SetField(ref _previewPath, value);
}

private bool _canHide;
public bool CanHide
{
get => _canHide;
set => SetField(ref _canHide, value);
}

private bool _canDelete;
public bool CanDelete
{
get => _canDelete;
set => SetField(ref _canDelete, value);
}

private IBrush? _starBrush = s_defaultIconBrush;
public IBrush? StarBrush
{
get => _starBrush;
set => SetField(ref _starBrush, value);
}

private IBrush? _hideBrush = s_defaultIconBrush;
public IBrush? HideBrush
{
get => _hideBrush;
set => SetField(ref _hideBrush, value);
}

private IBrush? _deleteBrush = s_defaultIconBrush;
public IBrush? DeleteBrush
{
get => _deleteBrush;
set => SetField(ref _deleteBrush, value);
}

public SpriteOptions SpriteOption
{
Expand All @@ -50,7 +100,7 @@ public SpriteOptions SpriteOption
Sprite.SpriteOption = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsFavorite));
OnPropertyChanged(nameof(IsNotFavorite));
OnPropertyChanged(nameof(IsHidden));
}
}

Expand All @@ -64,7 +114,37 @@ public bool Display

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

public bool IsNotFavorite => !IsFavorite;
public bool IsHidden => SpriteOption == SpriteOptions.Hide;

public void SetSpriteOption(SpriteOptions option)
{
SpriteOption = option;

if (Sprite.IsRandomSprite)
{
StarBrush = s_defaultIconBrush;
HideBrush = s_defaultIconBrush;
return;
}
else if (option == SpriteOptions.Default)
{
StarBrush = s_defaultIconBrush;
HideBrush = s_defaultIconBrush;
DeleteBrush = s_defaultIconBrush;
}
else if (option == SpriteOptions.Favorite)
{
StarBrush = s_starredIconBrush;
HideBrush = s_defaultIconBrush;
DeleteBrush = s_defaultIconBrush;
}
else if (option == SpriteOptions.Hide)
{
StarBrush = s_defaultIconBrush;
HideBrush = s_hiddenIconBrush;
DeleteBrush = s_defaultIconBrush;
}
}

public event PropertyChangedEventHandler? PropertyChanged;

Expand Down
Loading

0 comments on commit 355705a

Please sign in to comment.