diff --git a/osu-framework b/osu-framework index 383a8da7bc..ecc5e3189e 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 383a8da7bc45af498288b4b72c72a048a0996e74 +Subproject commit ecc5e3189e8229d36095882b9a7a0efc95402be9 diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs index 785f915a3e..385bc444d1 100644 --- a/osu.Desktop.Deploy/Program.cs +++ b/osu.Desktop.Deploy/Program.cs @@ -7,7 +7,6 @@ using System.Configuration; using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; using Newtonsoft.Json; using osu.Framework.IO.Network; using FileWebRequest = osu.Framework.IO.Network.FileWebRequest; @@ -391,8 +390,8 @@ namespace osu.Desktop.Deploy public static void AuthenticatedBlockingPerform(this WebRequest r) { - r.AddHeader("Authorization", $"token {GitHubAccessToken}"); - r.BlockingPerform(); + r.Headers.Add("Authorization", $"token {GitHubAccessToken}"); + r.Perform(); } } @@ -402,12 +401,7 @@ namespace osu.Desktop.Deploy { } - protected override HttpWebRequest CreateWebRequest(string requestString = null) - { - var req = base.CreateWebRequest(requestString); - req.Accept = "application/octet-stream"; - return req; - } + protected override string Accept => "application/octet-stream"; } internal class ReleaseLine diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 1e4bf3119d..5f05fae213 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -12,15 +12,12 @@ using osu.Desktop.Overlays; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game; -using osu.Game.Screens.Menu; using OpenTK.Input; namespace osu.Desktop { internal class OsuGameDesktop : OsuGame { - private VersionManager versionManager; - public OsuGameDesktop(string[] args = null) : base(args) { @@ -82,16 +79,11 @@ namespace osu.Desktop { base.LoadComplete(); - LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }); - - ScreenChanged += s => + LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => { - if (s is Intro && s.ChildScreen == null) - { - Add(versionManager); - versionManager.State = Visibility.Visible; - } - }; + Add(v); + v.State = Visibility.Visible; + }); } public override void SetHost(GameHost host) diff --git a/osu.Desktop/app.config b/osu.Desktop/app.config index 0841541f3d..ea1576b3d8 100644 --- a/osu.Desktop/app.config +++ b/osu.Desktop/app.config @@ -12,8 +12,32 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index ab64fa7ede..91c0da6f65 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -133,30 +133,42 @@ - ..\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll + $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll True - ..\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True - ..\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll + $(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll True $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll True + + $(SolutionDir)\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_green.dll + + + $(SolutionDir)\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_v2.dll + + + $(SolutionDir)\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll + + + $(SolutionDir)\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.8\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll + - ..\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll + $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll True - ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll + $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True @@ -262,4 +274,15 @@ + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index 80eb533644..6b6361b578 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -1,14 +1,20 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index b99e2808ce..77d78b8bd6 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps.IO { private const string osz_path = @"../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; - //[Test] + [Test] public void TestImportWhenClosed() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. @@ -40,14 +40,16 @@ namespace osu.Game.Tests.Beatmaps.IO } } - //[Test] + [Test] + [NonParallelizable] + [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] public void TestImportOverIPC() { using (HeadlessGameHost host = new HeadlessGameHost("host", true)) using (HeadlessGameHost client = new HeadlessGameHost("client", true)) { Assert.IsTrue(host.IsPrimaryInstance); - Assert.IsTrue(!client.IsPrimaryInstance); + Assert.IsFalse(client.IsPrimaryInstance); var osu = loadOsu(host); @@ -65,7 +67,7 @@ namespace osu.Game.Tests.Beatmaps.IO } } - //[Test] + [Test] public void TestImportWhenFileOpen() { using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenFileOpen")) diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs index 32934d3c5e..beec5ab271 100644 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs @@ -4,10 +4,9 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.MathUtils; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using OpenTK; -using OpenTK.Graphics; namespace osu.Game.Tests.Visual { @@ -19,29 +18,37 @@ namespace osu.Game.Tests.Visual { FillFlowContainer flow; - Add(flow = new FillFlowContainer + Add(new ScrollContainer { RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.5f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre + Child = flow = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + }, }); - int i = 50; foreach (FontAwesome fa in Enum.GetValues(typeof(FontAwesome))) + flow.Add(new Icon(fa)); + } + + private class Icon : Container, IHasTooltip + { + public string TooltipText { get; } + + public Icon(FontAwesome fa) { - flow.Add(new SpriteIcon + TooltipText = fa.ToString(); + + AutoSizeAxes = Axes.Both; + Child = new SpriteIcon { Icon = fa, Size = new Vector2(60), - Colour = new Color4( - Math.Max(0.5f, RNG.NextSingle()), - Math.Max(0.5f, RNG.NextSingle()), - Math.Max(0.5f, RNG.NextSingle()), - 1) - }); - - if (i-- == 0) break; + }; } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 47dbc72837..55c3e192e3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -184,7 +184,7 @@ namespace osu.Game.Beatmaps { var context = importContext.Value; - using (var transaction = context.Database.BeginTransaction()) + using (var transaction = context.BeginTransaction()) { // create local stores so we can isolate and thread safely, and share a context/transaction. var iFiles = new FileStore(() => context, storage); @@ -198,7 +198,7 @@ namespace osu.Game.Beatmaps context.SaveChanges(); } - transaction.Commit(); + context.SaveChanges(transaction); return set; } } @@ -295,7 +295,7 @@ namespace osu.Game.Beatmaps { var context = importContext.Value; - using (var transaction = context.Database.BeginTransaction()) + using (var transaction = context.BeginTransaction()) { context.ChangeTracker.AutoDetectChangesEnabled = false; @@ -313,9 +313,7 @@ namespace osu.Game.Beatmaps } context.ChangeTracker.AutoDetectChangesEnabled = true; - context.SaveChanges(); - - transaction.Commit(); + context.SaveChanges(transaction); } } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 42db025a40..1aff764ede 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -35,7 +35,8 @@ namespace osu.Game.Beatmaps.Drawables new ConstrainedIconContainer { RelativeSizeAxes = Axes.Both, - Icon = beatmap.Ruleset.CreateInstance().CreateIcon() + // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) + Icon = beatmap.Ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } } }; } diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index be86d35335..68f412eee6 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Threading; using osu.Framework.Logging; using osu.Framework.Platform; @@ -11,12 +12,26 @@ namespace osu.Game.Database { protected readonly Storage Storage; - protected readonly Func GetContext; + /// + /// Create a new instance (separate from the shared context via for performing isolated operations. + /// + protected readonly Func CreateContext; - protected DatabaseBackedStore(Func getContext, Storage storage = null) + private readonly ThreadLocal queryContext; + + /// + /// Retrieve a shared context for performing lookups (or write operations on the update thread, for now). + /// + protected OsuDbContext GetContext() => queryContext.Value; + + protected DatabaseBackedStore(Func createContext, Storage storage = null) { + CreateContext = createContext; + + // todo: while this seems to work quite well, we need to consider that contexts could enter a state where they are never cleaned up. + queryContext = new ThreadLocal(CreateContext); + Storage = storage; - GetContext = getContext; try { diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 7824cf9712..2187274c18 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; @@ -23,6 +24,7 @@ namespace osu.Game.Database public DbSet DatabasedKeyBinding { get; set; } public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } + private readonly string connectionString; private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory()); @@ -52,8 +54,6 @@ namespace osu.Game.Database { this.connectionString = connectionString; - Database.SetCommandTimeout(new TimeSpan(TimeSpan.TicksPerSecond * 10)); - var connection = Database.GetDbConnection(); connection.Open(); using (var cmd = connection.CreateCommand()) @@ -70,7 +70,7 @@ namespace osu.Game.Database // this is required for the time being due to the way we are querying in places like BeatmapStore. // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) - .UseSqlite(connectionString) + .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) .UseLoggerFactory(logger.Value); } @@ -95,6 +95,19 @@ namespace osu.Game.Database modelBuilder.Entity().HasOne(b => b.BaseDifficulty); } + public IDbContextTransaction BeginTransaction() + { + // return Database.BeginTransaction(); + return null; + } + + public new int SaveChanges(IDbContextTransaction transaction = null) + { + var ret = base.SaveChanges(); + transaction?.Commit(); + return ret; + } + private class OsuDbLoggerFactory : ILoggerFactory { #region Disposal @@ -153,7 +166,7 @@ namespace osu.Game.Database public bool IsEnabled(LogLevel logLevel) { -#if DEBUG +#if DEBUG_DATABASE return logLevel > LogLevel.Debug; #else return logLevel > LogLevel.Information; diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 6654fa7cb1..b69916e565 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -22,7 +22,7 @@ namespace osu.Game.IO public Storage Storage => base.Storage; - public FileStore(Func getContext, Storage storage) : base(getContext, storage.GetStorageForDirectory(@"files")) + public FileStore(Func createContext, Storage storage) : base(createContext, storage.GetStorageForDirectory(@"files")) { Store = new StorageBackedResourceStore(Storage); } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 07c0319f2f..1e1b1d74d4 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -17,8 +17,8 @@ namespace osu.Game.Input { public event Action KeyBindingChanged; - public KeyBindingStore(Func getContext, RulesetStore rulesets, Storage storage = null) - : base(getContext, storage) + public KeyBindingStore(Func createContext, RulesetStore rulesets, Storage storage = null) + : base(createContext, storage) { foreach (var info in rulesets.AvailableRulesets) { @@ -38,13 +38,14 @@ namespace osu.Game.Input private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { - using (var context = GetContext()) - using (var transaction = context.Database.BeginTransaction()) + var context = GetContext(); + + using (var transaction = context.BeginTransaction()) { // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = query(context, rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); + int count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -61,8 +62,7 @@ namespace osu.Game.Input }); } - context.SaveChanges(); - transaction.Commit(); + context.SaveChanges(transaction); } } @@ -72,10 +72,8 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - public IEnumerable Query(int? rulesetId = null, int? variant = null) => query(GetContext(), rulesetId, variant); - - private IEnumerable query(OsuDbContext context, int? rulesetId = null, int? variant = null) => - context.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); + public IEnumerable Query(int? rulesetId = null, int? variant = null) => + GetContext().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); public void Update(KeyBinding keyBinding) { diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 37903f924f..9a8180778d 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -106,7 +106,7 @@ namespace osu.Game.Online.API return; if (!WebRequest.Aborted) //could have been aborted by a Cancel() call - WebRequest.BlockingPerform(); + WebRequest.Perform(); if (checkAndProcessFailure()) return; diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 5410bcc55d..445688f2ce 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -37,7 +37,7 @@ namespace osu.Game.Online.API { try { - req.BlockingPerform(); + req.Perform(); } catch { @@ -61,7 +61,7 @@ namespace osu.Game.Online.API ClientSecret = clientSecret }) { - req.BlockingPerform(); + req.Perform(); Token = req.ResponseObject; return true; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d1baca68db..1e7b0dc0bc 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -157,40 +157,49 @@ namespace osu.Game BeatmapManager.PostNotification = n => notificationOverlay?.Post(n); BeatmapManager.GetStableStorage = GetStorageForStableInstall; - AddRange(new Drawable[] { + AddRange(new Drawable[] + { new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, ActionRequested = action => volume.Adjust(action) }, - mainContent = new Container - { - RelativeSizeAxes = Axes.Both, - }, - volume = new VolumeControl(), - overlayContent = new Container { RelativeSizeAxes = Axes.Both }, - new OnScreenDisplay(), + mainContent = new Container { RelativeSizeAxes = Axes.Both }, + overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, }); - LoadComponentAsync(screenStack = new Loader(), d => + loadComponentSingleFile(screenStack = new Loader(), d => { screenStack.ModePushed += screenAdded; screenStack.Exited += screenRemoved; mainContent.Add(screenStack); }); + loadComponentSingleFile(Toolbar = new Toolbar + { + Depth = -5, + OnHome = delegate + { + hideAllOverlays(); + intro?.ChildScreen?.MakeCurrent(); + }, + }, overlayContent.Add); + + loadComponentSingleFile(volume = new VolumeControl(), AddInternal); + loadComponentSingleFile(new OnScreenDisplay(), AddInternal); + //overlay elements - LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); - LoadComponentAsync(social = new SocialOverlay { Depth = -1 }, mainContent.Add); - LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); - LoadComponentAsync(settings = new MainSettings + loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); + loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, mainContent.Add); + loadComponentSingleFile(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); + loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset, Depth = -1 }, overlayContent.Add); - LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); - LoadComponentAsync(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); - LoadComponentAsync(musicController = new MusicController + loadComponentSingleFile(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); + loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); + loadComponentSingleFile(musicController = new MusicController { Depth = -4, Position = new Vector2(0, Toolbar.HEIGHT), @@ -198,16 +207,16 @@ namespace osu.Game Origin = Anchor.TopRight, }, overlayContent.Add); - LoadComponentAsync(notificationOverlay = new NotificationOverlay + loadComponentSingleFile(notificationOverlay = new NotificationOverlay { Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, overlayContent.Add); - LoadComponentAsync(dialogOverlay = new DialogOverlay + loadComponentSingleFile(dialogOverlay = new DialogOverlay { - Depth = -5, + Depth = -6, }, overlayContent.Add); Logger.NewEntry += entry => @@ -246,16 +255,6 @@ namespace osu.Game }; } - LoadComponentAsync(Toolbar = new Toolbar - { - Depth = -4, - OnHome = delegate - { - hideAllOverlays(); - intro?.ChildScreen?.MakeCurrent(); - }, - }, overlayContent.Add); - settings.StateChanged += delegate { switch (settings.State) @@ -272,6 +271,17 @@ namespace osu.Game Cursor.State = Visibility.Hidden; } + private Task asyncLoadStream; + + private void loadComponentSingleFile(T d, Action add) + where T : Drawable + { + // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). + // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, + // we could avoid the need for scheduling altogether. + Schedule(() => { asyncLoadStream = asyncLoadStream?.ContinueWith(t => LoadComponentAsync(d, add).Wait()) ?? LoadComponentAsync(d, add); }); + } + public bool OnPressed(GlobalAction action) { if (intro == null) return false; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 128b5e2f09..2ff5d7b81f 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.KeyBinding this.variant = variant; FlowContent.Spacing = new Vector2(0, 1); + FlowContent.Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index bc09a2145a..4ae127c816 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -20,11 +20,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsSlider { LabelText = "Audio Offset", - Bindable = config.GetBindable(OsuSetting.AudioOffset) + Bindable = config.GetBindable(OsuSetting.AudioOffset), + KeyboardStep = 100f }, - new OsuButton + new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Offset wizard" } }; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index ea442cdfc2..d197f8c466 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -16,9 +16,9 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { Children = new Drawable[] { - new SettingsSlider { LabelText = "Master", Bindable = audio.Volume }, - new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample }, - new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack }, + new SettingsSlider { LabelText = "Master", Bindable = audio.Volume, KeyboardStep = 0.1f }, + new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.1f }, + new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.1f }, }; } } diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs index 495a2543d1..23e7433732 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs @@ -6,7 +6,6 @@ using System.Runtime; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.Debug { @@ -24,9 +23,8 @@ namespace osu.Game.Overlays.Settings.Sections.Debug LabelText = "Active mode", Bindable = config.GetBindable(DebugSetting.ActiveGCMode) }, - new OsuButton + new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Force garbage collection", Action = GC.Collect }, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index a8ec04514a..8ec6af5cd0 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -19,7 +19,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsSlider { LabelText = "Background dim", - Bindable = config.GetBindable(OsuSetting.DimLevel) + Bindable = config.GetBindable(OsuSetting.DimLevel), + KeyboardStep = 0.1f }, new SettingsCheckbox { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 08dba011df..07a8e7464a 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -20,12 +20,14 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsSlider { LabelText = "Display beatmaps from", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum) + Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), + KeyboardStep = 1f }, new SettingsSlider { LabelText = "up to", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum) + Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), + KeyboardStep = 1f }, new SettingsEnumDropdown { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 8b7d7b0d69..56b9a55398 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -230,15 +230,13 @@ namespace osu.Game.Overlays.Settings.Sections.General LabelText = "Stay logged in", Bindable = config.GetBindable(OsuSetting.SavePassword), }, - new OsuButton + new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Sign in", Action = performLogin }, - new OsuButton + new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Register new account", //Action = registerLink } diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 833a5ff966..3bca0af3af 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.General { @@ -23,9 +22,8 @@ namespace osu.Game.Overlays.Settings.Sections.General LabelText = "Release stream", Bindable = config.GetBindable(OsuSetting.ReleaseStream), }, - new OsuButton + new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Open osu! folder", Action = storage.OpenInNativeExplorer, } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index c4ce742153..3d09d6b901 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -49,12 +49,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsSlider { LabelText = "Horizontal position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX) + Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX), + KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Vertical position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY) + Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY), + KeyboardStep = 0.1f }, } }, diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs index b68fd4bc04..00a1c093fd 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -14,9 +13,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Children = new Drawable[] { - new OsuButton + new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Key Configuration", Action = keyConfig.ToggleVisibility }, diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 233ca7be60..316be450dc 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -22,9 +23,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { Children = new Drawable[] { - importButton = new OsuButton + importButton = new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Import beatmaps from stable", Action = () => { @@ -32,9 +32,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Task.Run(() => beatmaps.ImportFromStable()).ContinueWith(t => Schedule(() => importButton.Enabled.Value = true)); } }, - deleteButton = new OsuButton + deleteButton = new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Delete ALL beatmaps", Action = () => { @@ -42,16 +41,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); } }, - restoreButton = new OsuButton + restoreButton = new SettingsButton { - RelativeSizeAxes = Axes.X, Text = "Restore all hidden difficulties", Action = () => { restoreButton.Enabled.Value = false; Task.Run(() => { - foreach (var b in beatmaps.QueryBeatmaps(b => b.Hidden)) + foreach (var b in beatmaps.QueryBeatmaps(b => b.Hidden).ToList()) beatmaps.Restore(b); }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 4b4426aca8..b4475aebb1 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -24,12 +24,14 @@ namespace osu.Game.Overlays.Settings.Sections new SettingsSlider { LabelText = "Menu cursor size", - Bindable = config.GetBindable(OsuSetting.MenuCursorSize) + Bindable = config.GetBindable(OsuSetting.MenuCursorSize), + KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Gameplay cursor size", - Bindable = config.GetBindable(OsuSetting.GameplayCursorSize) + Bindable = config.GetBindable(OsuSetting.GameplayCursorSize), + KeyboardStep = 0.1f }, new SettingsCheckbox { diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs new file mode 100644 index 0000000000..5320cef850 --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsButton : OsuButton + { + public SettingsButton() + { + RelativeSizeAxes = Axes.X; + Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index f2044f178b..5a0f25f7e0 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -2,17 +2,24 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using osu.Framework.Allocation; using OpenTK.Graphics; using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Settings { - public abstract class SettingsItem : FillFlowContainer, IFilterable + public abstract class SettingsItem : Container, IFilterable { protected abstract Drawable CreateControl(); @@ -20,8 +27,28 @@ namespace osu.Game.Overlays.Settings private IHasCurrentValue controlWithCurrent => Control as IHasCurrentValue; + protected override Container Content => FlowContent; + + protected readonly FillFlowContainer FlowContent; + private SpriteText text; + private readonly RestoreDefaultValueButton restoreDefaultValueButton = new RestoreDefaultValueButton(); + + public bool ShowsDefaultIndicator = true; + + private Color4? restoreDefaultValueColour; + + public Color4 RestoreDefaultValueColour + { + get { return restoreDefaultValueColour ?? Color4.White; } + set + { + restoreDefaultValueColour = value; + restoreDefaultValueButton?.SetButtonColour(RestoreDefaultValueColour); + } + } + public virtual string LabelText { get { return text?.Text ?? string.Empty; } @@ -51,6 +78,11 @@ namespace osu.Game.Overlays.Settings { bindable = value; controlWithCurrent?.Current.BindTo(bindable); + if (ShowsDefaultIndicator) + { + restoreDefaultValueButton.Bindable.BindTo(bindable); + restoreDefaultValueButton.Bindable.TriggerChange(); + } } } @@ -69,13 +101,94 @@ namespace osu.Game.Overlays.Settings { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = 5 }; + Padding = new MarginPadding { Right = SettingsOverlay.CONTENT_MARGINS }; + + FlowContent = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = 5 }, + }; if ((Control = CreateControl()) != null) { if (controlWithCurrent != null) controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; }; - Add(Control); + FlowContent.Add(Control); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddInternal(FlowContent); + + if (restoreDefaultValueButton != null) + { + if (!restoreDefaultValueColour.HasValue) + restoreDefaultValueColour = colours.Yellow; + restoreDefaultValueButton.SetButtonColour(RestoreDefaultValueColour); + AddInternal(restoreDefaultValueButton); + } + } + + private class RestoreDefaultValueButton : Box, IHasTooltip + { + internal readonly Bindable Bindable = new Bindable(); + + private Color4 buttonColour; + + private bool hovering; + + public RestoreDefaultValueButton() + { + Bindable.ValueChanged += value => UpdateState(); + Bindable.DisabledChanged += disabled => UpdateState(); + + RelativeSizeAxes = Axes.Y; + Width = SettingsOverlay.CONTENT_MARGINS; + Alpha = 0f; + } + + public string TooltipText => "Revert to default"; + + public override bool HandleInput => true; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + + protected override bool OnClick(InputState state) + { + if (!Bindable.Disabled) + Bindable.SetDefault(); + return true; + } + + protected override bool OnHover(InputState state) + { + hovering = true; + UpdateState(); + return true; + } + + protected override void OnHoverLost(InputState state) + { + hovering = false; + UpdateState(); + } + + internal void SetButtonColour(Color4 buttonColour) + { + this.buttonColour = buttonColour; + UpdateState(); + } + + internal void UpdateState() + { + var colour = Bindable.Disabled ? Color4.Gray : buttonColour; + this.FadeTo(Bindable.IsDefault ? 0f : hovering && !Bindable.Disabled ? 1f : 0.5f, 200, Easing.OutQuint); + this.FadeColour(ColourInfo.GradientHorizontal(colour.Opacity(0.8f), colour.Opacity(0)), 200, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index eb6e398477..a107878fb8 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -69,8 +69,6 @@ namespace osu.Game.Overlays.Settings Padding = new MarginPadding { Top = 20 + border_size, - Left = SettingsOverlay.CONTENT_MARGINS, - Right = SettingsOverlay.CONTENT_MARGINS, Bottom = 10, }, RelativeSizeAxes = Axes.X, @@ -81,7 +79,8 @@ namespace osu.Game.Overlays.Settings { TextSize = header_size, Text = Header, - Colour = colours.Yellow + Colour = colours.Yellow, + Margin = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS } }, FlowContent } diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index 2881d02302..49d73f77ec 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; @@ -22,5 +23,15 @@ namespace osu.Game.Overlays.Settings Margin = new MarginPadding { Top = 5, Bottom = 5 }, RelativeSizeAxes = Axes.X }; + + public float KeyboardStep; + + [BackgroundDependencyLoader] + private void load() + { + var slider = Control as U; + if (slider != null) + slider.KeyboardStep = KeyboardStep; + } } } diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 4164ceee21..79a644b2cb 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Settings new OsuSpriteText { Text = Header.ToUpper(), - Margin = new MarginPadding { Bottom = 10 }, + Margin = new MarginPadding { Bottom = 10, Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }, Font = @"Exo2.0-Black", }, FlowContent diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs index c1fd234628..95a25fcb86 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -11,11 +12,12 @@ namespace osu.Game.Overlays.Toolbar internal class ToolbarUserArea : Container { public LoginOverlay LoginOverlay; - private readonly ToolbarUserButton button; + private ToolbarUserButton button; public override RectangleF BoundingBox => button.BoundingBox; - public ToolbarUserArea() + [BackgroundDependencyLoader] + private void load() { RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; @@ -36,4 +38,4 @@ namespace osu.Game.Overlays.Toolbar }; } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs b/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs index ea958d05f9..16868e5843 100644 --- a/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs @@ -19,7 +19,8 @@ namespace osu.Game.Screens.Play.ReplaySettings new ReplaySliderBar { LabelText = "Playback speed", - Bindable = config.GetBindable(OsuSetting.PlaybackSpeed) + Bindable = config.GetBindable(OsuSetting.PlaybackSpeed), + KeyboardStep = 0.5f } }; } diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index ca0aaebb5e..b2c8be47bd 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -13,11 +13,9 @@ namespace osu.Game.Tests.Visual { using (var host = new HeadlessGameHost($"test-{Guid.NewGuid()}", realtime: false)) { + host.Storage.DeleteDirectory(string.Empty); host.Run(new OsuTestCaseTestRunner(this)); } - - // clean up after each run - //storage.DeleteDirectory(string.Empty); } public class OsuTestCaseTestRunner : OsuGameBase diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7b65aa8d66..8f519d8915 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -289,6 +289,7 @@ +