From 4c14b32783ada771f7e7e75dec301688bf98713b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 13:45:39 +0900 Subject: [PATCH 1/5] Add basic skin database model layout and importing --- osu.Game/Database/OsuDbContext.cs | 2 ++ osu.Game/OsuGame.cs | 2 ++ osu.Game/OsuGameBase.cs | 6 +++++ osu.Game/Skinning/SkinFileInfo.cs | 25 ++++++++++++++++++++ osu.Game/Skinning/SkinInfo.cs | 25 ++++++++++++++++++++ osu.Game/Skinning/SkinManager.cs | 39 +++++++++++++++++++++++++++++++ osu.Game/Skinning/SkinStore.cs | 22 +++++++++++++++++ osu.Game/osu.Game.csproj | 4 ++++ 8 files changed, 125 insertions(+) create mode 100644 osu.Game/Skinning/SkinFileInfo.cs create mode 100644 osu.Game/Skinning/SkinInfo.cs create mode 100644 osu.Game/Skinning/SkinManager.cs create mode 100644 osu.Game/Skinning/SkinStore.cs diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index e83b30595e..a4b0c30478 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; +using osu.Game.Skinning; namespace osu.Game.Database { @@ -26,6 +27,7 @@ public class OsuDbContext : DbContext public DbSet DatabasedSetting { get; set; } public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } + public DbSet SkinInfo { get; set; } private readonly string connectionString; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 14bc31aecf..15ee62c5e5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -187,7 +187,9 @@ protected override void LoadComplete() CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; // hook up notifications to components. + SkinManager.PostNotification = n => notifications?.Post(n); BeatmapManager.PostNotification = n => notifications?.Post(n); + BeatmapManager.GetStableStorage = GetStorageForStableInstall; AddRange(new Drawable[] diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b70055cc00..94ed696e49 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,6 +30,7 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; namespace osu.Game { @@ -39,6 +40,8 @@ public class OsuGameBase : Framework.Game, IOnlineComponent, ICanAcceptFiles protected BeatmapManager BeatmapManager; + protected SkinManager SkinManager; + protected RulesetStore RulesetStore; protected FileStore FileStore; @@ -103,6 +106,8 @@ private void load() runMigrations(); + dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host)); + dependencies.Cache(API = new APIAccess { Username = LocalConfig.Get(OsuSetting.Username), @@ -120,6 +125,7 @@ private void load() fileImporters.Add(BeatmapManager); fileImporters.Add(ScoreStore); + fileImporters.Add(SkinManager); //this completely overrides the framework default. will need to change once we make a proper FontStore. dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }); diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs new file mode 100644 index 0000000000..e8caf8f44a --- /dev/null +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; +using osu.Game.IO; + +namespace osu.Game.Skinning +{ + public class SkinFileInfo : INamedFileInfo + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public int SkinInfoID { get; set; } + + public int FileInfoID { get; set; } + + public FileInfo FileInfo { get; set; } + + [Required] + public string Filename { get; set; } + } +} diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs new file mode 100644 index 0000000000..ee9f63bec9 --- /dev/null +++ b/osu.Game/Skinning/SkinInfo.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; + +namespace osu.Game.Skinning +{ + public class SkinInfo : IHasFiles, IHasPrimaryKey, ISoftDelete + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public string Name { get; set; } + + public string Creator { get; set; } + + public List Files { get; set; } + + public bool DeletePending { get; set; } + + public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; + } +} diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs new file mode 100644 index 0000000000..ac3347e1d0 --- /dev/null +++ b/osu.Game/Skinning/SkinManager.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.IO.Archives; + +namespace osu.Game.Skinning +{ + public class SkinManager : ArchiveModelManager + { + public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; + + public override string[] HandledExtensions => new[] { ".osk" }; + + /// + /// Returns a list of all usable s. + /// + /// A list of available . + public List GetAllUsableSkins() + { + var userSkins = ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); + userSkins.Insert(0, SkinInfo.Default); + return userSkins; + } + + protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; + + private SkinStore store; + + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost) + : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) + { + } + } +} diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs new file mode 100644 index 0000000000..ffd9873901 --- /dev/null +++ b/osu.Game/Skinning/SkinStore.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.Skinning +{ + public class SkinStore : MutableDatabaseBackedStore + { + public SkinStore(DatabaseContextFactory contextFactory, Storage storage = null) + : base(contextFactory, storage) + { + } + + protected override IQueryable AddIncludesForConsumption(IQueryable query) => + base.AddIncludesForConsumption(query) + .Include(s => s.Files).ThenInclude(f => f.FileInfo); + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index afeb791029..4b1dad28db 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -850,6 +850,10 @@ + + + + From 2351b6ab269d7d4623dd9817ecbaf6b4571c691b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 15:44:13 +0900 Subject: [PATCH 2/5] Add migration --- .../20180219060912_AddSkins.Designer.cs | 379 ++++++++++++++++++ .../Migrations/20180219060912_AddSkins.cs | 73 ++++ .../Migrations/OsuDbContextModelSnapshot.cs | 50 +++ osu.Game/osu.Game.csproj | 4 + 4 files changed, 506 insertions(+) create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.Designer.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.cs diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs new file mode 100644 index 0000000000..83b8d6cf8a --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -0,0 +1,379 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180219060912_AddSkins")] + partial class AddSkins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + 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("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + 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.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 + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs new file mode 100644 index 0000000000..741fcf4079 --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -0,0 +1,73 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class AddSkins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SkinInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Creator = table.Column(type: "TEXT", nullable: true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "SkinFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false), + SkinInfoID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_SkinFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", + column: x => x.SkinInfoID, + principalTable: "SkinInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_FileInfoID", + table: "SkinFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_SkinInfoID", + table: "SkinFileInfo", + column: "SkinInfoID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SkinFileInfo"); + + migrationBuilder.DropTable( + name: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 157125102f..1627627790 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -281,6 +281,43 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") @@ -322,6 +359,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) .WithMany("BeatmapSets") .HasForeignKey("MetadataID"); }); + + 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 } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4b1dad28db..829addc360 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -322,6 +322,10 @@ 20171209034410_AddRulesetInfoShortName.cs + + + 20180219060912_AddSkins.cs + From 659cf629b6d62e614bc5bc87eefcaf6075b78182 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 15:44:25 +0900 Subject: [PATCH 3/5] Add skin seleciton dropdown to settings --- .../Overlays/Settings/Sections/SkinSection.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index f6915896d7..1cd1cd0f7f 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -1,26 +1,33 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; using OpenTK; namespace osu.Game.Overlays.Settings.Sections { public class SkinSection : SettingsSection { + private SettingsDropdown skinDropdown; + public override string Header => "Skin"; + public override FontAwesome Icon => FontAwesome.fa_paint_brush; [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, SkinManager skins) { FlowContent.Spacing = new Vector2(0, 5); Children = new Drawable[] { + skinDropdown = new SettingsDropdown(), new SettingsSlider { LabelText = "Menu cursor size", @@ -39,6 +46,14 @@ private void load(OsuConfigManager config) Bindable = config.GetBindable(OsuSetting.AutoCursorSize) }, }; + + void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.Name, s)); + skins.ItemAdded += _ => reloadSkins(); + skins.ItemRemoved += _ => reloadSkins(); + + reloadSkins(); + + skinDropdown.Bindable = skins.CurrentSkinInfo; } private class SizeSlider : OsuSliderBar From 402d71a8d9677b08f61130b5c0b269925f3cc772 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 16:29:05 +0900 Subject: [PATCH 4/5] Add user skin setting storage --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++++- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 11 +++++++++++ osu.Game/Overlays/Settings/Sections/SkinSection.cs | 8 ++++---- osu.Game/Skinning/SkinInfo.cs | 5 ++++- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c33dd91330..3d927ef67c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -14,6 +14,8 @@ protected override void InitialiseDefaults() { // UI/selection defaults Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); + Set(OsuSetting.Skin, 0, 0, int.MaxValue); + Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); Set(OsuSetting.ShowConvertedBeatmaps, true); @@ -122,6 +124,7 @@ public enum OsuSetting ChatDisplayHeight, Version, ShowConvertedBeatmaps, - SpeedChangeVisualisation + SpeedChangeVisualisation, + Skin } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index a65593ff82..854bee99a5 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -48,7 +48,7 @@ public abstract class ArchiveModelManager : ICanAcceptFiles protected readonly IDatabaseContextFactory ContextFactory; - protected readonly MutableDatabaseBackedStore ModelStore; + public readonly MutableDatabaseBackedStore ModelStore; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 15ee62c5e5..17a72d3c87 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -30,6 +30,7 @@ using osu.Game.Screens.Play; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; +using osu.Game.Skinning; using OpenTK.Graphics; namespace osu.Game @@ -79,6 +80,8 @@ private Intro intro private Bindable configRuleset; public Bindable Ruleset = new Bindable(); + private Bindable configSkin; + private readonly string[] args; private SettingsOverlay settings; @@ -122,10 +125,18 @@ private void load(FrameworkConfigManager frameworkConfig) dependencies.CacheAs(this); + // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; + // bind config int to database SkinInfo + configSkin = LocalConfig.GetBindable(OsuSetting.Skin); + + SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID; + configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == id) ?? SkinInfo.Default; + configSkin.TriggerChange(); + LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 1cd1cd0f7f..bc0b8b4aaa 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections { public class SkinSection : SettingsSection { - private SettingsDropdown skinDropdown; + private SettingsDropdown skinDropdown; public override string Header => "Skin"; @@ -27,7 +27,7 @@ private void load(OsuConfigManager config, SkinManager skins) FlowContent.Spacing = new Vector2(0, 5); Children = new Drawable[] { - skinDropdown = new SettingsDropdown(), + skinDropdown = new SettingsDropdown(), new SettingsSlider { LabelText = "Menu cursor size", @@ -47,13 +47,13 @@ private void load(OsuConfigManager config, SkinManager skins) }, }; - void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.Name, s)); + void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.Name, s.ID)); skins.ItemAdded += _ => reloadSkins(); skins.ItemRemoved += _ => reloadSkins(); reloadSkins(); - skinDropdown.Bindable = skins.CurrentSkinInfo; + skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin); } private class SizeSlider : OsuSliderBar diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index ee9f63bec9..45c8b97f63 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -1,13 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; namespace osu.Game.Skinning { - public class SkinInfo : IHasFiles, IHasPrimaryKey, ISoftDelete + public class SkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } @@ -21,5 +22,7 @@ public class SkinInfo : IHasFiles, IHasPrimaryKey, ISoftDelete public bool DeletePending { get; set; } public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; + + public bool Equals(SkinInfo other) => other != null && ID == other.ID; } } From e9c583438761b00e2b0332c4198a53afe45df946 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Feb 2018 13:22:33 +0900 Subject: [PATCH 5/5] Add query method for now --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 854bee99a5..a65593ff82 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -48,7 +48,7 @@ public abstract class ArchiveModelManager : ICanAcceptFiles protected readonly IDatabaseContextFactory ContextFactory; - public readonly MutableDatabaseBackedStore ModelStore; + protected readonly MutableDatabaseBackedStore ModelStore; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 17a72d3c87..95eb88c5c8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -134,7 +134,7 @@ private void load(FrameworkConfigManager frameworkConfig) configSkin = LocalConfig.GetBindable(OsuSetting.Skin); SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID; - configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == id) ?? SkinInfo.Default; + configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == id) ?? SkinInfo.Default; configSkin.TriggerChange(); LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ac3347e1d0..0031968b2b 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -1,8 +1,11 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using osu.Framework.Configuration; using osu.Framework.Platform; using osu.Game.Database; @@ -35,5 +38,12 @@ public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcH : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) { } + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The first result for the provided query, or null if no results were found. + public SkinInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); } }