mirror of
https://github.com/ppy/osu.git
synced 2025-02-16 16:23:16 +08:00
Merge remote-tracking branch 'refs/remotes/ppy/master' into news-sidebar-new
This commit is contained in:
commit
94543bf000
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
explosion = new LegacyRollingCounter(skin, LegacyFont.Combo)
|
||||
explosion = new LegacyRollingCounter(LegacyFont.Combo)
|
||||
{
|
||||
Alpha = 0.65f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(1.5f),
|
||||
},
|
||||
counter = new LegacyRollingCounter(skin, LegacyFont.Combo)
|
||||
counter = new LegacyRollingCounter(LegacyFont.Combo)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
Child = new SkinProvidingContainer(new DefaultSkin())
|
||||
Child = new SkinProvidingContainer(new DefaultSkin(null))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Y = SPINNER_TOP_OFFSET + 115,
|
||||
},
|
||||
bonusCounter = new LegacySpriteText(source, LegacyFont.Score)
|
||||
bonusCounter = new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.TopCentre,
|
||||
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Position = new Vector2(-87, 445 + spm_hide_offset),
|
||||
},
|
||||
spmCounter = new LegacySpriteText(source, LegacyFont.Score)
|
||||
spmCounter = new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopRight,
|
||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
return new LegacySpriteText(Source, LegacyFont.HitCircle)
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
|
@ -167,5 +167,21 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20)));
|
||||
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that hovering over two handles instantaneously from one to another does not crash or cause issues to the visibility state.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHoverOverTwoHandlesInstantaneously()
|
||||
{
|
||||
AddStep("hover over top-left scale handle", () =>
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == Anchor.TopLeft)));
|
||||
AddStep("hover over top-right scale handle", () =>
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == Anchor.TopRight)));
|
||||
AddUntilStep("top-left rotation handle hidden", () =>
|
||||
this.ChildrenOfType<SelectionBoxRotationHandle>().Single(r => r.Anchor == Anchor.TopLeft).Alpha == 0);
|
||||
AddUntilStep("top-right rotation handle shown", () =>
|
||||
this.ChildrenOfType<SelectionBoxRotationHandle>().Single(r => r.Anchor == Anchor.TopRight).Alpha == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Editor;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@ -14,12 +16,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private SkinEditor skinEditor;
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skinManager { get; set; }
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("add editor overlay", () =>
|
||||
AddStep("reload skin editor", () =>
|
||||
{
|
||||
skinEditor?.Expire();
|
||||
Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE);
|
||||
|
@ -1,13 +1,11 @@
|
||||
// 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;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -32,12 +30,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
SetContents(() =>
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var mods = new[] { ruleset.GetAutoplayMod() };
|
||||
var working = CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||
var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
|
||||
|
||||
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap);
|
||||
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap, mods);
|
||||
|
||||
var hudOverlay = new HUDOverlay(drawableRuleset, Array.Empty<Mod>())
|
||||
var hudOverlay = new HUDOverlay(drawableRuleset, mods)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready));
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
|
||||
|
||||
AddAssert("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
|
||||
public bool SkinLoaded => skin.IsResultAvailable;
|
||||
public ISkin Skin => skin.Value;
|
||||
|
||||
protected virtual ISkin GetSkin() => new DefaultSkin();
|
||||
protected virtual ISkin GetSkin() => new DefaultSkin(null);
|
||||
private readonly RecyclableLazy<ISkin> skin;
|
||||
|
||||
public abstract Stream GetStream(string storagePath);
|
||||
|
@ -472,7 +472,7 @@ namespace osu.Game.Database
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete new file.
|
||||
/// Delete an existing file.
|
||||
/// </summary>
|
||||
/// <param name="model">The item to operate on.</param>
|
||||
/// <param name="file">The existing file to be deleted.</param>
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Extensions
|
||||
@ -43,5 +45,23 @@ namespace osu.Game.Extensions
|
||||
/// <returns>The delta vector in Parent's coordinates.</returns>
|
||||
public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) =>
|
||||
drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta);
|
||||
|
||||
public static SkinnableInfo CreateSkinnableInfo(this Drawable component) => new SkinnableInfo(component);
|
||||
|
||||
public static void ApplySkinnableInfo(this Drawable component, SkinnableInfo info)
|
||||
{
|
||||
// todo: can probably make this better via deserialisation directly using a common interface.
|
||||
component.Position = info.Position;
|
||||
component.Rotation = info.Rotation;
|
||||
component.Scale = info.Scale;
|
||||
component.Anchor = info.Anchor;
|
||||
component.Origin = info.Origin;
|
||||
|
||||
if (component is Container container)
|
||||
{
|
||||
foreach (var child in info.Children)
|
||||
container.Add(child.CreateInstance());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
osu.Game/Extensions/TypeExtensions.cs
Normal file
31
osu.Game/Extensions/TypeExtensions.cs
Normal file
@ -0,0 +1,31 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Extensions
|
||||
{
|
||||
internal static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <paramref name="type"/>'s <see cref="Type.AssemblyQualifiedName"/>
|
||||
/// with the assembly version, culture and public key token values removed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is usually used in extensibility scenarios (i.e. for custom rulesets or skins)
|
||||
/// when a version-agnostic identifier associated with a C# class - potentially originating from
|
||||
/// an external assembly - is needed.
|
||||
/// Leaving only the type and assembly names in such a scenario allows to preserve compatibility
|
||||
/// across assembly versions.
|
||||
/// </remarks>
|
||||
internal static string GetInvariantInstantiationInfo(this Type type)
|
||||
{
|
||||
string assemblyQualifiedName = type.AssemblyQualifiedName;
|
||||
if (assemblyQualifiedName == null)
|
||||
throw new ArgumentException($"{type}'s assembly-qualified name is null. Ensure that it is a concrete type and not a generic type parameter.", nameof(type));
|
||||
|
||||
return string.Join(',', assemblyQualifiedName.Split(',').Take(2));
|
||||
}
|
||||
}
|
||||
}
|
18
osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs
Normal file
18
osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// 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.Framework.Allocation;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class DangerousTriangleButton : TriangleButton
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.PinkDark;
|
||||
Triangles.ColourDark = colours.PinkDarker;
|
||||
Triangles.ColourLight = colours.Pink;
|
||||
}
|
||||
}
|
||||
}
|
508
osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs
generated
Normal file
508
osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs
generated
Normal file
@ -0,0 +1,508 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
[DbContext(typeof(OsuDbContext))]
|
||||
[Migration("20210511060743_AddSkinInstantiationInfo")]
|
||||
partial class AddSkinInstantiationInfo
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<float>("ApproachRate");
|
||||
|
||||
b.Property<float>("CircleSize");
|
||||
|
||||
b.Property<float>("DrainRate");
|
||||
|
||||
b.Property<float>("OverallDifficulty");
|
||||
|
||||
b.Property<double>("SliderMultiplier");
|
||||
|
||||
b.Property<double>("SliderTickRate");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapDifficulty");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("AudioLeadIn");
|
||||
|
||||
b.Property<double>("BPM");
|
||||
|
||||
b.Property<int>("BaseDifficultyID");
|
||||
|
||||
b.Property<int>("BeatDivisor");
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<bool>("Countdown");
|
||||
|
||||
b.Property<double>("DistanceSpacing");
|
||||
|
||||
b.Property<bool>("EpilepsyWarning");
|
||||
|
||||
b.Property<int>("GridSize");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<bool>("Hidden");
|
||||
|
||||
b.Property<double>("Length");
|
||||
|
||||
b.Property<bool>("LetterboxInBreaks");
|
||||
|
||||
b.Property<string>("MD5Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapID");
|
||||
|
||||
b.Property<string>("Path");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<bool>("SpecialStyle");
|
||||
|
||||
b.Property<float>("StackLeniency");
|
||||
|
||||
b.Property<double>("StarDifficulty");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoredBookmarks");
|
||||
|
||||
b.Property<double>("TimelineZoom");
|
||||
|
||||
b.Property<string>("Version");
|
||||
|
||||
b.Property<bool>("WidescreenStoryboard");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BaseDifficultyID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("Hash");
|
||||
|
||||
b.HasIndex("MD5Hash");
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("BeatmapInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Artist");
|
||||
|
||||
b.Property<string>("ArtistUnicode");
|
||||
|
||||
b.Property<string>("AudioFile");
|
||||
|
||||
b.Property<string>("AuthorString")
|
||||
.HasColumnName("Author");
|
||||
|
||||
b.Property<string>("BackgroundFile");
|
||||
|
||||
b.Property<int>("PreviewTime");
|
||||
|
||||
b.Property<string>("Source");
|
||||
|
||||
b.Property<string>("Tags");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("TitleUnicode");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.ToTable("BeatmapSetFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset>("DateAdded");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapSetID");
|
||||
|
||||
b.Property<bool>("Protected");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapSetID")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("BeatmapSetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnName("Key");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("SkinInfoID");
|
||||
|
||||
b.Property<string>("StringValue")
|
||||
.HasColumnName("Value");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("ReferenceCount");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ReferenceCount");
|
||||
|
||||
b.ToTable("FileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("IntAction")
|
||||
.HasColumnName("Action");
|
||||
|
||||
b.Property<string>("KeysString")
|
||||
.HasColumnName("Keys");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("IntAction");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("KeyBinding");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
|
||||
{
|
||||
b.Property<int?>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("ShortName");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Available");
|
||||
|
||||
b.HasIndex("ShortName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RulesetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int?>("ScoreInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("ScoreInfoID");
|
||||
|
||||
b.ToTable("ScoreFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("Accuracy")
|
||||
.HasColumnType("DECIMAL(1,4)");
|
||||
|
||||
b.Property<int>("BeatmapInfoID");
|
||||
|
||||
b.Property<int>("Combo");
|
||||
|
||||
b.Property<DateTimeOffset>("Date");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("MaxCombo");
|
||||
|
||||
b.Property<string>("ModsJson")
|
||||
.HasColumnName("Mods");
|
||||
|
||||
b.Property<long?>("OnlineScoreID");
|
||||
|
||||
b.Property<double?>("PP");
|
||||
|
||||
b.Property<int>("Rank");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<string>("StatisticsJson")
|
||||
.HasColumnName("Statistics");
|
||||
|
||||
b.Property<long>("TotalScore");
|
||||
|
||||
b.Property<int?>("UserID")
|
||||
.HasColumnName("UserID");
|
||||
|
||||
b.Property<string>("UserString")
|
||||
.HasColumnName("User");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapInfoID");
|
||||
|
||||
b.HasIndex("OnlineScoreID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("ScoreInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int>("SkinInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.ToTable("SkinFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Creator");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SkinInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
|
||||
.WithMany()
|
||||
.HasForeignKey("BaseDifficultyID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("MetadataID");
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("BeatmapSets")
|
||||
.HasForeignKey("MetadataID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Settings")
|
||||
.HasForeignKey("SkinInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Scoring.ScoreInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ScoreInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
|
||||
.WithMany("Scores")
|
||||
.HasForeignKey("BeatmapInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("SkinInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
public partial class AddSkinInstantiationInfo : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "InstantiationInfo",
|
||||
table: "SkinInfo",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InstantiationInfo",
|
||||
table: "SkinInfo");
|
||||
}
|
||||
}
|
||||
}
|
@ -141,8 +141,6 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<string>("TitleUnicode");
|
||||
|
||||
b.Property<string>("VideoFile");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapMetadata");
|
||||
@ -352,7 +350,7 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<long>("TotalScore");
|
||||
|
||||
b.Property<long?>("UserID")
|
||||
b.Property<int?>("UserID")
|
||||
.HasColumnName("UserID");
|
||||
|
||||
b.Property<string>("UserString")
|
||||
@ -402,6 +400,8 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
@ -339,22 +339,13 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
}
|
||||
}
|
||||
|
||||
public class ClearButton : TriangleButton
|
||||
public class ClearButton : DangerousTriangleButton
|
||||
{
|
||||
public ClearButton()
|
||||
{
|
||||
Text = "Clear";
|
||||
Size = new Vector2(80, 20);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.Pink;
|
||||
|
||||
Triangles.ColourDark = colours.PinkDark;
|
||||
Triangles.ColourLight = colours.PinkLight;
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyButton : Container
|
||||
|
@ -11,7 +11,6 @@ using osu.Game.Input;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
@ -55,10 +54,10 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
}
|
||||
}
|
||||
|
||||
public class ResetButton : TriangleButton
|
||||
public class ResetButton : DangerousTriangleButton
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
Text = "Reset all bindings in section";
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -66,10 +65,6 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
Height = 20;
|
||||
|
||||
Content.CornerRadius = 5;
|
||||
|
||||
BackgroundColour = colours.PinkDark;
|
||||
Triangles.ColourDark = colours.PinkDarker;
|
||||
Triangles.ColourLight = colours.Pink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||
/// <summary> An error occurred and a complete check could not be made. </summary>
|
||||
Error,
|
||||
|
||||
// TODO: Negligible issues should be hidden by default.
|
||||
/// <summary> A possible mistake so minor/unlikely that it can often be safely ignored. </summary>
|
||||
Negligible,
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ using JetBrains.Annotations;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Filter;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
|
||||
@ -135,7 +136,7 @@ namespace osu.Game.Rulesets
|
||||
Name = Description,
|
||||
ShortName = ShortName,
|
||||
ID = (this as ILegacyRuleset)?.LegacyID,
|
||||
InstantiationInfo = GetType().AssemblyQualifiedName,
|
||||
InstantiationInfo = GetType().GetInvariantInstantiationInfo(),
|
||||
Available = true,
|
||||
};
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Testing;
|
||||
|
||||
@ -18,20 +17,7 @@ namespace osu.Game.Rulesets
|
||||
|
||||
public string ShortName { get; set; }
|
||||
|
||||
private string instantiationInfo;
|
||||
|
||||
public string InstantiationInfo
|
||||
{
|
||||
get => instantiationInfo;
|
||||
set => instantiationInfo = abbreviateInstantiationInfo(value);
|
||||
}
|
||||
|
||||
private string abbreviateInstantiationInfo(string value)
|
||||
{
|
||||
// exclude version onwards, matching only on namespace and type.
|
||||
// this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old.
|
||||
return string.Join(',', value.Split(',').Take(2));
|
||||
}
|
||||
public string InstantiationInfo { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Available { get; set; }
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
@ -24,6 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// Includes selection and manipulation support via a <see cref="Components.SelectionHandler{T}"/>.
|
||||
/// </summary>
|
||||
public abstract class BlueprintContainer<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
||||
where T : class
|
||||
{
|
||||
protected DragBox DragBox { get; private set; }
|
||||
|
||||
@ -39,6 +42,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
protected readonly BindableList<T> SelectedItems = new BindableList<T>();
|
||||
|
||||
protected BlueprintContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -47,6 +52,24 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SelectedItems.CollectionChanged += (selectedObjects, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var o in args.NewItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select();
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var o in args.OldItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
SelectionHandler = CreateSelectionHandler();
|
||||
SelectionHandler.DeselectAll = deselectAll;
|
||||
|
||||
|
@ -2,10 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -24,8 +22,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected readonly HitObjectComposer Composer;
|
||||
|
||||
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
||||
|
||||
protected EditorBlueprintContainer(HitObjectComposer composer)
|
||||
{
|
||||
Composer = composer;
|
||||
@ -34,23 +30,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
selectedHitObjects.BindTo(Beatmap.SelectedHitObjects);
|
||||
selectedHitObjects.CollectionChanged += (selectedObjects, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var o in args.NewItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select();
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var o in args.OldItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
SelectedItems.BindTo(Beatmap.SelectedHitObjects);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -84,8 +84,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (activeHandle?.IsHeld == true)
|
||||
return;
|
||||
|
||||
activeHandle = rotationHandles.SingleOrDefault(h => h.IsHeld || h.IsHovered);
|
||||
activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered);
|
||||
activeHandle = rotationHandles.FirstOrDefault(h => h.IsHeld || h.IsHovered);
|
||||
activeHandle ??= allDragHandles.FirstOrDefault(h => h.IsHovered);
|
||||
|
||||
if (activeHandle != null)
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public class RoundedContentEditorScreen : EditorScreen
|
||||
public class EditorRoundedScreen : EditorScreen
|
||||
{
|
||||
public const int HORIZONTAL_PADDING = 100;
|
||||
|
||||
@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected override Container<Drawable> Content => roundedContent;
|
||||
|
||||
public RoundedContentEditorScreen(EditorScreenMode mode)
|
||||
public EditorRoundedScreen(EditorScreenMode mode)
|
||||
: base(mode)
|
||||
{
|
||||
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
44
osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs
Normal file
44
osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public abstract class EditorRoundedScreenSettings : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = CreateSections()
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract IReadOnlyList<Drawable> CreateSections();
|
||||
}
|
||||
}
|
61
osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs
Normal file
61
osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs
Normal file
@ -0,0 +1,61 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public abstract class EditorRoundedScreenSettingsSection : CompositeDrawable
|
||||
{
|
||||
private const int header_height = 50;
|
||||
|
||||
protected abstract string HeaderText { get; }
|
||||
|
||||
protected FillFlowContainer Flow { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height,
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Child = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = HeaderText,
|
||||
Font = new FontUsage(size: 25, weight: "bold")
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Y = header_height,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = Flow = new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Spacing = new Vector2(10),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
public class SetupScreen : RoundedContentEditorScreen
|
||||
public class SetupScreen : EditorRoundedScreen
|
||||
{
|
||||
[Cached]
|
||||
private SectionsContainer<SetupSection> sections = new SectionsContainer<SetupSection>();
|
||||
|
@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
public SetupScreenTabControl()
|
||||
{
|
||||
TabContainer.Margin = new MarginPadding { Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING };
|
||||
TabContainer.Margin = new MarginPadding { Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING };
|
||||
|
||||
AddInternal(background = new Box
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 10,
|
||||
Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING
|
||||
Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING
|
||||
};
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
|
@ -2,44 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
public class ControlPointSettings : CompositeDrawable
|
||||
public class ControlPointSettings : EditorRoundedScreenSettings
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = createSections()
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
||||
protected override IReadOnlyList<Drawable> CreateSections() => new Drawable[]
|
||||
{
|
||||
new GroupSection(),
|
||||
new TimingSection(),
|
||||
|
@ -15,7 +15,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
public class TimingScreen : RoundedContentEditorScreen
|
||||
public class TimingScreen : EditorRoundedScreen
|
||||
{
|
||||
[Cached]
|
||||
private Bindable<ControlPointGroup> selectedGroup = new Bindable<ControlPointGroup>();
|
||||
|
27
osu.Game/Screens/Edit/Verify/InterpretationSection.cs
Normal file
27
osu.Game/Screens/Edit/Verify/InterpretationSection.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
internal class InterpretationSection : EditorRoundedScreenSettingsSection
|
||||
{
|
||||
protected override string HeaderText => "Interpretation";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(VerifyScreen verify)
|
||||
{
|
||||
Flow.Add(new SettingsEnumDropdown<DifficultyRating>
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
TooltipText = "Affects checks that depend on difficulty level",
|
||||
Current = verify.InterpretedDifficulty.GetBoundCopy()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
111
osu.Game/Screens/Edit/Verify/IssueList.cs
Normal file
111
osu.Game/Screens/Edit/Verify/IssueList.cs
Normal file
@ -0,0 +1,111 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
[Cached]
|
||||
public class IssueList : CompositeDrawable
|
||||
{
|
||||
private IssueTable table;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> workingBeatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private VerifyScreen verify { get; set; }
|
||||
|
||||
private IBeatmapVerifier rulesetVerifier;
|
||||
private BeatmapVerifier generalVerifier;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
generalVerifier = new BeatmapVerifier();
|
||||
rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier();
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background2,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = table = new IssueTable(),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TriangleButton
|
||||
{
|
||||
Text = "Refresh",
|
||||
Action = refresh,
|
||||
Size = new Vector2(120, 40),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
verify.InterpretedDifficulty.BindValueChanged(_ => refresh());
|
||||
verify.HiddenIssueTypes.BindCollectionChanged((_, __) => refresh());
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
var issues = generalVerifier.Run(beatmap, workingBeatmap.Value);
|
||||
|
||||
if (rulesetVerifier != null)
|
||||
issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value));
|
||||
|
||||
issues = filter(issues);
|
||||
|
||||
table.Issues = issues
|
||||
.OrderBy(issue => issue.Template.Type)
|
||||
.ThenBy(issue => issue.Check.Metadata.Category);
|
||||
}
|
||||
|
||||
private IEnumerable<Issue> filter(IEnumerable<Issue> issues)
|
||||
{
|
||||
return issues.Where(issue => !verify.HiddenIssueTypes.Contains(issue.Template.Type));
|
||||
}
|
||||
}
|
||||
}
|
@ -2,45 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
public class IssueSettings : CompositeDrawable
|
||||
public class IssueSettings : EditorRoundedScreenSettings
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Gray3,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = createSections()
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
||||
protected override IReadOnlyList<Drawable> CreateSections() => new Drawable[]
|
||||
{
|
||||
new InterpretationSection(),
|
||||
new VisibilitySection()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
public class IssueTable : EditorTable
|
||||
{
|
||||
[Resolved]
|
||||
private Bindable<Issue> selectedIssue { get; set; }
|
||||
private VerifyScreen verify { get; set; }
|
||||
|
||||
private Bindable<Issue> selectedIssue;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; }
|
||||
@ -71,6 +73,7 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
selectedIssue = verify.SelectedIssue.GetBoundCopy();
|
||||
selectedIssue.BindValueChanged(issue =>
|
||||
{
|
||||
foreach (var b in BackgroundFlow) b.Selected = b.Item == issue.NewValue;
|
||||
|
@ -1,26 +1,25 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
public class VerifyScreen : RoundedContentEditorScreen
|
||||
[Cached]
|
||||
public class VerifyScreen : EditorRoundedScreen
|
||||
{
|
||||
[Cached]
|
||||
private Bindable<Issue> selectedIssue = new Bindable<Issue>();
|
||||
public readonly Bindable<Issue> SelectedIssue = new Bindable<Issue>();
|
||||
|
||||
public readonly Bindable<DifficultyRating> InterpretedDifficulty = new Bindable<DifficultyRating>();
|
||||
|
||||
public readonly BindableList<IssueType> HiddenIssueTypes = new BindableList<IssueType> { IssueType.Negligible };
|
||||
|
||||
public IssueList IssueList { get; private set; }
|
||||
|
||||
public VerifyScreen()
|
||||
: base(EditorScreenMode.Verify)
|
||||
@ -30,6 +29,10 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InterpretedDifficulty.Default = EditorBeatmap.BeatmapInfo.DifficultyRating;
|
||||
InterpretedDifficulty.SetDefault();
|
||||
|
||||
IssueList = new IssueList();
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -45,92 +48,12 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new IssueList(),
|
||||
IssueList,
|
||||
new IssueSettings(),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public class IssueList : CompositeDrawable
|
||||
{
|
||||
private IssueTable table;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> workingBeatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Issue> selectedIssue { get; set; }
|
||||
|
||||
private IBeatmapVerifier rulesetVerifier;
|
||||
private BeatmapVerifier generalVerifier;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
generalVerifier = new BeatmapVerifier();
|
||||
rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier();
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background2,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = table = new IssueTable(),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TriangleButton
|
||||
{
|
||||
Text = "Refresh",
|
||||
Action = refresh,
|
||||
Size = new Vector2(120, 40),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
var issues = generalVerifier.Run(beatmap, workingBeatmap.Value);
|
||||
|
||||
if (rulesetVerifier != null)
|
||||
issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value));
|
||||
|
||||
table.Issues = issues
|
||||
.OrderBy(issue => issue.Template.Type)
|
||||
.ThenBy(issue => issue.Check.Metadata.Category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
54
osu.Game/Screens/Edit/Verify/VisibilitySection.cs
Normal file
54
osu.Game/Screens/Edit/Verify/VisibilitySection.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
internal class VisibilitySection : EditorRoundedScreenSettingsSection
|
||||
{
|
||||
private readonly IssueType[] configurableIssueTypes =
|
||||
{
|
||||
IssueType.Warning,
|
||||
IssueType.Error,
|
||||
IssueType.Negligible
|
||||
};
|
||||
|
||||
private BindableList<IssueType> hiddenIssueTypes;
|
||||
|
||||
protected override string HeaderText => "Visibility";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours, VerifyScreen verify)
|
||||
{
|
||||
hiddenIssueTypes = verify.HiddenIssueTypes.GetBoundCopy();
|
||||
|
||||
foreach (IssueType issueType in configurableIssueTypes)
|
||||
{
|
||||
var checkbox = new SettingsCheckbox
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
LabelText = issueType.ToString(),
|
||||
Current = { Default = !hiddenIssueTypes.Contains(issueType) }
|
||||
};
|
||||
|
||||
checkbox.Current.SetDefault();
|
||||
checkbox.Current.BindValueChanged(state =>
|
||||
{
|
||||
if (!state.NewValue)
|
||||
hiddenIssueTypes.Add(issueType);
|
||||
else
|
||||
hiddenIssueTypes.Remove(issueType);
|
||||
});
|
||||
|
||||
Flow.Add(checkbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,23 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableComponent
|
||||
public class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable
|
||||
{
|
||||
private readonly Vector2 offset = new Vector2(-20, 5);
|
||||
|
||||
public DefaultAccuracyCounter()
|
||||
{
|
||||
Origin = Anchor.TopRight;
|
||||
Anchor = Anchor.TopRight;
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private HUDOverlay hud { get; set; }
|
||||
|
||||
@ -27,17 +17,5 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
Colour = colours.BlueLighter;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score)
|
||||
{
|
||||
// for now align with the score counter. eventually this will be user customisable.
|
||||
Anchor = Anchor.TopLeft;
|
||||
Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopLeft) + offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,14 +9,11 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultComboCounter : RollingCounter<int>, ISkinnableComponent
|
||||
public class DefaultComboCounter : RollingCounter<int>, ISkinnableDrawable
|
||||
{
|
||||
private readonly Vector2 offset = new Vector2(20, 5);
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private HUDOverlay hud { get; set; }
|
||||
|
||||
@ -32,17 +29,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Current.BindTo(scoreProcessor.Combo);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score)
|
||||
{
|
||||
// for now align with the score counter. eventually this will be user customisable.
|
||||
Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopRight) + offset;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string FormatCount(int count)
|
||||
{
|
||||
return $@"{count}x";
|
||||
|
@ -17,7 +17,7 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableComponent
|
||||
public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The base opacity of the glow.
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultScoreCounter : GameplayScoreCounter, ISkinnableComponent
|
||||
public class DefaultScoreCounter : GameplayScoreCounter, ISkinnableDrawable
|
||||
{
|
||||
public DefaultScoreCounter()
|
||||
: base(6)
|
||||
@ -24,12 +24,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Colour = colours.BlueLighter;
|
||||
|
||||
// todo: check if default once health display is skinnable
|
||||
hud?.ShowHealthbar.BindValueChanged(healthBar =>
|
||||
{
|
||||
this.MoveToY(healthBar.NewValue ? 30 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,12 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
{
|
||||
public class BarHitErrorMeter : HitErrorMeter, ISkinnableComponent
|
||||
public class BarHitErrorMeter : HitErrorMeter
|
||||
{
|
||||
private readonly Anchor alignment;
|
||||
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// <summary>
|
||||
/// Uses the 'x' symbol and has a pop-out effect while rolling over.
|
||||
/// </summary>
|
||||
public class LegacyComboCounter : CompositeDrawable, ISkinnableComponent
|
||||
public class LegacyComboCounter : CompositeDrawable, ISkinnableDrawable
|
||||
{
|
||||
public Bindable<int> Current { get; } = new BindableInt { MinValue = 0, };
|
||||
|
||||
@ -84,13 +84,13 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
InternalChildren = new[]
|
||||
{
|
||||
popOutCount = new LegacySpriteText(skin, LegacyFont.Combo)
|
||||
popOutCount = new LegacySpriteText(LegacyFont.Combo)
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(0.05f),
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
displayedCountSpriteText = new LegacySpriteText(skin, LegacyFont.Combo)
|
||||
displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
|
||||
{
|
||||
Alpha = 0,
|
||||
},
|
||||
|
74
osu.Game/Screens/Play/HUD/SkinnableInfo.cs
Normal file
74
osu.Game/Screens/Play/HUD/SkinnableInfo.cs
Normal file
@ -0,0 +1,74 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialised information governing custom changes to an <see cref="ISkinnableDrawable"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SkinnableInfo : IJsonSerializable
|
||||
{
|
||||
public Type Type { get; set; }
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public float Rotation { get; set; }
|
||||
|
||||
public Vector2 Scale { get; set; }
|
||||
|
||||
public Anchor Anchor { get; set; }
|
||||
|
||||
public Anchor Origin { get; set; }
|
||||
|
||||
public List<SkinnableInfo> Children { get; } = new List<SkinnableInfo>();
|
||||
|
||||
[JsonConstructor]
|
||||
public SkinnableInfo()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance populating all attributes from the provided drawable.
|
||||
/// </summary>
|
||||
/// <param name="component">The drawable which attributes should be sourced from.</param>
|
||||
public SkinnableInfo(Drawable component)
|
||||
{
|
||||
Type = component.GetType();
|
||||
|
||||
Position = component.Position;
|
||||
Rotation = component.Rotation;
|
||||
Scale = component.Scale;
|
||||
Anchor = component.Anchor;
|
||||
Origin = component.Origin;
|
||||
|
||||
if (component is Container<Drawable> container)
|
||||
{
|
||||
foreach (var child in container.OfType<ISkinnableDrawable>().OfType<Drawable>())
|
||||
Children.Add(child.CreateSkinnableInfo());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an instance of the drawable with all attributes applied.
|
||||
/// </summary>
|
||||
/// <returns>The new instance.</returns>
|
||||
public Drawable CreateInstance()
|
||||
{
|
||||
Drawable d = (Drawable)Activator.CreateInstance(Type);
|
||||
d.ApplySkinnableInfo(this);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -22,7 +24,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;
|
||||
|
||||
@ -34,8 +36,6 @@ namespace osu.Game.Screens.Play
|
||||
public float TopScoringElementsHeight { get; private set; }
|
||||
|
||||
public readonly KeyCounterDisplay KeyCounter;
|
||||
public readonly SkinnableScoreCounter ScoreCounter;
|
||||
public readonly SkinnableAccuracyCounter AccuracyCounter;
|
||||
public readonly SongProgress Progress;
|
||||
public readonly ModDisplay ModDisplay;
|
||||
public readonly HoldForMenuButton HoldToQuit;
|
||||
@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private bool holdingForHUD;
|
||||
|
||||
private readonly SkinnableTargetContainer mainComponents;
|
||||
|
||||
private IEnumerable<Drawable> hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements };
|
||||
|
||||
public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
|
||||
@ -95,11 +97,19 @@ namespace osu.Game.Screens.Play
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
CreateHealthDisplay(),
|
||||
AccuracyCounter = CreateAccuracyCounter(),
|
||||
ScoreCounter = CreateScoreCounter(),
|
||||
CreateComboCounter(),
|
||||
CreateHitErrorDisplayOverlay(),
|
||||
mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// still need to be migrated; a bit more involved.
|
||||
new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -196,11 +206,25 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// HACK: for now align with the accuracy counter.
|
||||
// this is done for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
|
||||
// it only works with the default skin due to padding offsetting it *just enough* to coexist.
|
||||
topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y;
|
||||
Vector2 lowestScreenSpace = Vector2.Zero;
|
||||
|
||||
// LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
|
||||
foreach (var element in mainComponents.Components.Cast<Drawable>())
|
||||
{
|
||||
// for now align top-right components with the bottom-edge of the lowest top-anchored hud element.
|
||||
if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X))
|
||||
continue;
|
||||
|
||||
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
|
||||
if (element is LegacyHealthDisplay)
|
||||
continue;
|
||||
|
||||
var bottomRight = element.ScreenSpaceDrawQuad.BottomRight;
|
||||
if (bottomRight.Y > lowestScreenSpace.Y)
|
||||
lowestScreenSpace = bottomRight;
|
||||
}
|
||||
|
||||
topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y;
|
||||
bottomRightElements.Y = -Progress.Height;
|
||||
}
|
||||
|
||||
@ -261,48 +285,38 @@ namespace osu.Game.Screens.Play
|
||||
Progress.BindDrawableRuleset(drawableRuleset);
|
||||
}
|
||||
|
||||
protected SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter();
|
||||
|
||||
protected SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter();
|
||||
|
||||
protected SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter();
|
||||
|
||||
protected SkinnableHealthDisplay CreateHealthDisplay() => new SkinnableHealthDisplay();
|
||||
|
||||
protected virtual FailingLayer CreateFailingLayer() => new FailingLayer
|
||||
protected FailingLayer CreateFailingLayer() => new FailingLayer
|
||||
{
|
||||
ShowHealth = { BindTarget = ShowHealthbar }
|
||||
};
|
||||
|
||||
protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
|
||||
protected KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
};
|
||||
|
||||
protected virtual SongProgress CreateProgress() => new SongProgress
|
||||
protected SongProgress CreateProgress() => new SongProgress
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
};
|
||||
|
||||
protected virtual HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
|
||||
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
};
|
||||
|
||||
protected virtual ModDisplay CreateModsContainer() => new ModDisplay
|
||||
protected ModDisplay CreateModsContainer() => new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(drawableRuleset?.FirstAvailableHitWindows);
|
||||
|
||||
protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
|
||||
protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
|
@ -14,7 +14,6 @@ using osu.Framework.Timing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@ -181,12 +180,12 @@ namespace osu.Game.Screens.Play
|
||||
info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In);
|
||||
}
|
||||
|
||||
public class SongProgressDisplay : Container, ISkinnableComponent
|
||||
public class SongProgressDisplay : Container
|
||||
{
|
||||
public SongProgressDisplay()
|
||||
{
|
||||
// TODO: move actual implementation into this.
|
||||
// exists for skin customisation purposes.
|
||||
// exists for skin customisation purposes (interface should be added to this container).
|
||||
|
||||
Masking = true;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
@ -87,9 +87,9 @@ namespace osu.Game.Screens
|
||||
private static Color4 getColourFor(object type)
|
||||
{
|
||||
int hash = type.GetHashCode();
|
||||
byte r = (byte)Math.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255);
|
||||
byte g = (byte)Math.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255);
|
||||
byte b = (byte)Math.Clamp((hash & 0x0000FF) * 0.8f, 20, 255);
|
||||
byte r = (byte)Math.Clamp(((hash & 0xFF0000) >> 16) * 2, 128, 255);
|
||||
byte g = (byte)Math.Clamp(((hash & 0x00FF00) >> 8) * 2, 128, 255);
|
||||
byte b = (byte)Math.Clamp((hash & 0x0000FF) * 2, 128, 255);
|
||||
return new Color4(r, g, b, 255);
|
||||
}
|
||||
|
||||
@ -109,10 +109,10 @@ namespace osu.Game.Screens
|
||||
|
||||
private readonly Container boxContainer;
|
||||
|
||||
public UnderConstructionMessage(string name)
|
||||
public UnderConstructionMessage(string name, string description = "is not yet ready for use!")
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Size = new Vector2(0.3f);
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
@ -124,7 +124,7 @@ namespace osu.Game.Screens
|
||||
{
|
||||
CornerRadius = 20,
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
@ -133,15 +133,15 @@ namespace osu.Game.Screens
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
||||
Colour = colour,
|
||||
Alpha = 0.2f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Colour = colour.Darken(0.8f),
|
||||
Alpha = 0.8f,
|
||||
},
|
||||
TextContainer = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding(20),
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -157,14 +157,14 @@ namespace osu.Game.Screens
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = name,
|
||||
Colour = colour.Lighten(0.8f),
|
||||
Colour = colour,
|
||||
Font = OsuFont.GetFont(size: 36),
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = "is not yet ready for use!",
|
||||
Text = description,
|
||||
Font = OsuFont.GetFont(size: 20),
|
||||
},
|
||||
new OsuSpriteText
|
||||
|
@ -1,7 +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 JetBrains.Annotations;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -10,7 +12,13 @@ namespace osu.Game.Skinning
|
||||
public class DefaultLegacySkin : LegacySkin
|
||||
{
|
||||
public DefaultLegacySkin(IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
||||
: base(Info, storage, resources, string.Empty)
|
||||
: this(Info, storage, resources)
|
||||
{
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||
public DefaultLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
||||
: base(skin, storage, resources, string.Empty)
|
||||
{
|
||||
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
||||
Configuration.AddComboColours(
|
||||
@ -27,7 +35,8 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon.
|
||||
Name = "osu!classic",
|
||||
Creator = "team osu!"
|
||||
Creator = "team osu!",
|
||||
InstantiationInfo = typeof(DefaultLegacySkin).GetInvariantInstantiationInfo()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,30 +2,102 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class DefaultSkin : Skin
|
||||
{
|
||||
public DefaultSkin()
|
||||
: base(SkinInfo.Default)
|
||||
public DefaultSkin(IStorageResourceProvider resources)
|
||||
: this(SkinInfo.Default, resources)
|
||||
{
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||
public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||
: base(skin, resources)
|
||||
{
|
||||
Configuration = new DefaultSkinConfiguration();
|
||||
}
|
||||
|
||||
public override Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||
|
||||
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||
|
||||
public override ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||
|
||||
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:
|
||||
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
|
||||
{
|
||||
var score = container.OfType<DefaultScoreCounter>().FirstOrDefault();
|
||||
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
|
||||
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
||||
|
||||
if (score != null)
|
||||
{
|
||||
score.Anchor = Anchor.TopCentre;
|
||||
score.Origin = Anchor.TopCentre;
|
||||
|
||||
// elements default to beneath the health bar
|
||||
const float vertical_offset = 30;
|
||||
|
||||
const float horizontal_padding = 20;
|
||||
|
||||
score.Position = new Vector2(0, vertical_offset);
|
||||
|
||||
if (accuracy != null)
|
||||
{
|
||||
accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5);
|
||||
accuracy.Origin = Anchor.TopRight;
|
||||
accuracy.Anchor = Anchor.TopCentre;
|
||||
}
|
||||
|
||||
if (combo != null)
|
||||
{
|
||||
combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5);
|
||||
combo.Anchor = Anchor.TopCentre;
|
||||
}
|
||||
}
|
||||
})
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DefaultComboCounter(),
|
||||
new DefaultScoreCounter(),
|
||||
new DefaultAccuracyCounter(),
|
||||
new DefaultHealthDisplay(),
|
||||
}
|
||||
};
|
||||
|
||||
return skinnableTargetWrapper;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
switch (lookup)
|
||||
|
@ -13,7 +13,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinBlueprint : SelectionBlueprint<ISkinnableComponent>
|
||||
public class SkinBlueprint : SelectionBlueprint<ISkinnableDrawable>
|
||||
{
|
||||
private Container box;
|
||||
|
||||
@ -26,7 +26,7 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
protected override bool ShouldBeAlive => (drawable.IsAlive && Item.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
|
||||
|
||||
public SkinBlueprint(ISkinnableComponent component)
|
||||
public SkinBlueprint(ISkinnableDrawable component)
|
||||
: base(component)
|
||||
{
|
||||
}
|
||||
|
@ -1,42 +1,97 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinBlueprintContainer : BlueprintContainer<ISkinnableComponent>
|
||||
public class SkinBlueprintContainer : BlueprintContainer<ISkinnableDrawable>
|
||||
{
|
||||
private readonly Drawable target;
|
||||
|
||||
private readonly List<BindableList<ISkinnableDrawable>> targetComponents = new List<BindableList<ISkinnableDrawable>>();
|
||||
|
||||
public SkinBlueprintContainer(Drawable target)
|
||||
{
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(SkinEditor editor)
|
||||
{
|
||||
SelectedItems.BindTo(editor.SelectedComponents);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
checkForComponents();
|
||||
// track each target container on the current screen.
|
||||
var targetContainers = target.ChildrenOfType<ISkinnableTarget>().ToArray();
|
||||
|
||||
if (targetContainers.Length == 0)
|
||||
{
|
||||
var targetScreen = target.ChildrenOfType<Screen>().LastOrDefault()?.GetType().Name ?? "this screen";
|
||||
|
||||
AddInternal(new ScreenWhiteBox.UnderConstructionMessage(targetScreen, "doesn't support skin customisation just yet."));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var targetContainer in targetContainers)
|
||||
{
|
||||
var bindableList = new BindableList<ISkinnableDrawable> { BindTarget = targetContainer.Components };
|
||||
bindableList.BindCollectionChanged(componentsChanged, true);
|
||||
|
||||
targetComponents.Add(bindableList);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForComponents()
|
||||
private void componentsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
foreach (var c in target.ChildrenOfType<ISkinnableComponent>().ToArray()) AddBlueprintFor(c);
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var item in e.NewItems.Cast<ISkinnableDrawable>())
|
||||
AddBlueprintFor(item);
|
||||
break;
|
||||
|
||||
// We'd hope to eventually be running this in a more sensible way, but this handles situations where new drawables become present (ie. during ongoing gameplay)
|
||||
// or when drawables in the target are loaded asynchronously and may not be immediately available when this BlueprintContainer is loaded.
|
||||
Scheduler.AddDelayed(checkForComponents, 1000);
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
foreach (var item in e.OldItems.Cast<ISkinnableDrawable>())
|
||||
RemoveBlueprintFor(item);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (var item in e.OldItems.Cast<ISkinnableDrawable>())
|
||||
RemoveBlueprintFor(item);
|
||||
|
||||
foreach (var item in e.NewItems.Cast<ISkinnableDrawable>())
|
||||
AddBlueprintFor(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override SelectionHandler<ISkinnableComponent> CreateSelectionHandler() => new SkinSelectionHandler();
|
||||
protected override void AddBlueprintFor(ISkinnableDrawable item)
|
||||
{
|
||||
if (!item.IsEditable)
|
||||
return;
|
||||
|
||||
protected override SelectionBlueprint<ISkinnableComponent> CreateBlueprintFor(ISkinnableComponent component)
|
||||
base.AddBlueprintFor(item);
|
||||
}
|
||||
|
||||
protected override SelectionHandler<ISkinnableDrawable> CreateSelectionHandler() => new SkinSelectionHandler();
|
||||
|
||||
protected override SelectionBlueprint<ISkinnableDrawable> CreateBlueprintFor(ISkinnableDrawable component)
|
||||
=> new SkinBlueprint(component);
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Skinning.Editor
|
||||
Spacing = new Vector2(20)
|
||||
};
|
||||
|
||||
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableComponent).IsAssignableFrom(t)).ToArray();
|
||||
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)).ToArray();
|
||||
|
||||
foreach (var type in skinnableTypes)
|
||||
{
|
||||
@ -78,6 +78,9 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
Debug.Assert(instance != null);
|
||||
|
||||
if (!((ISkinnableDrawable)instance).IsEditable)
|
||||
return null;
|
||||
|
||||
return new ToolboxComponentButton(instance);
|
||||
}
|
||||
catch
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -11,28 +12,41 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
[Cached(typeof(SkinEditor))]
|
||||
public class SkinEditor : FocusedOverlayContainer
|
||||
{
|
||||
public const double TRANSITION_DURATION = 500;
|
||||
|
||||
private readonly Drawable target;
|
||||
|
||||
private OsuTextFlowContainer headerText;
|
||||
public readonly BindableList<ISkinnableDrawable> SelectedComponents = new BindableList<ISkinnableDrawable>();
|
||||
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
public SkinEditor(Drawable target)
|
||||
private readonly Drawable targetScreen;
|
||||
|
||||
private OsuTextFlowContainer headerText;
|
||||
|
||||
private Bindable<Skin> currentSkin;
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public SkinEditor(Drawable targetScreen)
|
||||
{
|
||||
this.target = target;
|
||||
this.targetScreen = targetScreen;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new OsuContextMenuContainer
|
||||
{
|
||||
@ -47,37 +61,157 @@ namespace osu.Game.Skinning.Editor
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X
|
||||
},
|
||||
new SkinBlueprintContainer(target),
|
||||
new SkinComponentToolbox(600)
|
||||
new GridContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RequestPlacement = placeComponent
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new SkinComponentToolbox(600)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RequestPlacement = placeComponent
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SkinBlueprintContainer(targetScreen),
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = 10,
|
||||
Left = 10,
|
||||
},
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Bottom = 10,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TriangleButton
|
||||
{
|
||||
Text = "Save Changes",
|
||||
Width = 140,
|
||||
Action = save,
|
||||
},
|
||||
new DangerousTriangleButton
|
||||
{
|
||||
Text = "Revert to default",
|
||||
Width = 140,
|
||||
Action = revert,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
headerText.AddParagraph("Skin editor (preview)", cp => cp.Font = OsuFont.Default.With(size: 24));
|
||||
headerText.AddParagraph("This is a preview of what is to come. Changes are lost on changing screens.", cp =>
|
||||
{
|
||||
cp.Font = OsuFont.Default.With(size: 12);
|
||||
cp.Colour = colours.Yellow;
|
||||
});
|
||||
}
|
||||
|
||||
private void placeComponent(Type type)
|
||||
{
|
||||
var instance = (Drawable)Activator.CreateInstance(type);
|
||||
|
||||
var targetContainer = target.ChildrenOfType<IDefaultSkinnableTarget>().FirstOrDefault();
|
||||
|
||||
targetContainer?.Add(instance);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Show();
|
||||
|
||||
// as long as the skin editor is loaded, let's make sure we can modify the current skin.
|
||||
currentSkin = skins.CurrentSkin.GetBoundCopy();
|
||||
|
||||
// schedule ensures this only happens when the skin editor is visible.
|
||||
// also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types).
|
||||
// probably something which will be factored out in a future database refactor so not too concerning for now.
|
||||
currentSkin.BindValueChanged(skin => Scheduler.AddOnce(skinChanged), true);
|
||||
}
|
||||
|
||||
private void skinChanged()
|
||||
{
|
||||
headerText.Clear();
|
||||
|
||||
headerText.AddParagraph("Skin editor", cp => cp.Font = OsuFont.Default.With(size: 24));
|
||||
headerText.NewParagraph();
|
||||
headerText.AddText("Currently editing ", cp =>
|
||||
{
|
||||
cp.Font = OsuFont.Default.With(size: 12);
|
||||
cp.Colour = colours.Yellow;
|
||||
});
|
||||
|
||||
headerText.AddText($"{currentSkin.Value.SkinInfo}", cp =>
|
||||
{
|
||||
cp.Font = OsuFont.Default.With(size: 12, weight: FontWeight.Bold);
|
||||
cp.Colour = colours.Yellow;
|
||||
});
|
||||
|
||||
skins.EnsureMutableSkin();
|
||||
}
|
||||
|
||||
private void placeComponent(Type type)
|
||||
{
|
||||
var targetContainer = getTarget(SkinnableTarget.MainHUDComponents);
|
||||
|
||||
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.
|
||||
drawableComponent.Origin = Anchor.TopCentre;
|
||||
drawableComponent.Anchor = Anchor.TopCentre;
|
||||
drawableComponent.Y = targetContainer.DrawSize.Y / 2;
|
||||
|
||||
targetContainer.Add(component);
|
||||
|
||||
SelectedComponents.Clear();
|
||||
SelectedComponents.Add(component);
|
||||
}
|
||||
|
||||
private ISkinnableTarget getTarget(SkinnableTarget target)
|
||||
{
|
||||
return targetScreen.ChildrenOfType<ISkinnableTarget>().FirstOrDefault(c => c.Target == target);
|
||||
}
|
||||
|
||||
private void revert()
|
||||
{
|
||||
SkinnableTargetContainer[] targetContainers = targetScreen.ChildrenOfType<SkinnableTargetContainer>().ToArray();
|
||||
|
||||
foreach (var t in targetContainers)
|
||||
{
|
||||
currentSkin.Value.ResetDrawableTarget(t);
|
||||
|
||||
// add back default components
|
||||
getTarget(t.Target).Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private void save()
|
||||
{
|
||||
SkinnableTargetContainer[] targetContainers = targetScreen.ChildrenOfType<SkinnableTargetContainer>().ToArray();
|
||||
|
||||
foreach (var t in targetContainers)
|
||||
currentSkin.Value.UpdateDrawableTarget(t);
|
||||
|
||||
skins.Save(skins.CurrentSkin.Value);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Skinning.Editor
|
||||
target.RelativePositionAxes = Axes.Both;
|
||||
|
||||
target.ScaleTo(VISIBLE_TARGET_SCALE, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
|
||||
target.MoveToX(0.1f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
|
||||
target.MoveToX(0.095f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinSelectionHandler : SelectionHandler<ISkinnableComponent>
|
||||
public class SkinSelectionHandler : SelectionHandler<ISkinnableDrawable>
|
||||
{
|
||||
public override bool HandleRotation(float angle)
|
||||
{
|
||||
@ -36,7 +36,7 @@ namespace osu.Game.Skinning.Editor
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent<ISkinnableComponent> moveEvent)
|
||||
public override bool HandleMovement(MoveSelectionEvent<ISkinnableDrawable> moveEvent)
|
||||
{
|
||||
foreach (var c in SelectedBlueprints)
|
||||
{
|
||||
@ -57,7 +57,7 @@ namespace osu.Game.Skinning.Editor
|
||||
SelectionBox.CanReverse = false;
|
||||
}
|
||||
|
||||
protected override void DeleteItems(IEnumerable<ISkinnableComponent> items)
|
||||
protected override void DeleteItems(IEnumerable<ISkinnableDrawable> items)
|
||||
{
|
||||
foreach (var i in items)
|
||||
{
|
||||
@ -66,17 +66,22 @@ namespace osu.Game.Skinning.Editor
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<ISkinnableComponent>> selection)
|
||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<ISkinnableDrawable>> selection)
|
||||
{
|
||||
yield return new OsuMenuItem("Anchor")
|
||||
{
|
||||
Items = createAnchorItems().ToArray()
|
||||
Items = createAnchorItems(d => d.Anchor, applyAnchor).ToArray()
|
||||
};
|
||||
|
||||
yield return new OsuMenuItem("Origin")
|
||||
{
|
||||
Items = createAnchorItems(d => d.Origin, applyOrigin).ToArray()
|
||||
};
|
||||
|
||||
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
||||
yield return item;
|
||||
|
||||
IEnumerable<AnchorMenuItem> createAnchorItems()
|
||||
IEnumerable<AnchorMenuItem> createAnchorItems(Func<Drawable, Anchor> checkFunction, Action<Anchor> applyFunction)
|
||||
{
|
||||
var displayableAnchors = new[]
|
||||
{
|
||||
@ -93,14 +98,20 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
return displayableAnchors.Select(a =>
|
||||
{
|
||||
return new AnchorMenuItem(a, selection, _ => applyAnchor(a))
|
||||
return new AnchorMenuItem(a, selection, _ => applyFunction(a))
|
||||
{
|
||||
State = { Value = GetStateFromSelection(selection, c => ((Drawable)c.Item).Anchor == a) }
|
||||
State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == a) }
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void applyOrigin(Anchor anchor)
|
||||
{
|
||||
foreach (var item in SelectedItems)
|
||||
((Drawable)item).Origin = anchor;
|
||||
}
|
||||
|
||||
private void applyAnchor(Anchor anchor)
|
||||
{
|
||||
foreach (var item in SelectedItems)
|
||||
@ -120,7 +131,7 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
public class AnchorMenuItem : TernaryStateMenuItem
|
||||
{
|
||||
public AnchorMenuItem(Anchor anchor, IEnumerable<SelectionBlueprint<ISkinnableComponent>> selection, Action<TernaryState> action)
|
||||
public AnchorMenuItem(Anchor anchor, IEnumerable<SelectionBlueprint<ISkinnableDrawable>> selection, Action<TernaryState> action)
|
||||
: base(anchor.ToString(), getNextState, MenuItemType.Standard, action)
|
||||
{
|
||||
}
|
||||
|
@ -8,7 +8,11 @@ namespace osu.Game.Skinning
|
||||
/// <summary>
|
||||
/// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications.
|
||||
/// </summary>
|
||||
public interface ISkinnableComponent : IDrawable
|
||||
public interface ISkinnableDrawable : IDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this component should be editable by an end user.
|
||||
/// </summary>
|
||||
bool IsEditable => true;
|
||||
}
|
||||
}
|
@ -1,15 +1,44 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes a container which can house <see cref="ISkinnableComponent"/>s.
|
||||
/// Denotes a container which can house <see cref="ISkinnableDrawable"/>s.
|
||||
/// </summary>
|
||||
public interface ISkinnableTarget : IContainerCollection<Drawable>
|
||||
public interface ISkinnableTarget : IDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The definition of this target.
|
||||
/// </summary>
|
||||
SkinnableTarget Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A bindable list of components which are being tracked by this skinnable target.
|
||||
/// </summary>
|
||||
IBindableList<ISkinnableDrawable> Components { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Serialise all children as <see cref="SkinnableInfo"/>.
|
||||
/// </summary>
|
||||
/// <returns>The serialised content.</returns>
|
||||
IEnumerable<SkinnableInfo> CreateSkinnableInfo() => Components.Select(d => ((Drawable)d).CreateSkinnableInfo());
|
||||
|
||||
/// <summary>
|
||||
/// Reload this target from the current skin.
|
||||
/// </summary>
|
||||
void Reload();
|
||||
|
||||
/// <summary>
|
||||
/// Add the provided item to this target.
|
||||
/// </summary>
|
||||
void Add(ISkinnableDrawable drawable);
|
||||
}
|
||||
}
|
||||
|
@ -10,39 +10,24 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableComponent
|
||||
public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable
|
||||
{
|
||||
private readonly ISkin skin;
|
||||
|
||||
public LegacyAccuracyCounter(ISkin skin)
|
||||
public LegacyAccuracyCounter()
|
||||
{
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
|
||||
Scale = new Vector2(0.6f);
|
||||
Margin = new MarginPadding(10);
|
||||
|
||||
this.skin = skin;
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private HUDOverlay hud { get; set; }
|
||||
|
||||
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, LegacyFont.Score)
|
||||
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
};
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (hud?.ScoreCounter.Drawable is LegacyScoreCounter score)
|
||||
{
|
||||
// for now align with the score counter. eventually this will be user customisable.
|
||||
Y = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,13 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyHealthDisplay : HealthDisplay
|
||||
public class LegacyHealthDisplay : HealthDisplay, ISkinnableDrawable
|
||||
{
|
||||
private const double epic_cutoff = 0.5;
|
||||
|
||||
private readonly Skin skin;
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; }
|
||||
|
||||
private LegacyHealthPiece fill;
|
||||
private LegacyHealthPiece marker;
|
||||
|
||||
@ -28,11 +30,6 @@ namespace osu.Game.Skinning
|
||||
|
||||
private bool isNewStyle;
|
||||
|
||||
public LegacyHealthDisplay(Skin skin)
|
||||
{
|
||||
this.skin = skin;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -79,7 +76,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
protected override void Flash(JudgementResult result) => marker.Flash(result);
|
||||
|
||||
private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}");
|
||||
private static Texture getTexture(ISkinSource skin, string name) => skin.GetTexture($"scorebar-{name}");
|
||||
|
||||
private static Color4 getFillColour(double hp)
|
||||
{
|
||||
@ -98,7 +95,7 @@ namespace osu.Game.Skinning
|
||||
private readonly Texture dangerTexture;
|
||||
private readonly Texture superDangerTexture;
|
||||
|
||||
public LegacyOldStyleMarker(Skin skin)
|
||||
public LegacyOldStyleMarker(ISkinSource skin)
|
||||
{
|
||||
normalTexture = getTexture(skin, "ki");
|
||||
dangerTexture = getTexture(skin, "kidanger");
|
||||
@ -129,9 +126,9 @@ namespace osu.Game.Skinning
|
||||
|
||||
public class LegacyNewStyleMarker : LegacyMarker
|
||||
{
|
||||
private readonly Skin skin;
|
||||
private readonly ISkinSource skin;
|
||||
|
||||
public LegacyNewStyleMarker(Skin skin)
|
||||
public LegacyNewStyleMarker(ISkinSource skin)
|
||||
{
|
||||
this.skin = skin;
|
||||
}
|
||||
@ -153,7 +150,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
internal class LegacyOldStyleFill : LegacyHealthPiece
|
||||
{
|
||||
public LegacyOldStyleFill(Skin skin)
|
||||
public LegacyOldStyleFill(ISkinSource skin)
|
||||
{
|
||||
// required for sizing correctly..
|
||||
var firstFrame = getTexture(skin, "colour-0");
|
||||
@ -176,7 +173,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
internal class LegacyNewStyleFill : LegacyHealthPiece
|
||||
{
|
||||
public LegacyNewStyleFill(Skin skin)
|
||||
public LegacyNewStyleFill(ISkinSource skin)
|
||||
{
|
||||
InternalChild = new Sprite
|
||||
{
|
||||
|
@ -12,7 +12,6 @@ namespace osu.Game.Skinning
|
||||
/// </summary>
|
||||
public class LegacyRollingCounter : RollingCounter<int>
|
||||
{
|
||||
private readonly ISkin skin;
|
||||
private readonly LegacyFont font;
|
||||
|
||||
protected override bool IsRollingProportional => true;
|
||||
@ -20,11 +19,9 @@ namespace osu.Game.Skinning
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="LegacyRollingCounter"/>.
|
||||
/// </summary>
|
||||
/// <param name="skin">The <see cref="ISkin"/> from which to get counter number sprites.</param>
|
||||
/// <param name="font">The legacy font to use for the counter.</param>
|
||||
public LegacyRollingCounter(ISkin skin, LegacyFont font)
|
||||
public LegacyRollingCounter(LegacyFont font)
|
||||
{
|
||||
this.skin = skin;
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
@ -33,6 +30,6 @@ namespace osu.Game.Skinning
|
||||
return Math.Abs(newValue - currentValue) * 75.0;
|
||||
}
|
||||
|
||||
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, font);
|
||||
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(font);
|
||||
}
|
||||
}
|
||||
|
@ -8,26 +8,22 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyScoreCounter : GameplayScoreCounter, ISkinnableComponent
|
||||
public class LegacyScoreCounter : GameplayScoreCounter, ISkinnableDrawable
|
||||
{
|
||||
private readonly ISkin skin;
|
||||
|
||||
protected override double RollingDuration => 1000;
|
||||
protected override Easing RollingEasing => Easing.Out;
|
||||
|
||||
public LegacyScoreCounter(ISkin skin)
|
||||
public LegacyScoreCounter()
|
||||
: base(6)
|
||||
{
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
|
||||
this.skin = skin;
|
||||
|
||||
Scale = new Vector2(0.96f);
|
||||
Margin = new MarginPadding(10);
|
||||
}
|
||||
|
||||
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, LegacyFont.Score)
|
||||
protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
|
@ -52,13 +52,14 @@ namespace osu.Game.Skinning
|
||||
|
||||
private readonly Dictionary<int, LegacyManiaSkinConfiguration> maniaConfigurations = new Dictionary<int, LegacyManiaSkinConfiguration>();
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||
public LegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||
: this(skin, new LegacySkinResourceStore<SkinFileInfo>(skin, resources.Files), resources, "skin.ini")
|
||||
{
|
||||
}
|
||||
|
||||
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 +322,42 @@ 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:
|
||||
|
||||
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
|
||||
{
|
||||
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
|
||||
var accuracy = container.OfType<GameplayAccuracyCounter>().FirstOrDefault();
|
||||
|
||||
if (score != null && accuracy != null)
|
||||
{
|
||||
accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y;
|
||||
}
|
||||
})
|
||||
{
|
||||
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 skinnableTargetWrapper;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case HUDSkinComponent hudComponent:
|
||||
{
|
||||
if (!this.HasFont(LegacyFont.Score))
|
||||
@ -334,13 +369,13 @@ namespace osu.Game.Skinning
|
||||
return new LegacyComboCounter();
|
||||
|
||||
case HUDSkinComponents.ScoreCounter:
|
||||
return new LegacyScoreCounter(this);
|
||||
return new LegacyScoreCounter();
|
||||
|
||||
case HUDSkinComponents.AccuracyCounter:
|
||||
return new LegacyAccuracyCounter(this);
|
||||
return new LegacyAccuracyCounter();
|
||||
|
||||
case HUDSkinComponents.HealthDisplay:
|
||||
return new LegacyHealthDisplay(this);
|
||||
return new LegacyHealthDisplay();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Text;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -9,19 +10,26 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacySpriteText : OsuSpriteText
|
||||
public sealed class LegacySpriteText : OsuSpriteText
|
||||
{
|
||||
private readonly LegacyGlyphStore glyphStore;
|
||||
private readonly LegacyFont font;
|
||||
|
||||
private LegacyGlyphStore glyphStore;
|
||||
|
||||
protected override char FixedWidthReferenceCharacter => '5';
|
||||
|
||||
protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' };
|
||||
|
||||
public LegacySpriteText(ISkin skin, LegacyFont font)
|
||||
public LegacySpriteText(LegacyFont font)
|
||||
{
|
||||
this.font = font;
|
||||
Shadow = false;
|
||||
UseFullGlyphHeight = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: true);
|
||||
Spacing = new Vector2(-skin.GetFontOverlap(font), 0);
|
||||
|
||||
|
@ -2,12 +2,18 @@
|
||||
// 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.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
|
||||
{
|
||||
@ -17,7 +23,9 @@ namespace osu.Game.Skinning
|
||||
|
||||
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 +35,65 @@ 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;
|
||||
|
||||
// we may want to move this to some kind of async operation in the future.
|
||||
foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget)))
|
||||
{
|
||||
string filename = $"{skinnableTarget}.json";
|
||||
|
||||
// skininfo files may be null for default skin.
|
||||
var fileInfo = SkinInfo.Files?.FirstOrDefault(f => f.Filename == filename);
|
||||
|
||||
if (fileInfo == null)
|
||||
continue;
|
||||
|
||||
var bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath);
|
||||
|
||||
if (bytes == null)
|
||||
continue;
|
||||
|
||||
string jsonContent = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
DrawableComponentInfo[skinnableTarget] = JsonConvert.DeserializeObject<IEnumerable<SkinnableInfo>>(jsonContent).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all stored customisations for the provided target.
|
||||
/// </summary>
|
||||
/// <param name="targetContainer">The target container to reset.</param>
|
||||
public void ResetDrawableTarget(ISkinnableTarget targetContainer)
|
||||
{
|
||||
DrawableComponentInfo.Remove(targetContainer.Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update serialised information for the provided target.
|
||||
/// </summary>
|
||||
/// <param name="targetContainer">The target container to serialise to this skin.</param>
|
||||
public void UpdateDrawableTarget(ISkinnableTarget targetContainer)
|
||||
{
|
||||
DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray();
|
||||
}
|
||||
|
||||
public virtual Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
switch (component)
|
||||
{
|
||||
case SkinnableTargetComponent target:
|
||||
if (!DrawableComponentInfo.TryGetValue(target.Target, out var skinnableInfo))
|
||||
return null;
|
||||
|
||||
return new SkinnableTargetComponentsContainer
|
||||
{
|
||||
ChildrenEnumerable = skinnableInfo.Select(i => i.CreateInstance())
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#region Disposal
|
||||
|
@ -3,8 +3,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
@ -22,7 +25,22 @@ namespace osu.Game.Skinning
|
||||
|
||||
public string Creator { get; set; }
|
||||
|
||||
public List<SkinFileInfo> Files { get; set; }
|
||||
public string InstantiationInfo { get; set; }
|
||||
|
||||
public virtual Skin CreateInstance(IResourceStore<byte[]> legacyDefaultResources, IStorageResourceProvider resources)
|
||||
{
|
||||
var type = string.IsNullOrEmpty(InstantiationInfo)
|
||||
// handle the case of skins imported before InstantiationInfo was added.
|
||||
? typeof(LegacySkin)
|
||||
: Type.GetType(InstantiationInfo);
|
||||
|
||||
if (type == typeof(DefaultLegacySkin))
|
||||
return (Skin)Activator.CreateInstance(type, this, legacyDefaultResources, resources);
|
||||
|
||||
return (Skin)Activator.CreateInstance(type, this, resources);
|
||||
}
|
||||
|
||||
public List<SkinFileInfo> Files { get; set; } = new List<SkinFileInfo>();
|
||||
|
||||
public List<DatabasedSetting> Settings { get; set; }
|
||||
|
||||
@ -32,7 +50,8 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
ID = DEFAULT_SKIN,
|
||||
Name = "osu!lazer",
|
||||
Creator = "team osu!"
|
||||
Creator = "team osu!",
|
||||
InstantiationInfo = typeof(DefaultSkin).GetInvariantInstantiationInfo()
|
||||
};
|
||||
|
||||
public bool Equals(SkinInfo other) => other != null && ID == other.ID;
|
||||
|
@ -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;
|
||||
@ -22,6 +24,7 @@ using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
|
||||
@ -36,7 +39,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
private readonly IResourceStore<byte[]> legacyDefaultResources;
|
||||
|
||||
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin());
|
||||
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin(null));
|
||||
public readonly Bindable<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
|
||||
|
||||
public override IEnumerable<string> HandledExtensions => new[] { ".osk" };
|
||||
@ -105,7 +108,7 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
// we need to populate early to create a hash based off skin.ini contents
|
||||
if (item.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true)
|
||||
populateMetadata(item);
|
||||
populateMetadata(item, GetSkin(item));
|
||||
|
||||
if (item.Creator != null && item.Creator != unknown_creator_string)
|
||||
{
|
||||
@ -122,18 +125,20 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
await base.Populate(model, archive, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var instance = GetSkin(model);
|
||||
|
||||
model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo();
|
||||
|
||||
if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true)
|
||||
populateMetadata(model);
|
||||
populateMetadata(model, instance);
|
||||
}
|
||||
|
||||
private void populateMetadata(SkinInfo item)
|
||||
private void populateMetadata(SkinInfo item, Skin instance)
|
||||
{
|
||||
Skin reference = GetSkin(item);
|
||||
|
||||
if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name))
|
||||
if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name))
|
||||
{
|
||||
item.Name = reference.Configuration.SkinInfo.Name;
|
||||
item.Creator = reference.Configuration.SkinInfo.Creator;
|
||||
item.Name = instance.Configuration.SkinInfo.Name;
|
||||
item.Creator = instance.Configuration.SkinInfo.Creator;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -147,15 +152,48 @@ namespace osu.Game.Skinning
|
||||
/// </summary>
|
||||
/// <param name="skinInfo">The skin to lookup.</param>
|
||||
/// <returns>A <see cref="Skin"/> instance correlating to the provided <see cref="SkinInfo"/>.</returns>
|
||||
public Skin GetSkin(SkinInfo skinInfo)
|
||||
public Skin GetSkin(SkinInfo skinInfo) => skinInfo.CreateInstance(legacyDefaultResources, this);
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the current skin is in a state it can accept user modifications.
|
||||
/// This will create a copy of any internal skin and being tracking in the database if not already.
|
||||
/// </summary>
|
||||
public void EnsureMutableSkin()
|
||||
{
|
||||
if (skinInfo == SkinInfo.Default)
|
||||
return new DefaultSkin();
|
||||
if (CurrentSkinInfo.Value.ID >= 1) return;
|
||||
|
||||
if (skinInfo == DefaultLegacySkin.Info)
|
||||
return new DefaultLegacySkin(legacyDefaultResources, this);
|
||||
var skin = CurrentSkin.Value;
|
||||
|
||||
return new LegacySkin(skinInfo, this);
|
||||
// if the user is attempting to save one of the default skin implementations, create a copy first.
|
||||
CurrentSkinInfo.Value = Import(new SkinInfo
|
||||
{
|
||||
Name = skin.SkinInfo.Name + " (modified)",
|
||||
Creator = skin.SkinInfo.Creator,
|
||||
InstantiationInfo = skin.SkinInfo.InstantiationInfo,
|
||||
}).Result;
|
||||
}
|
||||
|
||||
public void Save(Skin skin)
|
||||
{
|
||||
if (skin.SkinInfo.ID <= 0)
|
||||
throw new InvalidOperationException($"Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first.");
|
||||
|
||||
foreach (var drawableInfo in skin.DrawableComponentInfo)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -3,10 +3,8 @@
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
/// <summary>
|
||||
/// The default placement location for new <see cref="ISkinnableComponent"/>s.
|
||||
/// </summary>
|
||||
public interface IDefaultSkinnableTarget : ISkinnableTarget
|
||||
public enum SkinnableTarget
|
||||
{
|
||||
MainHUDComponents
|
||||
}
|
||||
}
|
17
osu.Game/Skinning/SkinnableTargetComponent.cs
Normal file
17
osu.Game/Skinning/SkinnableTargetComponent.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class SkinnableTargetComponent : ISkinComponent
|
||||
{
|
||||
public readonly SkinnableTarget Target;
|
||||
|
||||
public string LookupName => Target.ToString();
|
||||
|
||||
public SkinnableTargetComponent(SkinnableTarget target)
|
||||
{
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
}
|
46
osu.Game/Skinning/SkinnableTargetComponentsContainer.cs
Normal file
46
osu.Game/Skinning/SkinnableTargetComponentsContainer.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// 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;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
/// <summary>
|
||||
/// A container which groups the components of a <see cref="SkinnableTargetContainer"/> into a single object.
|
||||
/// Optionally also applies a default layout to the components.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SkinnableTargetComponentsContainer : Container, ISkinnableDrawable
|
||||
{
|
||||
public bool IsEditable => false;
|
||||
|
||||
private readonly Action<Container> applyDefaults;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a wrapper with defaults that should be applied once.
|
||||
/// </summary>
|
||||
/// <param name="applyDefaults">A function to apply the default layout.</param>
|
||||
public SkinnableTargetComponentsContainer(Action<Container> applyDefaults)
|
||||
: this()
|
||||
{
|
||||
this.applyDefaults = applyDefaults;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public SkinnableTargetComponentsContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// schedule is required to allow children to run their LoadComplete and take on their correct sizes.
|
||||
ScheduleAfterChildren(() => applyDefaults?.Invoke(this));
|
||||
}
|
||||
}
|
||||
}
|
71
osu.Game/Skinning/SkinnableTargetContainer.cs
Normal file
71
osu.Game/Skinning/SkinnableTargetContainer.cs
Normal file
@ -0,0 +1,71 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget
|
||||
{
|
||||
private SkinnableTargetComponentsContainer content;
|
||||
|
||||
public SkinnableTarget Target { get; }
|
||||
|
||||
public IBindableList<ISkinnableDrawable> Components => components;
|
||||
|
||||
private readonly BindableList<ISkinnableDrawable> components = new BindableList<ISkinnableDrawable>();
|
||||
|
||||
public SkinnableTargetContainer(SkinnableTarget target)
|
||||
{
|
||||
Target = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload all components in this container from the current skin.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
ClearInternal();
|
||||
components.Clear();
|
||||
|
||||
content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer;
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
LoadComponentAsync(content, wrapper =>
|
||||
{
|
||||
AddInternal(wrapper);
|
||||
components.AddRange(wrapper.Children.OfType<ISkinnableDrawable>());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new skinnable component to this target.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to add.</param>
|
||||
/// <exception cref="NotSupportedException">Thrown when attempting to add an element to a target which is not supported by the current skin.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if the provided instance is not a <see cref="Drawable"/>.</exception>
|
||||
public void Add(ISkinnableDrawable component)
|
||||
{
|
||||
if (content == null)
|
||||
throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin.");
|
||||
|
||||
if (!(component is Drawable drawable))
|
||||
throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(drawable));
|
||||
|
||||
content.Add(drawable);
|
||||
components.Add(component);
|
||||
}
|
||||
|
||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||
{
|
||||
base.SkinChanged(skin, allowFallback);
|
||||
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user