1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 20:23:00 +08:00

Hook up full save/load flow

This commit is contained in:
Dean Herbert 2021-05-10 22:43:48 +09:00
parent 004798d61d
commit b248b2e5e3
9 changed files with 181 additions and 72 deletions

View File

@ -1,15 +0,0 @@
// 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 osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// A container which supports skinnable components being added to it.
/// </summary>
public interface ISkinnableTarget
{
public SkinnableTarget Target { get; }
}
}

View File

@ -22,7 +22,7 @@ using osuTK;
namespace osu.Game.Screens.Play
{
[Cached]
public class HUDOverlay : Container, IKeyBindingHandler<GlobalAction>, IDefaultSkinnableTarget
public class HUDOverlay : Container, IKeyBindingHandler<GlobalAction>
{
public const float FADE_DURATION = 300;

View File

@ -2,17 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Screens.Play.HUD;
using osuTK.Graphics;
namespace osu.Game.Skinning
@ -20,35 +14,11 @@ namespace osu.Game.Skinning
public class DefaultSkin : Skin
{
public DefaultSkin()
: base(SkinInfo.Default)
: base(SkinInfo.Default, null)
{
Configuration = new DefaultSkinConfiguration();
}
public override Drawable GetDrawableComponent(ISkinComponent component)
{
switch (component)
{
case SkinnableTargetComponent target:
switch (target.Target)
{
case SkinnableTarget.MainHUDComponents:
var infos = JsonConvert.DeserializeObject<IEnumerable<SkinnableInfo>>(File.ReadAllText("/Users/Dean/json-out.json")).Where(i => i.Target == SkinnableTarget.MainHUDComponents);
var container = new SkinnableTargetWrapper(target.Target)
{
ChildrenEnumerable = infos.Select(i => i.CreateInstance())
};
return container;
}
break;
}
return null;
}
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public override ISample GetSample(ISampleInfo sampleInfo) => null;
@ -72,14 +42,4 @@ namespace osu.Game.Skinning
return null;
}
}
public class SkinnableTargetWrapper : Container, ISkinnableTarget
{
public SkinnableTarget Target { get; }
public SkinnableTargetWrapper(SkinnableTarget target)
{
Target = target;
}
}
}

View File

@ -1,12 +1,9 @@
// 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 System.IO;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Extensions;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components;
@ -32,11 +29,6 @@ namespace osu.Game.Skinning.Editor
{
ISkinnableComponent[] skinnableComponents = target.ChildrenOfType<ISkinnableComponent>().ToArray();
// todo: the OfType() call can be removed with better IDrawable support.
string json = JsonConvert.SerializeObject(skinnableComponents.OfType<Drawable>().Select(d => d.CreateSerialisedInformation()), new JsonSerializerSettings { Formatting = Formatting.Indented });
File.WriteAllText("/Users/Dean/json-out.json", json);
foreach (var c in skinnableComponents)
AddBlueprintFor(c);

View File

@ -11,6 +11,8 @@ using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning.Editor
{
@ -24,6 +26,9 @@ namespace osu.Game.Skinning.Editor
protected override bool StartHidden => true;
[Resolved]
private SkinManager skins { get; set; }
public SkinEditor(Drawable target)
{
this.target = target;
@ -53,7 +58,24 @@ namespace osu.Game.Skinning.Editor
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RequestPlacement = placeComponent
}
},
new TriangleButton
{
Text = "Save Changes",
Width = 140,
Height = 50,
Padding = new MarginPadding
{
Top = 10,
Left = 10,
},
Margin = new MarginPadding
{
Right = 10,
Bottom = 10,
},
Action = save,
},
}
};
@ -71,7 +93,7 @@ namespace osu.Game.Skinning.Editor
var targetContainer = target.ChildrenOfType<IDefaultSkinnableTarget>().FirstOrDefault();
targetContainer?.Add(instance);
(targetContainer as Container)?.Add(instance);
}
protected override void LoadComplete()
@ -80,6 +102,23 @@ namespace osu.Game.Skinning.Editor
Show();
}
private void save()
{
var currentSkin = skins.CurrentSkin.Value;
var legacySkin = currentSkin as LegacySkin;
if (legacySkin == null)
return;
SkinnableElementTargetContainer[] targetContainers = target.ChildrenOfType<SkinnableElementTargetContainer>().ToArray();
foreach (var t in targetContainers)
legacySkin.UpdateDrawableTarget(t);
skins.Save(skins.CurrentSkin.Value);
}
protected override bool OnHover(HoverEvent e) => true;
protected override bool OnMouseDown(MouseDownEvent e) => true;

View File

@ -2,14 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning
{
/// <summary>
/// Denotes a container which can house <see cref="ISkinnableComponent"/>s.
/// </summary>
public interface ISkinnableTarget : IContainerCollection<Drawable>
public interface ISkinnableTarget : IDrawable
{
public SkinnableTarget Target { get; }
}
}

