1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 12:57:36 +08:00

Add SkinnableSprite for arbitrary sprite additions

This commit is contained in:
Dean Herbert 2022-03-23 15:11:35 +09:00
parent 3a16483214
commit fca9faac9b
4 changed files with 147 additions and 6 deletions

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
namespace osu.Game.Skinning.Components
{
/// <summary>
/// Intended to be a test bed for skinning. May be removed at some point in the future.
/// </summary>
[UsedImplicitly]
public class SkinSprite : CompositeDrawable, ISkinnableDrawable
{
public bool UsesFixedAnchor { get; set; }
[SettingSource("Sprite name", "The filename of the sprite")]
public Bindable<string> SpriteName { get; } = new Bindable<string>(string.Empty);
[Resolved]
private ISkinSource source { get; set; }
public SkinSprite()
{
AutoSizeAxes = Axes.Both;
}
protected override void LoadComplete()
{
base.LoadComplete();
SpriteName.BindValueChanged(spriteName =>
{
InternalChildren = new Drawable[]
{
new Sprite
{
Texture = source.GetTexture(SpriteName.Value),
}
};
}, true);
}
}
}

View File

@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -11,6 +13,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
@ -18,11 +21,12 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Skinning.Components;
namespace osu.Game.Skinning.Editor
{
[Cached(typeof(SkinEditor))]
public class SkinEditor : VisibilityContainer
public class SkinEditor : VisibilityContainer, ICanAcceptFiles
{
public const double TRANSITION_DURATION = 500;
@ -36,6 +40,9 @@ namespace osu.Game.Skinning.Editor
private Bindable<Skin> currentSkin;
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
[Resolved]
private SkinManager skins { get; set; }
@ -171,6 +178,8 @@ namespace osu.Game.Skinning.Editor
Show();
game?.RegisterImportHandler(this);
// as long as the skin editor is loaded, let's make sure we can modify the current skin.
currentSkin = skins.CurrentSkin.GetBoundCopy();
@ -186,6 +195,13 @@ namespace osu.Game.Skinning.Editor
SelectedComponents.BindCollectionChanged((_, __) => Scheduler.AddOnce(populateSettings), true);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
game?.UnregisterImportHandler(this);
}
public void UpdateTargetScreen(Drawable targetScreen)
{
this.targetScreen = targetScreen;
@ -229,15 +245,20 @@ namespace osu.Game.Skinning.Editor
}
private void placeComponent(Type type)
{
if (!(Activator.CreateInstance(type) is ISkinnableDrawable component))
throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISkinnableDrawable)}.");
placeComponent(component);
}
private void placeComponent(ISkinnableDrawable component)
{
var targetContainer = getFirstTarget();
if (targetContainer == null)
return;
if (!(Activator.CreateInstance(type) is ISkinnableDrawable component))
throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISkinnableDrawable)}.");
var drawableComponent = (Drawable)component;
// give newly added components a sane starting location.
@ -313,5 +334,32 @@ namespace osu.Game.Skinning.Editor
foreach (var item in items)
availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item);
}
public Task Import(params string[] paths)
{
Schedule(() =>
{
var file = new FileInfo(paths.First());
// import to skin
currentSkin.Value.SkinInfo.PerformWrite(skinInfo =>
{
using (var contents = file.OpenRead())
skins.AddFile(skinInfo, contents, file.Name);
});
// place component
placeComponent(new SkinSprite
{
SpriteName = { Value = Path.GetFileNameWithoutExtension(file.Name) }
});
});
return Task.CompletedTask;
}
public Task Import(params ImportTask[] tasks) => throw new NotImplementedException();
public IEnumerable<string> HandledExtensions => new[] { ".jpg", ".jpeg", ".png" };
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
@ -23,6 +24,7 @@ using osu.Game.Audio;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
namespace osu.Game.Skinning
@ -35,7 +37,7 @@ namespace osu.Game.Skinning
/// For gameplay components, see <see cref="RulesetSkinProvidingContainer"/> which adds extra legacy and toggle logic that may affect the lookup process.
/// </remarks>
[ExcludeFromDynamicCompile]
public class SkinManager : ISkinSource, IStorageResourceProvider, IModelImporter<SkinInfo>
public class SkinManager : ISkinSource, IStorageResourceProvider, IModelImporter<SkinInfo>, IModelManager<SkinInfo>, IModelFileManager<SkinInfo, RealmNamedFileUsage>
{
private readonly AudioManager audio;
@ -306,5 +308,45 @@ namespace osu.Game.Skinning
}
#endregion
public bool Delete(SkinInfo item)
{
return skinModelManager.Delete(item);
}
public void Delete(List<SkinInfo> items, bool silent = false)
{
skinModelManager.Delete(items, silent);
}
public void Undelete(List<SkinInfo> items, bool silent = false)
{
skinModelManager.Undelete(items, silent);
}
public void Undelete(SkinInfo item)
{
skinModelManager.Undelete(item);
}
public bool IsAvailableLocally(SkinInfo model)
{
return skinModelManager.IsAvailableLocally(model);
}
public void ReplaceFile(SkinInfo model, RealmNamedFileUsage file, Stream contents)
{
skinModelManager.ReplaceFile(model, file, contents);
}
public void DeleteFile(SkinInfo model, RealmNamedFileUsage file)
{
skinModelManager.DeleteFile(model, file);
}
public void AddFile(SkinInfo model, Stream contents, string filename)
{
skinModelManager.AddFile(model, contents, filename);
}
}
}

View File

@ -11,7 +11,7 @@ namespace osu.Game.Skinning
/// <summary>
/// A skinnable element which uses a stable sprite and can therefore share implementation logic.
/// </summary>
public class SkinnableSprite : SkinnableDrawable
public class SkinnableSprite : SkinnableDrawable, ISkinnableDrawable
{
protected override bool ApplySizeRestrictionsToDefault => true;
@ -42,5 +42,7 @@ namespace osu.Game.Skinning
public string LookupName { get; }
}
public bool UsesFixedAnchor { get; set; }
}
}