View File

@ -58,7 +58,7 @@ namespace osu.Game.Skinning
}
protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, string filename)
: base(skin)
: base(skin, resources)
{
using (var stream = storage?.GetStream(filename))
{
@ -321,8 +321,32 @@ namespace osu.Game.Skinning
public override Drawable GetDrawableComponent(ISkinComponent component)
{
if (base.GetDrawableComponent(component) is Drawable c)
return c;
switch (component)
{
case SkinnableTargetComponent target:
switch (target.Target)
{
case SkinnableTarget.MainHUDComponents:
return new SkinnableTargetWrapper(target.Target)
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
// TODO: these should fallback to the osu!classic skin.
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboCounter)) ?? new DefaultComboCounter(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)) ?? new DefaultScoreCounter(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(),
}
};
}
return null;
case HUDSkinComponent hudComponent:
{
if (!this.HasFont(LegacyFont.Score))

View File

@ -2,12 +2,19 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning
{
@ -15,9 +22,13 @@ namespace osu.Game.Skinning
{
public readonly SkinInfo SkinInfo;
private readonly IStorageResourceProvider resources;
public SkinConfiguration Configuration { get; protected set; }
public abstract Drawable GetDrawableComponent(ISkinComponent componentName);
public IDictionary<SkinnableTarget, SkinnableInfo[]> DrawableComponentInfo => drawableComponentInfo;
private readonly Dictionary<SkinnableTarget, SkinnableInfo[]> drawableComponentInfo = new Dictionary<SkinnableTarget, SkinnableInfo[]>();
public abstract ISample GetSample(ISampleInfo sampleInfo);
@ -27,9 +38,62 @@ namespace osu.Game.Skinning
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
protected Skin(SkinInfo skin)
protected Skin(SkinInfo skin, IStorageResourceProvider resources)
{
SkinInfo = skin;
// may be null for default skin.
this.resources = resources;
}
public void UpdateDrawableTarget(SkinnableElementTargetContainer targetContainer)
{
DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSerialisedChildren().ToArray();
}
public virtual Drawable GetDrawableComponent(ISkinComponent component)
{
switch (component)
{
case SkinnableTargetComponent target:
var skinnableTarget = target.Target;
if (!DrawableComponentInfo.TryGetValue(skinnableTarget, out var skinnableInfo))
{
switch (skinnableTarget)
{
case SkinnableTarget.MainHUDComponents:
// skininfo files may be null for default skin.
var fileInfo = SkinInfo.Files?.FirstOrDefault(f => f.Filename == $"{skinnableTarget}.json");
if (fileInfo == null)
return null;
var bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath);
if (bytes == null)
return null;
string jsonContent = Encoding.UTF8.GetString(bytes);
DrawableComponentInfo[skinnableTarget] = skinnableInfo =
JsonConvert.DeserializeObject<IEnumerable<SkinnableInfo>>(jsonContent).Where(i => i.Target == SkinnableTarget.MainHUDComponents).ToArray();
break;
}
}
if (skinnableInfo == null)
return null;
return new SkinnableTargetWrapper(skinnableTarget)
{
ChildrenEnumerable = skinnableInfo.Select(i => i.CreateInstance())
};
}
return null;
}
#region Disposal
@ -58,4 +122,17 @@ namespace osu.Game.Skinning
#endregion
}
public class SkinnableTargetWrapper : Container, IDefaultSkinnableTarget
{
// this is just here to implement the interface and allow a silly parent lookup to work (where the target is not the direct parent for reasons).
// TODO: need to fix this.
public SkinnableTarget Target { get; }
public SkinnableTargetWrapper(SkinnableTarget target)
{
Target = target;
RelativeSizeAxes = Axes.Both;
}
}
}

View File

@ -6,9 +6,11 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
@ -17,6 +19,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Framework.Utils;
@ -158,6 +161,35 @@ namespace osu.Game.Skinning
return new LegacySkin(skinInfo, this);
}
public void Save(Skin skin)
{
// some skins don't support saving just yet.
// eventually we will want to create a copy of the skin to allow for customisation.
if (skin.SkinInfo.Files == null)
return;
foreach (var drawableInfo in skin.DrawableComponentInfo)
{
// todo: the OfType() call can be removed with better IDrawable support.
string json = JsonConvert.SerializeObject(drawableInfo.Value, new JsonSerializerSettings { Formatting = Formatting.Indented });
using (var streamContent = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
string filename = $"{drawableInfo.Key}.json";
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
if (oldFile != null)
ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename);
else
AddFile(skin.SkinInfo, streamContent, filename);
Logger.Log($"Saving out {filename} with {json.Length} bytes");
Logger.Log(json);
}
}
}
/// <summary>
/// Perform a lookup query on available <see cref="SkinInfo"/>s.
/// </summary>