From 62e676feb5e6ca8a4d352f96c51ffd4cc8c01446 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:18:02 +0100 Subject: [PATCH 01/74] Restyle SpotlightsDropdown to match osu-web --- .../Overlays/Rankings/SpotlightSelector.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index f019b50ae8..13640c7fe7 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -165,8 +165,28 @@ namespace osu.Game.Overlays.Rankings [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { + // osu-web adds a 0.6 opacity container on top of the 0.5 base one when hovering, 0.8 on a single container here matches the resulting colour + AccentColour = colourProvider.Background6.Opacity(0.8f); menu.BackgroundColour = colourProvider.Background5; - AccentColour = colourProvider.Background6; + Padding = new MarginPadding { Vertical = 20 }; + } + + private class SpotlightsDropdownHeader : OsuDropdownHeader + { + public SpotlightsDropdownHeader() : base() + { + Height = 48; + Text.Font = OsuFont.GetFont(size: 15); + Foreground.Padding = new MarginPadding { Horizontal = 10, Vertical = 15 }; + Margin = Icon.Margin = new MarginPadding(0); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundColour = colourProvider.Background6.Opacity(0.5f); + BackgroundColourHover = colourProvider.Background5; + } } } } From 82cbd35e30940df06e6197bda5dbfba12eb47a62 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:22:22 +0100 Subject: [PATCH 02/74] Make CountryName use LinkFlowContainer for consistency --- .../Rankings/Tables/CountriesTable.cs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 0b9a48ce0e..5306000d6a 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -5,11 +5,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using osu.Game.Users; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Rankings.Tables { @@ -62,35 +61,22 @@ namespace osu.Game.Overlays.Rankings.Tables } }; - private class CountryName : OsuHoverContainer + private class CountryName : LinkFlowContainer { - protected override IEnumerable EffectTargets => new[] { text }; - [Resolved(canBeNull: true)] private RankingsOverlay rankings { get; set; } - private readonly OsuSpriteText text; private readonly Country country; - public CountryName(Country country) + public CountryName(Country country) : base(t => t.Font = OsuFont.GetFont(size: 12)) { this.country = country; - AutoSizeAxes = Axes.Both; - Add(text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Text = country.FullName ?? string.Empty, - }); - } + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + TextAnchor = Anchor.CentreLeft; - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Content2; - - Action = () => rankings?.ShowCountry(country); + AddLink(country.FullName ?? string.Empty, () => rankings?.ShowCountry(country)); } } } From f03ada65ddec5c5b38f125bf49b776a0c80a5f0a Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:23:50 +0100 Subject: [PATCH 03/74] Adjust grade columns spacing --- .../Overlays/Rankings/Tables/RankingsTable.cs | 10 +++++++--- .../Overlays/Rankings/Tables/UserBasedTable.cs | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 943897581e..946bd82cf8 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; @@ -96,19 +96,23 @@ namespace osu.Game.Overlays.Rankings.Tables } }; + protected virtual IEnumerable GradeColumns() => new List(); + protected virtual string HighlightedColumn() => @"Performance"; private class HeaderText : OsuSpriteText { private readonly string highlighted; - public HeaderText(string text, string highlighted) + public HeaderText(string text, string highlighted, IEnumerable gradeColumns) { this.highlighted = highlighted; Text = text; Font = OsuFont.GetFont(size: 12); - Margin = new MarginPadding { Horizontal = 10 }; + + var isGrade = gradeColumns.Contains(text); + Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index cad7364103..8eecffd738 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; @@ -19,16 +19,18 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override TableColumn[] CreateAdditionalHeaders() => new[] + protected override IEnumerable GradeColumns() => new List() { "SS", "S", "A" }; + + protected override TableColumn[] CreateAdditionalHeaders() { + var gradeColumns = GradeColumns().Select(grade => new TableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))); + + return new[] + { new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - }.Concat(CreateUniqueHeaders()).Concat(new[] - { - new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - }).ToArray(); + }.Concat(CreateUniqueHeaders()).Concat(gradeColumns).ToArray(); + } protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; From 9e4a6a9cf3cf309f7c88e4791e438b605d3116f5 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:25:49 +0100 Subject: [PATCH 04/74] Add spacing between RankingsTable rows to match osu-web --- .../Overlays/Rankings/Tables/RankingsTable.cs | 23 +++++++++++-------- .../Rankings/Tables/TableRowBackground.cs | 7 +++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 946bd82cf8..e7da5c01fb 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; @@ -20,7 +20,8 @@ namespace osu.Game.Overlays.Rankings.Tables { protected const int TEXT_SIZE = 12; private const float horizontal_inset = 20; - private const float row_height = 25; + private const float row_height = 32; + private const float row_spacing = 3; private const int items_per_page = 50; private readonly int page; @@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Rankings.Tables AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Horizontal = horizontal_inset }; - RowSize = new Dimension(GridSizeMode.Absolute, row_height); + RowSize = new Dimension(GridSizeMode.Absolute, row_height + row_spacing); } [BackgroundDependencyLoader] @@ -47,10 +48,11 @@ namespace osu.Game.Overlays.Rankings.Tables { RelativeSizeAxes = Axes.Both, Depth = 1f, - Margin = new MarginPadding { Top = row_height } + Margin = new MarginPadding { Top = row_height + row_spacing }, + Spacing = new Vector2(0, row_spacing), }); - rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); + rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground(row_height))); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); @@ -68,13 +70,13 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract Drawable[] CreateAdditionalContent(TModel item); - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn()); + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn(), GradeColumns()); protected abstract Country GetCountry(TModel item); protected abstract Drawable CreateFlagContent(TModel item); - private OsuSpriteText createIndexDrawable(int index) => new OsuSpriteText + private OsuSpriteText createIndexDrawable(int index) => new RowText { Text = $"#{index + 1}", Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.SemiBold) @@ -84,12 +86,13 @@ namespace osu.Game.Overlays.Rankings.Tables { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Bottom = row_spacing }, Children = new[] { new UpdateableFlag(GetCountry(item)) { - Size = new Vector2(20, 13), + Size = new Vector2(30, 20), ShowPlaceholderOnNull = false, }, CreateFlagContent(item) @@ -128,7 +131,7 @@ namespace osu.Game.Overlays.Rankings.Tables public RowText() { Font = OsuFont.GetFont(size: TEXT_SIZE); - Margin = new MarginPadding { Horizontal = 10 }; + Margin = new MarginPadding { Horizontal = 10, Bottom = row_spacing }; } } diff --git a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs index fe87a8b3d4..d5e2f12172 100644 --- a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs +++ b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs @@ -19,13 +19,14 @@ namespace osu.Game.Overlays.Rankings.Tables private Color4 idleColour; private Color4 hoverColour; - public TableRowBackground() + public TableRowBackground(float height) { RelativeSizeAxes = Axes.X; - Height = 25; + Height = height; - CornerRadius = 3; + CornerRadius = 4; Masking = true; + MaskingSmoothness = 0.5f; InternalChild = background = new Box { From 1d6746102f682469d9528a851d8d804e1f2b3e09 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:26:21 +0100 Subject: [PATCH 05/74] Adjust user links --- osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 8eecffd738..45a2c20000 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; @@ -27,8 +27,8 @@ namespace osu.Game.Overlays.Rankings.Tables return new[] { - new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }.Concat(CreateUniqueHeaders()).Concat(gradeColumns).ToArray(); } @@ -36,7 +36,12 @@ namespace osu.Game.Overlays.Rankings.Tables protected sealed override Drawable CreateFlagContent(UserStatistics item) { - var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE, italics: true)) { AutoSizeAxes = Axes.Both }; + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE, italics: true)) + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + TextAnchor = Anchor.CentreLeft + }; username.AddUserLink(item.User); return username; } From b65f5031a2e1a9ddc9c73ee0acea921a65cb7461 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:26:45 +0100 Subject: [PATCH 06/74] Adjust spacing in SpotlightSelector --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 13640c7fe7..21e9881327 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Overlays.Rankings { @@ -50,7 +51,7 @@ namespace osu.Game.Overlays.Rankings public SpotlightSelector() { RelativeSizeAxes = Axes.X; - Height = 100; + Height = 155; Add(content = new Container { RelativeSizeAxes = Axes.Both, @@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Rankings new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, Children = new Drawable[] { dropdown = new SpotlightsDropdown @@ -128,6 +129,7 @@ namespace osu.Game.Overlays.Rankings { AutoSizeAxes = Axes.Both; Direction = FillDirection.Vertical; + Padding = new MarginPadding { Vertical = 15 }; Children = new Drawable[] { new OsuSpriteText @@ -138,12 +140,12 @@ namespace osu.Game.Overlays.Rankings new Container { AutoSizeAxes = Axes.X, - Height = 20, + Height = 25, Child = valueText = new OsuSpriteText { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Light), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light), } } }; From 92da7132cd616a6c3d7dbd5901f6fdfa351748be Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:35:02 +0100 Subject: [PATCH 07/74] Fix code quality issues --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 +- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 7 ++----- osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 21e9881327..37e2c1c38b 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.Rankings private class SpotlightsDropdownHeader : OsuDropdownHeader { - public SpotlightsDropdownHeader() : base() + public SpotlightsDropdownHeader() { Height = 48; Text.Font = OsuFont.GetFont(size: 15); diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 5306000d6a..c43ecf9be5 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -66,12 +66,9 @@ namespace osu.Game.Overlays.Rankings.Tables [Resolved(canBeNull: true)] private RankingsOverlay rankings { get; set; } - private readonly Country country; - - public CountryName(Country country) : base(t => t.Font = OsuFont.GetFont(size: 12)) + public CountryName(Country country) + : base(t => t.Font = OsuFont.GetFont(size: 12)) { - this.country = country; - AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 45a2c20000..8ce31c8539 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override IEnumerable GradeColumns() => new List() { "SS", "S", "A" }; + protected override IEnumerable GradeColumns() => new List { "SS", "S", "A" }; protected override TableColumn[] CreateAdditionalHeaders() { From 6d09f1eea41d3cb3a01d19dcfc0e8f5daa2f2967 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Fri, 28 Feb 2020 13:18:40 +0100 Subject: [PATCH 08/74] Hook up SpotlightsDropdownHeader --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 37e2c1c38b..408070f4c7 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -164,6 +164,8 @@ namespace osu.Game.Overlays.Rankings protected override DropdownMenu CreateMenu() => menu = base.CreateMenu().With(m => m.MaxHeight = 400); + protected override DropdownHeader CreateHeader() => new SpotlightsDropdownHeader(); + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { From 0760ccf0245afcdf60a87512dcec454a1dd85414 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sat, 29 Feb 2020 15:14:25 +0100 Subject: [PATCH 09/74] Adjust CountryFilter spacing to match web --- osu.Game/Overlays/Rankings/CountryFilter.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryPill.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 4bdefb06ef..9950f36141 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Rankings public class CountryFilter : CompositeDrawable, IHasCurrentValue { private const int duration = 200; - private const int height = 50; + private const int height = 70; private readonly BindableWithCurrent current = new BindableWithCurrent(); diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 1b19bbd95e..edd7b596d2 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Rankings InternalChild = content = new CircularContainer { - Height = 25, + Height = 30, AutoSizeDuration = duration, AutoSizeEasing = Easing.OutQuint, Masking = true, @@ -58,9 +58,9 @@ namespace osu.Game.Overlays.Rankings Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Margin = new MarginPadding { Horizontal = 10 }, + Margin = new MarginPadding { Horizontal = 15 }, Direction = FillDirection.Horizontal, - Spacing = new Vector2(8, 0), + Spacing = new Vector2(15, 0), Children = new Drawable[] { new FillFlowContainer @@ -70,14 +70,14 @@ namespace osu.Game.Overlays.Rankings Anchor = Anchor.Centre, Origin = Anchor.Centre, Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), + Spacing = new Vector2(5, 0), Children = new Drawable[] { flag = new UpdateableFlag { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(22, 15) + Size = new Vector2(30, 20) }, countryName = new OsuSpriteText { @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Both; Add(icon = new SpriteIcon { - Size = new Vector2(8), + Size = new Vector2(10), Icon = FontAwesome.Solid.Times }); } From 520bbcf2e4d4fa56090b3da2b3680a34fab5a0d8 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sat, 29 Feb 2020 15:19:36 +0100 Subject: [PATCH 10/74] Use correct x-axis margins --- osu.Game/Overlays/Rankings/CountryFilter.cs | 4 ++-- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 9950f36141..ab5df80d8a 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Rankings Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Margin = new MarginPadding { Left = OverlayHeader.CONTENT_X_MARGIN }, Children = new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 408070f4c7..4fb0d58e57 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Rankings new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Horizontal = OverlayHeader.CONTENT_X_MARGIN }, Children = new Drawable[] { dropdown = new SpotlightsDropdown From e09fbcb05fac1b881593810b1bed91d1ba69f101 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 17:47:48 +0100 Subject: [PATCH 11/74] Only add link if the country name isn't null --- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index c43ecf9be5..011a8207a0 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -73,7 +73,8 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - AddLink(country.FullName ?? string.Empty, () => rankings?.ShowCountry(country)); + if (country.FullName != null) + AddLink(country.FullName, () => rankings?.ShowCountry(country)); } } } From 276a90fb8495f71b29e9886b7796746d754e4007 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 18:19:28 +0100 Subject: [PATCH 12/74] Use public property instead of ctor parameter --- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 4 ++-- osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index e7da5c01fb..c03a279afa 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Rankings.Tables Spacing = new Vector2(0, row_spacing), }); - rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground(row_height))); + rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground { Height = row_height })); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); diff --git a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs index d5e2f12172..b49fec65db 100644 --- a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs +++ b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -19,10 +19,9 @@ namespace osu.Game.Overlays.Rankings.Tables private Color4 idleColour; private Color4 hoverColour; - public TableRowBackground(float height) + public TableRowBackground() { RelativeSizeAxes = Axes.X; - Height = height; CornerRadius = 4; Masking = true; From 06642dc7ff26a99386b8a2c8d7d8a861cdc03bfc Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 18:20:55 +0100 Subject: [PATCH 13/74] Improve HeaderText highlight and spacing logic --- .../Overlays/Rankings/Tables/RankingsTable.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index c03a279afa..eb2b676500 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -70,7 +70,14 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract Drawable[] CreateAdditionalContent(TModel item); - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn(), GradeColumns()); + protected override Drawable CreateHeader(int index, TableColumn column) + { + var title = column?.Header ?? string.Empty; + var isHighlighted = HighlightedColumn() == title; + var isGrade = GradeColumns().Contains(title); + + return new HeaderText(title, isHighlighted) { Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } }; + } protected abstract Country GetCountry(TModel item); @@ -105,23 +112,20 @@ namespace osu.Game.Overlays.Rankings.Tables private class HeaderText : OsuSpriteText { - private readonly string highlighted; + private readonly bool isHighlighted; - public HeaderText(string text, string highlighted, IEnumerable gradeColumns) + public HeaderText(string text, bool isHighlighted) { - this.highlighted = highlighted; + this.isHighlighted = isHighlighted; Text = text; Font = OsuFont.GetFont(size: 12); - - var isGrade = gradeColumns.Contains(text); - Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 }; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - if (Text != highlighted) + if (isHighlighted) Colour = colourProvider.Foreground1; } } From 56f76580fdaa02a04575f4a7e1be4b5b231194fb Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 18:22:56 +0100 Subject: [PATCH 14/74] Use Y-axis autosizing in SpotlightsDropdownHeader --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 4fb0d58e57..d2cb94de9a 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -179,8 +179,9 @@ namespace osu.Game.Overlays.Rankings { public SpotlightsDropdownHeader() { - Height = 48; + AutoSizeAxes = Axes.Y; Text.Font = OsuFont.GetFont(size: 15); + Text.Padding = new MarginPadding { Vertical = 1.5f }; // osu-web line-height difference compensation Foreground.Padding = new MarginPadding { Horizontal = 10, Vertical = 15 }; Margin = Icon.Margin = new MarginPadding(0); } From df328dd9817c82c776b6211e710f177091637017 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 5 Mar 2020 23:09:51 +0100 Subject: [PATCH 15/74] Revert margin changes --- osu.Game/Overlays/Rankings/CountryFilter.cs | 4 ++-- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index ab5df80d8a..9950f36141 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Rankings Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Left = OverlayHeader.CONTENT_X_MARGIN }, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, Children = new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index d2cb94de9a..a9fcac42af 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Rankings new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = OverlayHeader.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, Children = new Drawable[] { dropdown = new SpotlightsDropdown From 1c69e4eb5b56246e91e4c89a96c9639c848e6007 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 18:26:33 +0100 Subject: [PATCH 16/74] Update SpotlightSelector design --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index a9fcac42af..7299a48483 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -77,11 +77,11 @@ namespace osu.Game.Overlays.Rankings }, new FillFlowContainer { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), + Spacing = new Vector2(20, 0), Children = new Drawable[] { startDateColumn = new InfoColumn(@"Start Date"), From 280a009784707a6a60586d89fe8bf7d53a998a8d Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 18:31:44 +0100 Subject: [PATCH 17/74] Fix header colours being flipped --- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index eb2b676500..65ef7dc6b7 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.Rankings.Tables [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - if (isHighlighted) + if (!isHighlighted) Colour = colourProvider.Foreground1; } } From f1e54d2745c62e85ba06ec01a58b237ea673e2a0 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 18:32:12 +0100 Subject: [PATCH 18/74] Apply suggestions --- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 4 ++-- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 011a8207a0..c5e413c7fa 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (country.FullName != null) + if (!string.IsNullOrEmpty(country.FullName)) AddLink(country.FullName, () => rankings?.ShowCountry(country)); } } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 65ef7dc6b7..17d0c9cf24 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -76,7 +76,10 @@ namespace osu.Game.Overlays.Rankings.Tables var isHighlighted = HighlightedColumn() == title; var isGrade = GradeColumns().Contains(title); - return new HeaderText(title, isHighlighted) { Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } }; + return new HeaderText(title, isHighlighted) + { + Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } + }; } protected abstract Country GetCountry(TModel item); From db55b98ed338b4c9c20d4c3a0ee593c7cae0db6a Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 22:03:54 +0100 Subject: [PATCH 19/74] Move grade column spacing logic to UserBasedTable --- .../Overlays/Rankings/Tables/RankingsTable.cs | 17 +++------- .../Overlays/Rankings/Tables/ScoresTable.cs | 2 +- .../Rankings/Tables/UserBasedTable.cs | 31 ++++++++++++++----- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 17d0c9cf24..3fb8602cbc 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -70,16 +70,12 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract Drawable[] CreateAdditionalContent(TModel item); + protected virtual string HighlightedColumn => @"Performance"; + protected override Drawable CreateHeader(int index, TableColumn column) { var title = column?.Header ?? string.Empty; - var isHighlighted = HighlightedColumn() == title; - var isGrade = GradeColumns().Contains(title); - - return new HeaderText(title, isHighlighted) - { - Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } - }; + return new HeaderText(title, title == HighlightedColumn); } protected abstract Country GetCountry(TModel item); @@ -109,11 +105,7 @@ namespace osu.Game.Overlays.Rankings.Tables } }; - protected virtual IEnumerable GradeColumns() => new List(); - - protected virtual string HighlightedColumn() => @"Performance"; - - private class HeaderText : OsuSpriteText + protected class HeaderText : OsuSpriteText { private readonly bool isHighlighted; @@ -123,6 +115,7 @@ namespace osu.Game.Overlays.Rankings.Tables Text = text; Font = OsuFont.GetFont(size: 12); + Margin = new MarginPadding { Vertical = 5, Horizontal = 10 }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 370ee506c2..9fae8e1897 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -33,6 +33,6 @@ namespace osu.Game.Overlays.Rankings.Tables } }; - protected override string HighlightedColumn() => @"Ranked Score"; + protected override string HighlightedColumn => @"Ranked Score"; } } diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 8ce31c8539..a6969f483f 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -19,17 +19,20 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override IEnumerable GradeColumns() => new List { "SS", "S", "A" }; + protected virtual IEnumerable GradeColumns => new List { "SS", "S", "A" }; - protected override TableColumn[] CreateAdditionalHeaders() - { - var gradeColumns = GradeColumns().Select(grade => new TableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))); - - return new[] + protected override TableColumn[] CreateAdditionalHeaders() => new[] { new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - }.Concat(CreateUniqueHeaders()).Concat(gradeColumns).ToArray(); + }.Concat(CreateUniqueHeaders()) + .Concat(GradeColumns.Select(grade => new TableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)))) + .ToArray(); + + protected override Drawable CreateHeader(int index, TableColumn column) + { + var title = column?.Header ?? string.Empty; + return new UserTableHeaderText(title, HighlightedColumn == title, GradeColumns.Contains(title)); } protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; @@ -60,5 +63,19 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract TableColumn[] CreateUniqueHeaders(); protected abstract Drawable[] CreateUniqueContent(UserStatistics item); + + private class UserTableHeaderText : HeaderText + { + public UserTableHeaderText(string text, bool isHighlighted, bool isGrade) + : base(text, isHighlighted) + { + Margin = new MarginPadding + { + // Grade columns have extra horizontal padding for readibility + Horizontal = isGrade ? 20 : 10, + Vertical = 5 + }; + } + } } } From 67f0344c0c93f082ddaedf920fd741d2b25faff7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Jun 2021 23:07:44 +0300 Subject: [PATCH 20/74] Add support for loading shaders from ruleset resources --- .../UI/DrawableRulesetDependencies.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index e66a8c016c..b910f708a2 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; @@ -34,6 +35,11 @@ namespace osu.Game.Rulesets.UI /// public ISampleStore SampleStore { get; } + /// + /// The shader manager to be used for the ruleset. + /// + public ShaderManager ShaderManager { get; } + /// /// The ruleset config manager. /// @@ -52,6 +58,9 @@ namespace osu.Game.Rulesets.UI SampleStore = parent.Get().GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get())); + + ShaderManager = new ShaderManager(new NamespacedResourceStore(resources, @"Shaders")); + CacheAs(ShaderManager = new FallbackShaderManager(ShaderManager, parent.Get())); } RulesetConfigManager = parent.Get().GetConfigFor(ruleset); @@ -84,6 +93,7 @@ namespace osu.Game.Rulesets.UI SampleStore?.Dispose(); TextureStore?.Dispose(); + ShaderManager?.Dispose(); RulesetConfigManager = null; } @@ -172,5 +182,25 @@ namespace osu.Game.Rulesets.UI primary?.Dispose(); } } + + private class FallbackShaderManager : ShaderManager + { + private readonly ShaderManager primary; + private readonly ShaderManager fallback; + + public FallbackShaderManager(ShaderManager primary, ShaderManager fallback) + { + this.primary = primary; + this.fallback = fallback; + } + + public override byte[] LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name); + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + primary?.Dispose(); + } + } } } From eabcbd1d424761d762d8d4a8fb860956db6f1049 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Jun 2021 23:11:10 +0300 Subject: [PATCH 21/74] Consider shader manager for ruleset dependencies disposal testing --- .../TestSceneDrawableRulesetDependencies.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index f421a30283..94cf317c20 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -13,6 +13,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; @@ -31,12 +32,14 @@ namespace osu.Game.Tests.Rulesets DrawableWithDependencies drawable = null; TestTextureStore textureStore = null; TestSampleStore sampleStore = null; + TestShaderManager shaderManager = null; AddStep("add dependencies", () => { Child = drawable = new DrawableWithDependencies(); textureStore = drawable.ParentTextureStore; sampleStore = drawable.ParentSampleStore; + shaderManager = drawable.ParentShaderManager; }); AddStep("clear children", Clear); @@ -52,12 +55,14 @@ namespace osu.Game.Tests.Rulesets AddAssert("parent texture store not disposed", () => !textureStore.IsDisposed); AddAssert("parent sample store not disposed", () => !sampleStore.IsDisposed); + AddAssert("parent shader manager not disposed", () => !shaderManager.IsDisposed); } private class DrawableWithDependencies : CompositeDrawable { public TestTextureStore ParentTextureStore { get; private set; } public TestSampleStore ParentSampleStore { get; private set; } + public TestShaderManager ParentShaderManager { get; private set; } public DrawableWithDependencies() { @@ -70,6 +75,7 @@ namespace osu.Game.Tests.Rulesets dependencies.CacheAs(ParentTextureStore = new TestTextureStore()); dependencies.CacheAs(ParentSampleStore = new TestSampleStore()); + dependencies.CacheAs(ParentShaderManager = new TestShaderManager()); return new DrawableRulesetDependencies(new OsuRuleset(), dependencies); } @@ -135,5 +141,18 @@ namespace osu.Game.Tests.Rulesets public int PlaybackConcurrency { get; set; } } + + private class TestShaderManager : ShaderManager + { + public override byte[] LoadRaw(string name) => null; + + public bool IsDisposed { get; private set; } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + IsDisposed = true; + } + } } } From c933cbe89d0aedf6ddbef2c0f320595973a1aec5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 00:09:47 +0300 Subject: [PATCH 22/74] Add sample shaders and test case for ruleset-specific shaders --- .../Resources/Shaders/sh_TestFragment.fs | 11 +++++++ .../Resources/Shaders/sh_TestVertex.vs | 31 +++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 9 ++++++ 3 files changed, 51 insertions(+) create mode 100644 osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs create mode 100644 osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs diff --git a/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs b/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs new file mode 100644 index 0000000000..c70ad751be --- /dev/null +++ b/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs @@ -0,0 +1,11 @@ +#include "sh_Utils.h" + +varying mediump vec2 v_TexCoord; +varying mediump vec4 v_TexRect; + +void main(void) +{ + float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]); + gl_FragColor = hsv2rgb(vec4(hueValue, 1, 1, 1)); +} + diff --git a/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs new file mode 100644 index 0000000000..4485356fa4 --- /dev/null +++ b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs @@ -0,0 +1,31 @@ +#include "sh_Utils.h" + +attribute highp vec2 m_Position; +attribute lowp vec4 m_Colour; +attribute mediump vec2 m_TexCoord; +attribute mediump vec4 m_TexRect; +attribute mediump vec2 m_BlendRange; + +varying highp vec2 v_MaskingPosition; +varying lowp vec4 v_Colour; +varying mediump vec2 v_TexCoord; +varying mediump vec4 v_TexRect; +varying mediump vec2 v_BlendRange; + +uniform highp mat4 g_ProjMatrix; +uniform highp mat3 g_ToMaskingSpace; + +void main(void) +{ + // Transform from screen space to masking space. + highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0); + v_MaskingPosition = maskingPos.xy / maskingPos.z; + + v_Colour = m_Colour; + v_TexCoord = m_TexCoord; + v_TexRect = m_TexRect; + v_BlendRange = m_BlendRange; + + gl_Position = gProjMatrix * vec4(m_Position, 1.0, 1.0); +} + diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..433022788b 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Configuration.Tracking; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; @@ -45,6 +46,14 @@ namespace osu.Game.Tests.Testing Dependencies.Get().Get(@"test-sample") != null); } + [Test] + public void TestRetrieveShader() + { + AddAssert("ruleset shaders retrieved", () => + Dependencies.Get().LoadRaw(@"sh_TestVertex.vs") != null && + Dependencies.Get().LoadRaw(@"sh_TestFragment.fs") != null); + } + [Test] public void TestResolveConfigManager() { From 804a0433ccc40ed49e71b873b863a5507ee4dbaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:59:55 +0900 Subject: [PATCH 23/74] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1dc99bb60a..3c4380e355 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3c52405f8e..f91620bd25 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3689ce51f2..22c4340ba2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 2b1f461d79f8999dea041d4d10c2dd45543b1c62 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 12:24:57 +0300 Subject: [PATCH 24/74] Pass empty resource store for `FallbackShaderManager` base constructor --- osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index b910f708a2..e49e515d2e 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -189,6 +189,7 @@ namespace osu.Game.Rulesets.UI private readonly ShaderManager fallback; public FallbackShaderManager(ShaderManager primary, ShaderManager fallback) + : base(new ResourceStore()) { this.primary = primary; this.fallback = fallback; From bea828a36433c855f1765c5686848a0f44597adf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 13:17:34 +0300 Subject: [PATCH 25/74] Also pass empty resource in `TestSceneDrawableRulesetDependencies` --- .../Rulesets/TestSceneDrawableRulesetDependencies.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 94cf317c20..c357fccd27 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; @@ -144,6 +145,11 @@ namespace osu.Game.Tests.Rulesets private class TestShaderManager : ShaderManager { + public TestShaderManager() + : base(new ResourceStore()) + { + } + public override byte[] LoadRaw(string name) => null; public bool IsDisposed { get; private set; } From d1553f086413f786e4450aa3ac0333efd2e3359d Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 14:47:07 +0200 Subject: [PATCH 26/74] Implement ability to switch between volume meters --- .../Input/Bindings/GlobalActionContainer.cs | 9 +++ .../Overlays/Volume/VolumeControlReceptor.cs | 2 + osu.Game/Overlays/Volume/VolumeMeter.cs | 26 ++++++- osu.Game/Overlays/VolumeOverlay.cs | 69 +++++++++++++++---- 4 files changed, 91 insertions(+), 15 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c8227c0887..2482be90ee 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -103,6 +103,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.PreviousVolumeMeter), + new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.NextVolumeMeter), + new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), @@ -263,5 +266,11 @@ namespace osu.Game.Input.Bindings [Description("Toggle skin editor")] ToggleSkinEditor, + + [Description("Previous volume meter")] + PreviousVolumeMeter, + + [Description("Next volume meter")] + NextVolumeMeter, } } diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index ae9c2eb394..b24214ff3d 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -30,6 +30,8 @@ namespace osu.Game.Overlays.Volume return true; case GlobalAction.ToggleMute: + case GlobalAction.NextVolumeMeter: + case GlobalAction.PreviousVolumeMeter: ActionRequested?.Invoke(action); return true; } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 532b0f4a81..df8f1b7012 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -27,6 +28,11 @@ namespace osu.Game.Overlays.Volume { public class VolumeMeter : Container, IKeyBindingHandler { + [Resolved(canBeNull: true)] + private Bindable focusedMeter { get; set; } + + private bool isFocused => focusedMeter == null || focusedMeter.Value == this; + private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -41,6 +47,8 @@ namespace osu.Game.Overlays.Volume private Sample sample; private double sampleLastPlaybackTime; + public Action RequestFocus; + public VolumeMeter(string name, float circleSize, Color4 meterColour) { this.circleSize = circleSize; @@ -310,20 +318,32 @@ namespace osu.Game.Overlays.Volume private const float transition_length = 500; + public void Focus() + { + if (focusedMeter != null) + focusedMeter.Value = this; + + this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + } + + public void Unfocus() + { + this.ScaleTo(1f, transition_length, Easing.OutExpo); + } + protected override bool OnHover(HoverEvent e) { - this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + Focus(); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.ScaleTo(1f, transition_length, Easing.OutExpo); } public bool OnPressed(GlobalAction action) { - if (!IsHovered) + if (!IsHovered || !isFocused) return false; switch (action) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index eb639431ae..750d54e091 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -19,6 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { + [Cached] public class VolumeOverlay : VisibilityContainer { private const float offset = 10; @@ -32,6 +33,8 @@ namespace osu.Game.Overlays public Bindable IsMuted { get; } = new Bindable(); + private FillFlowContainer volumeMeters; + [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { @@ -53,7 +56,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding(10), Current = { BindTarget = IsMuted } }, - new FillFlowContainer + volumeMeters = new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, @@ -61,7 +64,7 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new Drawable[] + Children = new VolumeMeter[] { volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), @@ -81,8 +84,13 @@ namespace osu.Game.Overlays else audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); }); + + focusedMeter.BindValueChanged(meter => meter.OldValue?.Unfocus()); } + [Cached] + private Bindable focusedMeter = new Bindable(); + protected override void LoadComplete() { base.LoadComplete(); @@ -93,6 +101,23 @@ namespace osu.Game.Overlays muteButton.Current.ValueChanged += _ => Show(); } + public bool HandleAction(GlobalAction action) + { + if (!IsLoaded) return false; + + switch (action) + { + case GlobalAction.DecreaseVolume: + case GlobalAction.IncreaseVolume: + return Adjust(action); + case GlobalAction.NextVolumeMeter: + return true; + + } + + return true; + } + public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) { if (!IsLoaded) return false; @@ -102,25 +127,32 @@ namespace osu.Game.Overlays case GlobalAction.DecreaseVolume: if (State.Value == Visibility.Hidden) Show(); - else if (volumeMeterMusic.IsHovered) - volumeMeterMusic.Decrease(amount, isPrecise); - else if (volumeMeterEffect.IsHovered) - volumeMeterEffect.Decrease(amount, isPrecise); else - volumeMeterMaster.Decrease(amount, isPrecise); + focusedMeter.Value.Decrease(amount, isPrecise); return true; case GlobalAction.IncreaseVolume: if (State.Value == Visibility.Hidden) Show(); - else if (volumeMeterMusic.IsHovered) - volumeMeterMusic.Increase(amount, isPrecise); - else if (volumeMeterEffect.IsHovered) - volumeMeterEffect.Increase(amount, isPrecise); else - volumeMeterMaster.Increase(amount, isPrecise); + focusedMeter.Value.Increase(amount, isPrecise); return true; + case GlobalAction.NextVolumeMeter: + if (State.Value == Visibility.Hidden) + Show(); + else + focusShift(1); + return true; + + case GlobalAction.PreviousVolumeMeter: + if (State.Value == Visibility.Hidden) + Show(); + else + focusShift(-1); + return true; + + case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; @@ -130,10 +162,23 @@ namespace osu.Game.Overlays return false; } + private void focusShift(int direction = 1) + { + Show(); + var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; + if (newIndex < 0) + newIndex += volumeMeters.Count; + + volumeMeters.Children[newIndex % volumeMeters.Count].Focus(); + } + private ScheduledDelegate popOutDelegate; public override void Show() { + if (State.Value == Visibility.Hidden) + volumeMeterMaster.Focus(); + if (State.Value == Visibility.Visible) schedulePopOut(); From e151c7ffd017fe07aa7a6555dad88425b1fb1c1c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 15:31:43 +0200 Subject: [PATCH 27/74] Let VolumeMeter request focus instead of taking it --- osu.Game/Overlays/Volume/VolumeMeter.cs | 13 +++------- osu.Game/Overlays/VolumeOverlay.cs | 33 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index df8f1b7012..530a10ea7b 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -28,10 +27,7 @@ namespace osu.Game.Overlays.Volume { public class VolumeMeter : Container, IKeyBindingHandler { - [Resolved(canBeNull: true)] - private Bindable focusedMeter { get; set; } - - private bool isFocused => focusedMeter == null || focusedMeter.Value == this; + private bool isFocused = true; private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -320,20 +316,19 @@ namespace osu.Game.Overlays.Volume public void Focus() { - if (focusedMeter != null) - focusedMeter.Value = this; - + isFocused = true; this.ScaleTo(1.04f, transition_length, Easing.OutExpo); } public void Unfocus() { + isFocused = false; this.ScaleTo(1f, transition_length, Easing.OutExpo); } protected override bool OnHover(HoverEvent e) { - Focus(); + RequestFocus?.Invoke(this); return false; } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 750d54e091..c9321f5d60 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -19,7 +19,6 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - [Cached] public class VolumeOverlay : VisibilityContainer { private const float offset = 10; @@ -64,11 +63,20 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new VolumeMeter[] + Children = new [] { - volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), - volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), - volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), + volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) + { + RequestFocus = v => focusedMeter.Value = v + }, + volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker) + { + RequestFocus = v => focusedMeter.Value = v + }, + volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker) + { + RequestFocus = v => focusedMeter.Value = v + }, } } }); @@ -85,11 +93,14 @@ namespace osu.Game.Overlays audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); }); - focusedMeter.BindValueChanged(meter => meter.OldValue?.Unfocus()); + focusedMeter.BindValueChanged(meter => + { + meter.OldValue?.Unfocus(); + meter.NewValue?.Focus(); + }); } - [Cached] - private Bindable focusedMeter = new Bindable(); + private readonly Bindable focusedMeter = new Bindable(); protected override void LoadComplete() { @@ -165,19 +176,21 @@ namespace osu.Game.Overlays private void focusShift(int direction = 1) { Show(); + var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; if (newIndex < 0) newIndex += volumeMeters.Count; - volumeMeters.Children[newIndex % volumeMeters.Count].Focus(); + focusedMeter.Value = volumeMeters.Children[newIndex % volumeMeters.Count]; } private ScheduledDelegate popOutDelegate; public override void Show() { + // Focus on the master meter as a default if previously hidden if (State.Value == Visibility.Hidden) - volumeMeterMaster.Focus(); + focusedMeter.Value = volumeMeterMaster; if (State.Value == Visibility.Visible) schedulePopOut(); From d0707079b1011d2107a0fd00d3117bd83c78c6d4 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 15:35:51 +0200 Subject: [PATCH 28/74] Remove unused method --- osu.Game/Overlays/VolumeOverlay.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index c9321f5d60..cad2c81bb5 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -112,23 +112,6 @@ namespace osu.Game.Overlays muteButton.Current.ValueChanged += _ => Show(); } - public bool HandleAction(GlobalAction action) - { - if (!IsLoaded) return false; - - switch (action) - { - case GlobalAction.DecreaseVolume: - case GlobalAction.IncreaseVolume: - return Adjust(action); - case GlobalAction.NextVolumeMeter: - return true; - - } - - return true; - } - public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) { if (!IsLoaded) return false; From 50c9e17e52ff6963fda8574d520e8122fdf1b755 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 15:42:26 +0200 Subject: [PATCH 29/74] Return focus when using UP/DOWN on unfocused meter --- osu.Game/Overlays/Volume/VolumeMeter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 530a10ea7b..1c997a0abc 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -338,9 +338,13 @@ namespace osu.Game.Overlays.Volume public bool OnPressed(GlobalAction action) { - if (!IsHovered || !isFocused) + if (!IsHovered) return false; + // Grab back focus is UP/DOWN input is directed at this meter + if (!isFocused) + RequestFocus?.Invoke(this); + switch (action) { case GlobalAction.SelectPrevious: From 14a861003af7104d4ad06ca19f4ae299678eca79 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 16:06:28 +0200 Subject: [PATCH 30/74] Fix code quality errors --- osu.Game/Overlays/VolumeOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index cad2c81bb5..dca39774f2 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new [] + Children = new[] { volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) { @@ -146,7 +146,6 @@ namespace osu.Game.Overlays focusShift(-1); return true; - case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; From 69803105efbaee00bd3aa8494cb0d4bf5c74b99f Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 17:19:44 +0200 Subject: [PATCH 31/74] Fix volume meter requesting focus for any action --- osu.Game/Overlays/Volume/VolumeMeter.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 1c997a0abc..55f7588741 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -27,8 +27,6 @@ namespace osu.Game.Overlays.Volume { public class VolumeMeter : Container, IKeyBindingHandler { - private bool isFocused = true; - private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -316,13 +314,11 @@ namespace osu.Game.Overlays.Volume public void Focus() { - isFocused = true; this.ScaleTo(1.04f, transition_length, Easing.OutExpo); } public void Unfocus() { - isFocused = false; this.ScaleTo(1f, transition_length, Easing.OutExpo); } @@ -341,17 +337,15 @@ namespace osu.Game.Overlays.Volume if (!IsHovered) return false; - // Grab back focus is UP/DOWN input is directed at this meter - if (!isFocused) - RequestFocus?.Invoke(this); - switch (action) { case GlobalAction.SelectPrevious: + RequestFocus?.Invoke(this); adjust(1, false); return true; case GlobalAction.SelectNext: + RequestFocus?.Invoke(this); adjust(-1, false); return true; } From 3fe875efb2a28edfcd88036e83e81cfa67624c48 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 5 Jul 2021 15:47:35 +0200 Subject: [PATCH 32/74] Add glow to focused meter --- osu.Game/Overlays/Volume/VolumeMeter.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 7132d8e3ea..9210001a3a 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -38,6 +38,8 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; + private Container focusGlowContainer; + private Sample sample; private double sampleLastPlaybackTime; @@ -75,6 +77,25 @@ namespace osu.Game.Overlays.Volume Size = new Vector2(circleSize), Children = new Drawable[] { + focusGlowContainer = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = meterColour.Opacity(0.5f), + Radius = 5, + Hollow = true, + } + }, new BufferedContainer { Alpha = 0.9f, @@ -312,11 +333,13 @@ namespace osu.Game.Overlays.Volume public void Focus() { this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); } public void Unfocus() { this.ScaleTo(1f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); } protected override bool OnHover(HoverEvent e) From d495196b66d7d3b63cd3909f08778b21f1e071bf Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 5 Jul 2021 19:22:55 +0200 Subject: [PATCH 33/74] Share item cycling logic with GameplayMenuOverlay --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 34 ++++----- .../SelectionCycleFillFlowContainer.cs | 55 ++++++++++++++ .../Graphics/UserInterface/DialogButton.cs | 36 +++++---- .../Graphics/UserInterface/ISelectable.cs | 10 +++ osu.Game/Overlays/Volume/VolumeMeter.cs | 45 +++++++----- osu.Game/Overlays/VolumeOverlay.cs | 70 +++++++----------- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 73 +++++-------------- 7 files changed, 174 insertions(+), 149 deletions(-) create mode 100644 osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs create mode 100644 osu.Game/Graphics/UserInterface/ISelectable.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index d69ac665cc..0aafbda951 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); } /// @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected.Value); + AddAssert("First button selected", () => getButton(0).Selected); } /// @@ -111,11 +111,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); } /// @@ -127,11 +127,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); } /// @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); } /// @@ -162,11 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hide overlay", () => pauseOverlay.Hide()); showOverlay(); - AddAssert("First button not selected", () => !getButton(0).Selected.Value); + AddAssert("First button not selected", () => !getButton(0).Selected); AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1))); - AddAssert("First button selected", () => getButton(0).Selected.Value); + AddAssert("First button selected", () => getButton(0).Selected); } /// @@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); - AddAssert("First button not selected", () => !getButton(0).Selected.Value); - AddAssert("Second button selected", () => getButton(1).Selected.Value); + AddAssert("First button not selected", () => !getButton(0).Selected); + AddAssert("Second button selected", () => getButton(1).Selected); } /// @@ -196,8 +196,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Second button not selected", () => !getButton(1).Selected.Value); - AddAssert("First button selected", () => getButton(0).Selected.Value); + AddAssert("Second button not selected", () => !getButton(1).Selected); + AddAssert("First button selected", () => getButton(0).Selected); } /// @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition + AddAssert("First button selected", () => getButton(0).Selected); // Initial state condition } /// @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddAssert("No button selected", - () => pauseOverlay.Buttons.All(button => !button.Selected.Value)); + () => pauseOverlay.Buttons.All(button => !button.Selected)); } private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show()); diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs new file mode 100644 index 0000000000..0e7b2bcc05 --- /dev/null +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A FillFlowContainer that provides functionality to cycle selection between children + /// The selection wraps around when overflowing past the first or last child. + /// + public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, ISelectable + { + private int selectedIndex = -1; + + private void setSelected(int value) + { + if (selectedIndex == value) + return; + + // Deselect the previously-selected button + if (selectedIndex != -1) + this[selectedIndex].Selected = false; + + selectedIndex = value; + + // Select the newly-selected button + if (selectedIndex != -1) + this[selectedIndex].Selected = true; + } + + public void SelectNext() + { + if (selectedIndex == -1 || selectedIndex == Count - 1) + setSelected(0); + else + setSelected(selectedIndex + 1); + } + + public void SelectPrevious() + { + if (selectedIndex == -1 || selectedIndex == 0) + setSelected(Count - 1); + else + setSelected(selectedIndex - 1); + } + + public void Deselect() => setSelected(-1); + public void Select(T item) => setSelected(IndexOf(item)); + + public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex] : null; + } +} diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 1047aa4255..718a5171c2 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -2,24 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osuTK; -using osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Effects; -using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public class DialogButton : OsuClickableContainer + public class DialogButton : OsuClickableContainer, ISelectable { private const float idle_width = 0.8f; private const float hover_width = 0.9f; @@ -27,7 +27,13 @@ namespace osu.Game.Graphics.UserInterface private const float hover_duration = 500; private const float click_duration = 200; - public readonly BindableBool Selected = new BindableBool(); + public readonly BindableBool SelectedBindable = new BindableBool(); + + public bool Selected + { + get => SelectedBindable.Value; + set => SelectedBindable.Value = value; + } private readonly Container backgroundContainer; private readonly Container colourContainer; @@ -153,7 +159,7 @@ namespace osu.Game.Graphics.UserInterface updateGlow(); - Selected.ValueChanged += selectionChanged; + SelectedBindable.ValueChanged += selectionChanged; } private Color4 buttonColour; @@ -221,7 +227,7 @@ namespace osu.Game.Graphics.UserInterface .OnComplete(_ => { clickAnimating = false; - Selected.TriggerChange(); + SelectedBindable.TriggerChange(); }); return base.OnClick(e); @@ -235,7 +241,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnMouseUp(MouseUpEvent e) { - if (Selected.Value) + if (Selected) colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); base.OnMouseUp(e); } @@ -243,7 +249,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { base.OnHover(e); - Selected.Value = true; + Selected = true; return true; } @@ -251,7 +257,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - Selected.Value = false; + Selected = false; } private void selectionChanged(ValueChangedEvent args) diff --git a/osu.Game/Graphics/UserInterface/ISelectable.cs b/osu.Game/Graphics/UserInterface/ISelectable.cs new file mode 100644 index 0000000000..49c3d725c8 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ISelectable.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Graphics.UserInterface +{ + public interface ISelectable + { + bool Selected { get; set; } + } +} diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 9210001a3a..3e9849a077 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -19,13 +19,14 @@ using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container, IKeyBindingHandler + public class VolumeMeter : Container, IKeyBindingHandler, ISelectable { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -43,7 +44,13 @@ namespace osu.Game.Overlays.Volume private Sample sample; private double sampleLastPlaybackTime; - public Action RequestFocus; + public BindableBool SelectedBindable = new BindableBool(); + + public bool Selected + { + get => SelectedBindable.Value; + set => SelectedBindable.Value = value; + } public VolumeMeter(string name, float circleSize, Color4 meterColour) { @@ -212,6 +219,8 @@ namespace osu.Game.Overlays.Volume Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); bgProgress.Current.Value = 0.75f; + + SelectedBindable.ValueChanged += selectionChanged; } private int? displayVolumeInt; @@ -330,21 +339,9 @@ namespace osu.Game.Overlays.Volume private const float transition_length = 500; - public void Focus() - { - this.ScaleTo(1.04f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); - } - - public void Unfocus() - { - this.ScaleTo(1f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); - } - protected override bool OnHover(HoverEvent e) { - RequestFocus?.Invoke(this); + Selected = true; return false; } @@ -352,6 +349,20 @@ namespace osu.Game.Overlays.Volume { } + private void selectionChanged(ValueChangedEvent selected) + { + if (selected.NewValue) + { + this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); + } + else + { + this.ScaleTo(1f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); + } + } + public bool OnPressed(GlobalAction action) { if (!IsHovered) @@ -360,12 +371,12 @@ namespace osu.Game.Overlays.Volume switch (action) { case GlobalAction.SelectPrevious: - RequestFocus?.Invoke(this); + Selected = true; adjust(1, false); return true; case GlobalAction.SelectNext: - RequestFocus?.Invoke(this); + Selected = true; adjust(-1, false); return true; } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index dca39774f2..0c56e48cd4 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays public Bindable IsMuted { get; } = new Bindable(); - private FillFlowContainer volumeMeters; + private SelectionCycleFillFlowContainer volumeMeters; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) @@ -55,7 +56,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding(10), Current = { BindTarget = IsMuted } }, - volumeMeters = new FillFlowContainer + volumeMeters = new SelectionCycleFillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, @@ -65,18 +66,9 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Left = offset }, Children = new[] { - volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) - { - RequestFocus = v => focusedMeter.Value = v - }, - volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker) - { - RequestFocus = v => focusedMeter.Value = v - }, - volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker) - { - RequestFocus = v => focusedMeter.Value = v - }, + volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), + volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), + volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), } } }); @@ -92,23 +84,18 @@ namespace osu.Game.Overlays else audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); }); - - focusedMeter.BindValueChanged(meter => - { - meter.OldValue?.Unfocus(); - meter.NewValue?.Focus(); - }); } - private readonly Bindable focusedMeter = new Bindable(); - protected override void LoadComplete() { base.LoadComplete(); - volumeMeterMaster.Bindable.ValueChanged += _ => Show(); - volumeMeterEffect.Bindable.ValueChanged += _ => Show(); - volumeMeterMusic.Bindable.ValueChanged += _ => Show(); + foreach (var volumeMeter in volumeMeters) + { + volumeMeter.Bindable.ValueChanged += _ => Show(); + volumeMeter.SelectedBindable.ValueChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected.NewValue); + } + muteButton.Current.ValueChanged += _ => Show(); } @@ -122,28 +109,26 @@ namespace osu.Game.Overlays if (State.Value == Visibility.Hidden) Show(); else - focusedMeter.Value.Decrease(amount, isPrecise); + volumeMeters.Selected?.Decrease(amount, isPrecise); return true; case GlobalAction.IncreaseVolume: if (State.Value == Visibility.Hidden) Show(); else - focusedMeter.Value.Increase(amount, isPrecise); + volumeMeters.Selected?.Increase(amount, isPrecise); return true; case GlobalAction.NextVolumeMeter: - if (State.Value == Visibility.Hidden) - Show(); - else - focusShift(1); + if (State.Value == Visibility.Visible) + volumeMeters.SelectNext(); + Show(); return true; case GlobalAction.PreviousVolumeMeter: - if (State.Value == Visibility.Hidden) - Show(); - else - focusShift(-1); + if (State.Value == Visibility.Visible) + volumeMeters.SelectPrevious(); + Show(); return true; case GlobalAction.ToggleMute: @@ -155,15 +140,12 @@ namespace osu.Game.Overlays return false; } - private void focusShift(int direction = 1) + private void volumeMeterSelectionChanged(VolumeMeter meter, bool isSelected) { - Show(); - - var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; - if (newIndex < 0) - newIndex += volumeMeters.Count; - - focusedMeter.Value = volumeMeters.Children[newIndex % volumeMeters.Count]; + if (!isSelected) + volumeMeters.Deselect(); + else + volumeMeters.Select(meter); } private ScheduledDelegate popOutDelegate; @@ -172,7 +154,7 @@ namespace osu.Game.Overlays { // Focus on the master meter as a default if previously hidden if (State.Value == Visibility.Hidden) - focusedMeter.Value = volumeMeterMaster; + volumeMeters.Select(volumeMeterMaster); if (State.Value == Visibility.Visible) schedulePopOut(); diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 4a28da0dde..876f33d814 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -2,23 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using Humanizer; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; -using osu.Game.Graphics; -using osu.Framework.Allocation; -using osu.Game.Graphics.UserInterface; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using Humanizer; -using osu.Framework.Graphics.Effects; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -41,18 +42,18 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); + protected virtual Action BackAction => () => InternalButtons.Selected?.Click(); /// /// Action that is invoked when is triggered. /// - protected virtual Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected.Value)?.Click(); + protected virtual Action SelectAction => () => InternalButtons.Selected?.Click(); public abstract string Header { get; } public abstract string Description { get; } - protected ButtonContainer InternalButtons; + protected SelectionCycleFillFlowContainer InternalButtons; public IReadOnlyList Buttons => InternalButtons; private FillFlowContainer retryCounterContainer; @@ -116,7 +117,7 @@ namespace osu.Game.Screens.Play } } }, - InternalButtons = new ButtonContainer + InternalButtons = new SelectionCycleFillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -183,7 +184,7 @@ namespace osu.Game.Screens.Play } }; - button.Selected.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); + button.SelectedBindable.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); InternalButtons.Add(button); } @@ -255,46 +256,6 @@ namespace osu.Game.Screens.Play }; } - protected class ButtonContainer : FillFlowContainer - { - private int selectedIndex = -1; - - private void setSelected(int value) - { - if (selectedIndex == value) - return; - - // Deselect the previously-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected.Value = false; - - selectedIndex = value; - - // Select the newly-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected.Value = true; - } - - public void SelectNext() - { - if (selectedIndex == -1 || selectedIndex == Count - 1) - setSelected(0); - else - setSelected(selectedIndex + 1); - } - - public void SelectPrevious() - { - if (selectedIndex == -1 || selectedIndex == 0) - setSelected(Count - 1); - else - setSelected(selectedIndex - 1); - } - - public void Deselect() => setSelected(-1); - public void Select(DialogButton button) => setSelected(IndexOf(button)); - } - private class Button : DialogButton { // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved) @@ -302,7 +263,7 @@ namespace osu.Game.Screens.Play protected override bool OnMouseMove(MouseMoveEvent e) { - Selected.Value = true; + Selected = true; return base.OnMouseMove(e); } } From 93ef783339908aab6e10b0a5dbc530a67c0fa7c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:40:23 +0900 Subject: [PATCH 34/74] Remove BindableList usage --- .../Skinning/RulesetSkinProvidingContainer.cs | 18 +++-- osu.Game/Skinning/SkinProvidingContainer.cs | 77 ++++++------------- 2 files changed, 37 insertions(+), 58 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 19efc66814..4dea1ff51e 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . 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 JetBrains.Annotations; using osu.Framework.Allocation; @@ -72,31 +73,36 @@ namespace osu.Game.Skinning protected virtual void UpdateSkinSources() { - SkinSources.Clear(); + ResetSources(); + + var skinSources = new List(); foreach (var skin in parentSource.AllSources) { switch (skin) { case LegacySkin legacySkin: - SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + skinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); break; default: - SkinSources.Add(skin); + skinSources.Add(skin); break; } } - int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = skinSources.IndexOf(skinSources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. if (lastDefaultSkinIndex >= 0) - SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); + skinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else - SkinSources.Add(rulesetResourcesSkin); + skinSources.Add(rulesetResourcesSkin); + + foreach (var skin in skinSources) + AddSource(skin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index c83c299723..b1ada54a26 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -27,13 +26,13 @@ namespace osu.Game.Skinning /// /// Skins which should be exposed by this container, in order of lookup precedence. /// - protected readonly BindableList SkinSources = new BindableList(); + protected IEnumerable SkinSources => skinSources.Keys; /// /// A dictionary mapping each from the /// to one that performs the "allow lookup" checks before proceeding with a lookup. /// - private readonly Dictionary disableableSkinSources = new Dictionary(); + private readonly Dictionary skinSources = new Dictionary(); [CanBeNull] private ISkinSource fallbackSource; @@ -60,7 +59,7 @@ namespace osu.Game.Skinning : this() { if (skin != null) - SkinSources.Add(skin); + AddSource(skin); } /// @@ -70,61 +69,35 @@ namespace osu.Game.Skinning protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; + } - SkinSources.BindCollectionChanged(((_, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (var skin in args.NewItems.Cast()) - { - disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); + public void AddSource(ISkin skin) + { + skinSources.Add(skin, new DisableableSkinSource(skin, this)); - if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; - } + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } - break; + public void RemoveSource(ISkin skin) + { + skinSources.Remove(skin); - case NotifyCollectionChangedAction.Reset: - case NotifyCollectionChangedAction.Remove: - foreach (var skin in args.OldItems.Cast()) - { - disableableSkinSources.Remove(skin); + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } - if (skin is ISkinSource source) - source.SourceChanged -= OnSourceChanged; - } - - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var skin in args.OldItems.Cast()) - { - disableableSkinSources.Remove(skin); - - if (skin is ISkinSource source) - source.SourceChanged -= OnSourceChanged; - } - - foreach (var skin in args.NewItems.Cast()) - { - disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); - - if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; - } - - break; - } - }), true); + public void ResetSources() + { + foreach (var skin in AllSources.ToArray()) + RemoveSource(skin); } public ISkin FindProvider(Func lookupFunction) { foreach (var skin in SkinSources) { - if (lookupFunction(disableableSkinSources[skin])) + if (lookupFunction(skinSources[skin])) return skin; } @@ -151,7 +124,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { Drawable sourceDrawable; - if ((sourceDrawable = disableableSkinSources[skin]?.GetDrawableComponent(component)) != null) + if ((sourceDrawable = skinSources[skin]?.GetDrawableComponent(component)) != null) return sourceDrawable; } @@ -163,7 +136,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { Texture sourceTexture; - if ((sourceTexture = disableableSkinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + if ((sourceTexture = skinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } @@ -175,7 +148,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { ISample sourceSample; - if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null) + if ((sourceSample = skinSources[skin]?.GetSample(sampleInfo)) != null) return sourceSample; } @@ -187,7 +160,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { IBindable bindable; - if ((bindable = disableableSkinSources[skin]?.GetConfig(lookup)) != null) + if ((bindable = skinSources[skin]?.GetConfig(lookup)) != null) return bindable; } From 7ef7c5148f21c35426a45f4ae02d6a60755fc584 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 16:19:31 +0900 Subject: [PATCH 35/74] Add `ScrollingPath` for visualization of the real path of a `JuiceStream` --- .../Blueprints/Components/ScrollingPath.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs new file mode 100644 index 0000000000..337b8de92e --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class ScrollingPath : CompositeDrawable + { + private readonly Path drawablePath; + + private readonly List<(double Distance, float X)> vertices = new List<(double, float)>(); + + public ScrollingPath() + { + Anchor = Anchor.BottomLeft; + + InternalChildren = new Drawable[] + { + drawablePath = new SmoothPath + { + PathRadius = 2, + Alpha = 0.5f + }, + }; + } + + public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + { + X = hitObject.OriginalX; + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + } + + public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject) + { + double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity); + + computeDistanceXs(hitObject); + drawablePath.Vertices = vertices + .Select(v => new Vector2(v.X, (float)(v.Distance * distanceToYFactor))) + .ToArray(); + drawablePath.OriginPosition = drawablePath.PositionInBoundingBox(Vector2.Zero); + } + + private void computeDistanceXs(JuiceStream hitObject) + { + vertices.Clear(); + + var sliderVertices = new List(); + hitObject.Path.GetPathToProgress(sliderVertices, 0, 1); + + if (sliderVertices.Count == 0) + return; + + double distance = 0; + Vector2 lastPosition = Vector2.Zero; + + for (int repeat = 0; repeat < hitObject.RepeatCount + 1; repeat++) + { + foreach (var position in sliderVertices) + { + distance += Vector2.Distance(lastPosition, position); + lastPosition = position; + + vertices.Add((distance, position.X)); + } + + sliderVertices.Reverse(); + } + } + } +} From 0fa7716ceda7f8de41b1bf744bb5e98de3ff0d49 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 16:46:12 +0900 Subject: [PATCH 36/74] Show path of juice stream in selection blueprint --- .../JuiceStreamSelectionBlueprint.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index d6b8c35a09..9f9d6a0556 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -3,7 +3,9 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; using osuTK; @@ -17,9 +19,14 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private float minNestedX; private float maxNestedX; + private readonly ScrollingPath scrollingPath; + + private readonly Cached pathCache = new Cached(); + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { + InternalChild = scrollingPath = new ScrollingPath(); } [BackgroundDependencyLoader] @@ -29,7 +36,25 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints computeObjectBounds(); } - private void onDefaultsApplied(HitObject _) => computeObjectBounds(); + protected override void Update() + { + base.Update(); + + if (!IsSelected) return; + + scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); + + if (pathCache.IsValid) return; + + scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject); + pathCache.Validate(); + } + + private void onDefaultsApplied(HitObject _) + { + computeObjectBounds(); + pathCache.Invalidate(); + } private void computeObjectBounds() { From 935fbe7cc627f78deba63f33f1b0057d0470eb7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:41:48 +0900 Subject: [PATCH 37/74] Remove double fetch/binding of parent source --- .../Skinning/RulesetSkinProvidingContainer.cs | 17 +++---- osu.Game/Skinning/SkinProvidingContainer.cs | 44 ++++++++++++------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4dea1ff51e..d5be5d9394 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -47,22 +48,19 @@ namespace osu.Game.Skinning }; } - private ISkinSource parentSource; - private ResourceStoreBackedSkin rulesetResourcesSkin; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - parentSource = parent.Get(); - parentSource.SourceChanged += OnSourceChanged; - if (Ruleset.CreateResourceStore() is IResourceStore resources) rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get(), parent.Get()); + var dependencies = base.CreateChildDependencies(parent); + // ensure sources are populated and ready for use before childrens' asynchronous load flow. UpdateSkinSources(); - return base.CreateChildDependencies(parent); + return dependencies; } protected override void OnSourceChanged() @@ -77,7 +75,9 @@ namespace osu.Game.Skinning var skinSources = new List(); - foreach (var skin in parentSource.AllSources) + Debug.Assert(ParentSource != null); + + foreach (var skin in ParentSource.AllSources) { switch (skin) { @@ -121,9 +121,6 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (parentSource != null) - parentSource.SourceChanged -= OnSourceChanged; - rulesetResourcesSkin?.Dispose(); } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index b1ada54a26..a1e71502b9 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Skinning private readonly Dictionary skinSources = new Dictionary(); [CanBeNull] - private ISkinSource fallbackSource; + protected ISkinSource ParentSource { get; private set; } /// /// Whether falling back to parent s is allowed in this container. @@ -101,7 +101,10 @@ namespace osu.Game.Skinning return skin; } - return fallbackSource?.FindProvider(lookupFunction); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.FindProvider(lookupFunction); } public IEnumerable AllSources @@ -111,9 +114,9 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) yield return skin; - if (fallbackSource != null) + if (AllowFallingBackToParent && ParentSource != null) { - foreach (var skin in fallbackSource.AllSources) + foreach (var skin in ParentSource.AllSources) yield return skin; } } @@ -128,7 +131,10 @@ namespace osu.Game.Skinning return sourceDrawable; } - return fallbackSource?.GetDrawableComponent(component); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) @@ -140,7 +146,10 @@ namespace osu.Game.Skinning return sourceTexture; } - return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) @@ -152,7 +161,10 @@ namespace osu.Game.Skinning return sourceSample; } - return fallbackSource?.GetSample(sampleInfo); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) @@ -164,7 +176,10 @@ namespace osu.Game.Skinning return bindable; } - return fallbackSource?.GetConfig(lookup); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetConfig(lookup); } protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); @@ -173,12 +188,9 @@ namespace osu.Game.Skinning { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - if (AllowFallingBackToParent) - { - fallbackSource = dependencies.Get(); - if (fallbackSource != null) - fallbackSource.SourceChanged += OnSourceChanged; - } + ParentSource = dependencies.Get(); + if (ParentSource != null) + ParentSource.SourceChanged += OnSourceChanged; dependencies.CacheAs(this); @@ -192,8 +204,8 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); - if (fallbackSource != null) - fallbackSource.SourceChanged -= OnSourceChanged; + if (ParentSource != null) + ParentSource.SourceChanged -= OnSourceChanged; foreach (var source in SkinSources.OfType()) source.SourceChanged -= OnSourceChanged; From ec1224218c86587eb62137a96f4cd328ff0dcd35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:57:19 +0900 Subject: [PATCH 38/74] Localise source changed flow for better clarity --- osu.Game/Skinning/SkinProvidingContainer.cs | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a1e71502b9..5beadd7178 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Skinning skinSources.Add(skin, new DisableableSkinSource(skin, this)); if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; + source.SourceChanged += anySourceChanged; } public void RemoveSource(ISkin skin) @@ -84,7 +84,7 @@ namespace osu.Game.Skinning skinSources.Remove(skin); if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; + source.SourceChanged += anySourceChanged; } public void ResetSources() @@ -182,7 +182,10 @@ namespace osu.Game.Skinning return ParentSource?.GetConfig(lookup); } - protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); + /// + /// Invoked when any source has changed (either or + /// + protected virtual void OnSourceChanged() { } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -190,13 +193,21 @@ namespace osu.Game.Skinning ParentSource = dependencies.Get(); if (ParentSource != null) - ParentSource.SourceChanged += OnSourceChanged; + ParentSource.SourceChanged += anySourceChanged; dependencies.CacheAs(this); return dependencies; } + private void anySourceChanged() + { + // Expose to implementations, giving them a chance to react before notifying external consumers. + OnSourceChanged(); + + SourceChanged?.Invoke(); + } + protected override void Dispose(bool isDisposing) { // Must be done before base.Dispose() @@ -205,10 +216,10 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (ParentSource != null) - ParentSource.SourceChanged -= OnSourceChanged; + ParentSource.SourceChanged -= anySourceChanged; foreach (var source in SkinSources.OfType()) - source.SourceChanged -= OnSourceChanged; + source.SourceChanged -= anySourceChanged; } private class DisableableSkinSource : ISkin From b4240d3ca4db7d06599524612486f125f1a01f0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:04:59 +0900 Subject: [PATCH 39/74] Simplify lookups to avoid a second dictionary fetch --- osu.Game/Skinning/SkinProvidingContainer.cs | 33 ++++++++------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 5beadd7178..a6debcbf66 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -24,13 +24,7 @@ namespace osu.Game.Skinning public event Action SourceChanged; /// - /// Skins which should be exposed by this container, in order of lookup precedence. - /// - protected IEnumerable SkinSources => skinSources.Keys; - - /// - /// A dictionary mapping each from the - /// to one that performs the "allow lookup" checks before proceeding with a lookup. + /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// private readonly Dictionary skinSources = new Dictionary(); @@ -64,7 +58,6 @@ namespace osu.Game.Skinning /// /// Constructs a new with no sources. - /// Implementations can add or change sources through the list. /// protected SkinProvidingContainer() { @@ -95,9 +88,9 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - foreach (var skin in SkinSources) + foreach (var (skin, lookupWrapper) in skinSources) { - if (lookupFunction(skinSources[skin])) + if (lookupFunction(lookupWrapper)) return skin; } @@ -111,7 +104,7 @@ namespace osu.Game.Skinning { get { - foreach (var skin in SkinSources) + foreach (var skin in skinSources.Keys) yield return skin; if (AllowFallingBackToParent && ParentSource != null) @@ -124,10 +117,10 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { Drawable sourceDrawable; - if ((sourceDrawable = skinSources[skin]?.GetDrawableComponent(component)) != null) + if ((sourceDrawable = lookupWrapper.GetDrawableComponent(component)) != null) return sourceDrawable; } @@ -139,10 +132,10 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { Texture sourceTexture; - if ((sourceTexture = skinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + if ((sourceTexture = lookupWrapper.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } @@ -154,10 +147,10 @@ namespace osu.Game.Skinning public ISample GetSample(ISampleInfo sampleInfo) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { ISample sourceSample; - if ((sourceSample = skinSources[skin]?.GetSample(sampleInfo)) != null) + if ((sourceSample = lookupWrapper.GetSample(sampleInfo)) != null) return sourceSample; } @@ -169,10 +162,10 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { IBindable bindable; - if ((bindable = skinSources[skin]?.GetConfig(lookup)) != null) + if ((bindable = lookupWrapper.GetConfig(lookup)) != null) return bindable; } @@ -218,7 +211,7 @@ namespace osu.Game.Skinning if (ParentSource != null) ParentSource.SourceChanged -= anySourceChanged; - foreach (var source in SkinSources.OfType()) + foreach (var source in skinSources.Keys.OfType()) source.SourceChanged -= anySourceChanged; } From 1232925f93113846049c7be94a09b8cd14a76299 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:06:00 +0900 Subject: [PATCH 40/74] Make source manipulation methods `protected` --- osu.Game/Skinning/SkinProvidingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a6debcbf66..730659eacd 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - public void AddSource(ISkin skin) + protected void AddSource(ISkin skin) { skinSources.Add(skin, new DisableableSkinSource(skin, this)); @@ -72,7 +72,7 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } - public void RemoveSource(ISkin skin) + protected void RemoveSource(ISkin skin) { skinSources.Remove(skin); @@ -80,7 +80,7 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } - public void ResetSources() + protected void ResetSources() { foreach (var skin in AllSources.ToArray()) RemoveSource(skin); From 032c285edefbefa8aa2874bca356f9b295123caf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:07:25 +0900 Subject: [PATCH 41/74] Move private downwards --- osu.Game/Skinning/SkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 730659eacd..6b6fdce480 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -23,11 +23,6 @@ namespace osu.Game.Skinning { public event Action SourceChanged; - /// - /// A dictionary mapping each source to a wrapper which handles lookup allowances. - /// - private readonly Dictionary skinSources = new Dictionary(); - [CanBeNull] protected ISkinSource ParentSource { get; private set; } @@ -46,6 +41,11 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; + /// + /// A dictionary mapping each source to a wrapper which handles lookup allowances. + /// + private readonly Dictionary skinSources = new Dictionary(); + /// /// Constructs a new initialised with a single skin source. /// From 7833a1b09a32b7e7061f44935da8e8107d981489 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 17:15:27 +0900 Subject: [PATCH 42/74] Allow `FruitOutline` to be used for nested hit objects --- .../Edit/Blueprints/Components/FruitOutline.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs index 8769acc382..345b59bdcd 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { Anchor = Anchor.BottomLeft; Origin = Anchor.Centre; - Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS); InternalChild = new BorderPiece(); } @@ -28,10 +28,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Colour = osuColour.Yellow; } - public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null) { - X = hitObject.EffectiveX; - Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + X = hitObject.EffectiveX - (parent?.OriginalX ?? 0); + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current); Scale = new Vector2(hitObject.Scale); } } From 2ba300393495ab865ffe622a86772903ba809d9d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 17:15:51 +0900 Subject: [PATCH 43/74] Add nested fruit outlines to juice stream selection blueprint --- .../Components/NestedOutlineContainer.cs | 53 +++++++++++++++++++ .../JuiceStreamSelectionBlueprint.cs | 12 ++++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs new file mode 100644 index 0000000000..9e1743de98 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class NestedOutlineContainer : CompositeDrawable + { + private readonly Container nestedOutlines; + + private readonly List nestedHitObjects = new List(); + + public NestedOutlineContainer() + { + Anchor = Anchor.BottomLeft; + + InternalChild = nestedOutlines = new Container(); + } + + public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) + { + X = parentHitObject.OriginalX; + Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime); + } + + public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) + { + nestedHitObjects.Clear(); + nestedHitObjects.AddRange(parentHitObject.NestedHitObjects + .OfType() + .Where(h => !(h is TinyDroplet))); + + while (nestedHitObjects.Count < nestedOutlines.Count) + nestedOutlines.Remove(nestedOutlines[^1]); + + while (nestedOutlines.Count < nestedHitObjects.Count) + nestedOutlines.Add(new FruitOutline()); + + for (int i = 0; i < nestedHitObjects.Count; i++) + { + var hitObject = nestedHitObjects[i]; + nestedOutlines[i].UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + nestedOutlines[i].Scale *= hitObject is Droplet ? 0.5f : 1; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 9f9d6a0556..bf7b962e0a 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Caching; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; @@ -21,12 +22,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private readonly ScrollingPath scrollingPath; + private readonly NestedOutlineContainer nestedOutlineContainer; + private readonly Cached pathCache = new Cached(); public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { - InternalChild = scrollingPath = new ScrollingPath(); + InternalChildren = new Drawable[] + { + scrollingPath = new ScrollingPath(), + nestedOutlineContainer = new NestedOutlineContainer() + }; } [BackgroundDependencyLoader] @@ -43,10 +50,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints if (!IsSelected) return; scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject); if (pathCache.IsValid) return; scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject); + pathCache.Validate(); } From cd4885e4502feb0f28b3ea32c26fa13640e4c224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:18:45 +0900 Subject: [PATCH 44/74] Add xmldoc and remove any question of how the intitial flow is being run --- .../Skinning/RulesetSkinProvidingContainer.cs | 28 ++++++------------- osu.Game/Skinning/SkinProvidingContainer.cs | 21 ++++++++++++-- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index d5be5d9394..f5a7788359 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -55,25 +55,15 @@ namespace osu.Game.Skinning if (Ruleset.CreateResourceStore() is IResourceStore resources) rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get(), parent.Get()); - var dependencies = base.CreateChildDependencies(parent); - - // ensure sources are populated and ready for use before childrens' asynchronous load flow. - UpdateSkinSources(); - - return dependencies; + return base.CreateChildDependencies(parent); } protected override void OnSourceChanged() - { - UpdateSkinSources(); - base.OnSourceChanged(); - } - - protected virtual void UpdateSkinSources() { ResetSources(); - var skinSources = new List(); + // Populate a local list first so we can adjust the returned order as we go. + var sources = new List(); Debug.Assert(ParentSource != null); @@ -82,26 +72,26 @@ namespace osu.Game.Skinning switch (skin) { case LegacySkin legacySkin: - skinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + sources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); break; default: - skinSources.Add(skin); + sources.Add(skin); break; } } - int lastDefaultSkinIndex = skinSources.IndexOf(skinSources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. if (lastDefaultSkinIndex >= 0) - skinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); + sources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else - skinSources.Add(rulesetResourcesSkin); + sources.Add(rulesetResourcesSkin); - foreach (var skin in skinSources) + foreach (var skin in sources) AddSource(skin); } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6b6fdce480..6033776979 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,6 +64,10 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } + /// + /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. + /// + /// The skin to add. protected void AddSource(ISkin skin) { skinSources.Add(skin, new DisableableSkinSource(skin, this)); @@ -72,14 +76,22 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } + /// + /// Remove a skin from this provider. + /// + /// The skin to remove. protected void RemoveSource(ISkin skin) { - skinSources.Remove(skin); + if (!skinSources.Remove(skin)) + return; if (skin is ISkinSource source) - source.SourceChanged += anySourceChanged; + source.SourceChanged -= anySourceChanged; } + /// + /// Clears all skin sources. + /// protected void ResetSources() { foreach (var skin in AllSources.ToArray()) @@ -176,7 +188,8 @@ namespace osu.Game.Skinning } /// - /// Invoked when any source has changed (either or + /// Invoked when any source has changed (either or a source registered via ). + /// This is also invoked once initially during to ensure sources are ready for children consumption. /// protected virtual void OnSourceChanged() { } @@ -190,6 +203,8 @@ namespace osu.Game.Skinning dependencies.CacheAs(this); + anySourceChanged(); + return dependencies; } From d75d67577a1668749ffcb08600ce1440fc503256 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:37:34 +0900 Subject: [PATCH 45/74] Fix regressed tests --- .../Visual/Gameplay/TestSceneSkinnableDrawable.cs | 2 +- osu.Game/Skinning/BeatmapSkinProvidingContainer.cs | 6 +++--- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 02b1959dab..3e8ba69e01 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void Disable() { allow = false; - OnSourceChanged(); + TriggerSourceChanged(); } public SwitchableSkinProvidingContainer(ISkin skin) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index f12f44e347..57c08a903f 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -83,9 +83,9 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load() { - beatmapSkins.BindValueChanged(_ => OnSourceChanged()); - beatmapColours.BindValueChanged(_ => OnSourceChanged()); - beatmapHitsounds.BindValueChanged(_ => OnSourceChanged()); + beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); + beatmapColours.BindValueChanged(_ => TriggerSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6033776979..d2f38b58a4 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Skinning skinSources.Add(skin, new DisableableSkinSource(skin, this)); if (skin is ISkinSource source) - source.SourceChanged += anySourceChanged; + source.SourceChanged += TriggerSourceChanged; } /// @@ -86,7 +86,7 @@ namespace osu.Game.Skinning return; if (skin is ISkinSource source) - source.SourceChanged -= anySourceChanged; + source.SourceChanged -= TriggerSourceChanged; } /// @@ -199,16 +199,16 @@ namespace osu.Game.Skinning ParentSource = dependencies.Get(); if (ParentSource != null) - ParentSource.SourceChanged += anySourceChanged; + ParentSource.SourceChanged += TriggerSourceChanged; dependencies.CacheAs(this); - anySourceChanged(); + TriggerSourceChanged(); return dependencies; } - private void anySourceChanged() + protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. OnSourceChanged(); @@ -224,10 +224,10 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (ParentSource != null) - ParentSource.SourceChanged -= anySourceChanged; + ParentSource.SourceChanged -= TriggerSourceChanged; foreach (var source in skinSources.Keys.OfType()) - source.SourceChanged -= anySourceChanged; + source.SourceChanged -= TriggerSourceChanged; } private class DisableableSkinSource : ISkin From 32ef2405c4783f5a2db54f80bdfa5f24782a383c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 11:30:56 +0200 Subject: [PATCH 46/74] Use null instead of -1 --- .../SelectionCycleFillFlowContainer.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 0e7b2bcc05..19a423c25b 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -13,43 +13,51 @@ namespace osu.Game.Graphics.Containers /// public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, ISelectable { - private int selectedIndex = -1; + private int? selectedIndex; - private void setSelected(int value) + private void setSelected(int? value) { if (selectedIndex == value) return; // Deselect the previously-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected = false; + if (selectedIndex.HasValue) + this[selectedIndex.Value].Selected = false; selectedIndex = value; // Select the newly-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected = true; + if (selectedIndex.HasValue) + this[selectedIndex.Value].Selected = true; } public void SelectNext() { - if (selectedIndex == -1 || selectedIndex == Count - 1) + if (!selectedIndex.HasValue || selectedIndex == Count - 1) setSelected(0); else - setSelected(selectedIndex + 1); + setSelected(selectedIndex.Value + 1); } public void SelectPrevious() { - if (selectedIndex == -1 || selectedIndex == 0) + if (!selectedIndex.HasValue || selectedIndex == 0) setSelected(Count - 1); else - setSelected(selectedIndex - 1); + setSelected(selectedIndex.Value - 1); } - public void Deselect() => setSelected(-1); - public void Select(T item) => setSelected(IndexOf(item)); + public void Deselect() => setSelected(null); + public void Select(T item) + { + var newIndex = IndexOf(item); - public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex] : null; + if (newIndex < 0) + setSelected(null); + else + setSelected(IndexOf(item)); + } + + public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; } } From c5a0672277514f27cd2fb84b99669cf9c80f91c4 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 12:07:25 +0200 Subject: [PATCH 47/74] Use IStateful instead of ISelected --- .../SelectionCycleFillFlowContainer.cs | 7 ++-- .../Graphics/UserInterface/DialogButton.cs | 35 ++++++++++++------- .../Graphics/UserInterface/ISelectable.cs | 10 ------ osu.Game/Overlays/Volume/VolumeMeter.cs | 32 +++++++++++------ osu.Game/Overlays/VolumeOverlay.cs | 7 ++-- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 8 ++--- 6 files changed, 56 insertions(+), 43 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/ISelectable.cs diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 19a423c25b..5849fbbe30 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -11,7 +12,7 @@ namespace osu.Game.Graphics.Containers /// A FillFlowContainer that provides functionality to cycle selection between children /// The selection wraps around when overflowing past the first or last child. /// - public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, ISelectable + public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, IStateful { private int? selectedIndex; @@ -22,13 +23,13 @@ namespace osu.Game.Graphics.Containers // Deselect the previously-selected button if (selectedIndex.HasValue) - this[selectedIndex.Value].Selected = false; + this[selectedIndex.Value].State = SelectionState.NotSelected; selectedIndex = value; // Select the newly-selected button if (selectedIndex.HasValue) - this[selectedIndex.Value].Selected = true; + this[selectedIndex.Value].State = SelectionState.Selected; } public void SelectNext() diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 718a5171c2..f9fbec2b48 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -19,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public class DialogButton : OsuClickableContainer, ISelectable + public class DialogButton : OsuClickableContainer, IStateful { private const float idle_width = 0.8f; private const float hover_width = 0.9f; @@ -27,12 +29,21 @@ namespace osu.Game.Graphics.UserInterface private const float hover_duration = 500; private const float click_duration = 200; - public readonly BindableBool SelectedBindable = new BindableBool(); + public event Action StateChanged; - public bool Selected + private SelectionState state; + + public SelectionState State { - get => SelectedBindable.Value; - set => SelectedBindable.Value = value; + get => state; + set + { + if (state == value) + return; + + state = value; + StateChanged?.Invoke(value); + } } private readonly Container backgroundContainer; @@ -159,7 +170,7 @@ namespace osu.Game.Graphics.UserInterface updateGlow(); - SelectedBindable.ValueChanged += selectionChanged; + StateChanged += selectionChanged; } private Color4 buttonColour; @@ -227,7 +238,7 @@ namespace osu.Game.Graphics.UserInterface .OnComplete(_ => { clickAnimating = false; - SelectedBindable.TriggerChange(); + StateChanged?.Invoke(State); }); return base.OnClick(e); @@ -241,7 +252,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnMouseUp(MouseUpEvent e) { - if (Selected) + if (State == SelectionState.Selected) colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); base.OnMouseUp(e); } @@ -249,7 +260,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { base.OnHover(e); - Selected = true; + State = SelectionState.Selected; return true; } @@ -257,15 +268,15 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - Selected = false; + State = SelectionState.NotSelected; } - private void selectionChanged(ValueChangedEvent args) + private void selectionChanged(SelectionState newState) { if (clickAnimating) return; - if (args.NewValue) + if (newState == SelectionState.Selected) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); diff --git a/osu.Game/Graphics/UserInterface/ISelectable.cs b/osu.Game/Graphics/UserInterface/ISelectable.cs deleted file mode 100644 index 49c3d725c8..0000000000 --- a/osu.Game/Graphics/UserInterface/ISelectable.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Graphics.UserInterface -{ - public interface ISelectable - { - bool Selected { get; set; } - } -} diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 3e9849a077..a7c4fb6e7d 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -26,7 +27,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container, IKeyBindingHandler, ISelectable + public class VolumeMeter : Container, IKeyBindingHandler, IStateful { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -44,12 +45,21 @@ namespace osu.Game.Overlays.Volume private Sample sample; private double sampleLastPlaybackTime; - public BindableBool SelectedBindable = new BindableBool(); + public event Action StateChanged; - public bool Selected + private SelectionState state; + + public SelectionState State { - get => SelectedBindable.Value; - set => SelectedBindable.Value = value; + get => state; + set + { + if (state == value) + return; + + state = value; + StateChanged?.Invoke(value); + } } public VolumeMeter(string name, float circleSize, Color4 meterColour) @@ -220,7 +230,7 @@ namespace osu.Game.Overlays.Volume bgProgress.Current.Value = 0.75f; - SelectedBindable.ValueChanged += selectionChanged; + StateChanged += stateChanged; } private int? displayVolumeInt; @@ -341,7 +351,7 @@ namespace osu.Game.Overlays.Volume protected override bool OnHover(HoverEvent e) { - Selected = true; + State = SelectionState.Selected; return false; } @@ -349,9 +359,9 @@ namespace osu.Game.Overlays.Volume { } - private void selectionChanged(ValueChangedEvent selected) + private void stateChanged(SelectionState newState) { - if (selected.NewValue) + if (newState == SelectionState.Selected) { this.ScaleTo(1.04f, transition_length, Easing.OutExpo); focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); @@ -371,12 +381,12 @@ namespace osu.Game.Overlays.Volume switch (action) { case GlobalAction.SelectPrevious: - Selected = true; + State = SelectionState.Selected; adjust(1, false); return true; case GlobalAction.SelectNext: - Selected = true; + State = SelectionState.Selected; adjust(-1, false); return true; } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 0c56e48cd4..56aa62d6e4 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; @@ -93,7 +94,7 @@ namespace osu.Game.Overlays foreach (var volumeMeter in volumeMeters) { volumeMeter.Bindable.ValueChanged += _ => Show(); - volumeMeter.SelectedBindable.ValueChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected.NewValue); + volumeMeter.StateChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected); } muteButton.Current.ValueChanged += _ => Show(); @@ -140,9 +141,9 @@ namespace osu.Game.Overlays return false; } - private void volumeMeterSelectionChanged(VolumeMeter meter, bool isSelected) + private void volumeMeterSelectionChanged(VolumeMeter meter, SelectionState state) { - if (!isSelected) + if (state == SelectionState.NotSelected) volumeMeters.Deselect(); else volumeMeters.Select(meter); diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 876f33d814..32f7a93cef 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play } }; - button.SelectedBindable.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); + button.StateChanged += selected => buttonSelectionChanged(button, selected); InternalButtons.Add(button); } @@ -217,9 +217,9 @@ namespace osu.Game.Screens.Play { } - private void buttonSelectionChanged(DialogButton button, bool isSelected) + private void buttonSelectionChanged(DialogButton button, SelectionState state) { - if (!isSelected) + if (state == SelectionState.NotSelected) InternalButtons.Deselect(); else InternalButtons.Select(button); @@ -263,7 +263,7 @@ namespace osu.Game.Screens.Play protected override bool OnMouseMove(MouseMoveEvent e) { - Selected = true; + State = SelectionState.Selected; return base.OnMouseMove(e); } } From 7b21d1ecf9ac251e697d65e626044ed3d5244e2c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 19:50:32 +0900 Subject: [PATCH 48/74] Fix juice stream outline disappears away when start position is outside the screen. --- .../Components/NestedOutlineContainer.cs | 20 +++++++++---------- .../Blueprints/Components/ScrollingPath.cs | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs index 9e1743de98..48d90e8b24 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -12,15 +13,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { public class NestedOutlineContainer : CompositeDrawable { - private readonly Container nestedOutlines; - private readonly List nestedHitObjects = new List(); public NestedOutlineContainer() { Anchor = Anchor.BottomLeft; - - InternalChild = nestedOutlines = new Container(); } public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) @@ -36,18 +33,21 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components .OfType() .Where(h => !(h is TinyDroplet))); - while (nestedHitObjects.Count < nestedOutlines.Count) - nestedOutlines.Remove(nestedOutlines[^1]); + while (nestedHitObjects.Count < InternalChildren.Count) + RemoveInternal(InternalChildren[^1]); - while (nestedOutlines.Count < nestedHitObjects.Count) - nestedOutlines.Add(new FruitOutline()); + while (InternalChildren.Count < nestedHitObjects.Count) + AddInternal(new FruitOutline()); for (int i = 0; i < nestedHitObjects.Count; i++) { var hitObject = nestedHitObjects[i]; - nestedOutlines[i].UpdateFrom(hitObjectContainer, hitObject, parentHitObject); - nestedOutlines[i].Scale *= hitObject is Droplet ? 0.5f : 1; + var outline = (FruitOutline)InternalChildren[i]; + outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + outline.Scale *= hitObject is Droplet ? 0.5f : 1; } } + + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs index 337b8de92e..96111beda4 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -75,5 +76,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components sliderVertices.Reverse(); } } + + // Because this has 0x0 size, the contents are otherwise masked away if the start position is outside the screen. + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; } } From 07d54d261a0359b7a2df15e685ee0874b2cbb4bf Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 13:24:18 +0200 Subject: [PATCH 49/74] Let selection container handle manual selection changes --- .../SelectionCycleFillFlowContainer.cs | 31 +++++++++++++++++++ osu.Game/Overlays/VolumeOverlay.cs | 12 ------- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 12 ------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 5849fbbe30..b48a697903 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -60,5 +62,34 @@ namespace osu.Game.Graphics.Containers } public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; + + private readonly Dictionary> handlerMap = new Dictionary>(); + + public override void Add(T drawable) + { + // This event is used to update selection state when modified within the drawable itself. + // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container + drawable.StateChanged += handlerMap[drawable] = state => selectionChanged(drawable, state); + + base.Add(drawable); + } + + public override bool Remove(T drawable) + { + if (!base.Remove(drawable)) + return false; + + drawable.StateChanged -= handlerMap[drawable]; + handlerMap.Remove(drawable); + return true; + } + + private void selectionChanged(T drawable, SelectionState state) + { + if (state == SelectionState.NotSelected) + Deselect(); + else + Select(drawable); + } } } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 56aa62d6e4..a96949e96f 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; @@ -92,10 +91,7 @@ namespace osu.Game.Overlays base.LoadComplete(); foreach (var volumeMeter in volumeMeters) - { volumeMeter.Bindable.ValueChanged += _ => Show(); - volumeMeter.StateChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected); - } muteButton.Current.ValueChanged += _ => Show(); } @@ -141,14 +137,6 @@ namespace osu.Game.Overlays return false; } - private void volumeMeterSelectionChanged(VolumeMeter meter, SelectionState state) - { - if (state == SelectionState.NotSelected) - volumeMeters.Deselect(); - else - volumeMeters.Select(meter); - } - private ScheduledDelegate popOutDelegate; public override void Show() diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 32f7a93cef..c71dd2ce65 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -184,8 +183,6 @@ namespace osu.Game.Screens.Play } }; - button.StateChanged += selected => buttonSelectionChanged(button, selected); - InternalButtons.Add(button); } @@ -216,15 +213,6 @@ namespace osu.Game.Screens.Play public void OnReleased(GlobalAction action) { } - - private void buttonSelectionChanged(DialogButton button, SelectionState state) - { - if (state == SelectionState.NotSelected) - InternalButtons.Deselect(); - else - InternalButtons.Select(button); - } - private void updateRetryCount() { // "You've retried 1,065 times in this session" From ffe18ebe516e20ea367e836908d51d435d455f65 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 14:11:46 +0200 Subject: [PATCH 50/74] Resolve build errors --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 34 +++++++++---------- .../SelectionCycleFillFlowContainer.cs | 12 +++++-- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 1 + 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 0aafbda951..d6a50fc346 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().State == SelectionState.Selected); } /// @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected); + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); } /// @@ -111,11 +111,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected); } /// @@ -127,11 +127,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected); } /// @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.State == SelectionState.Selected)); } /// @@ -162,11 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hide overlay", () => pauseOverlay.Hide()); showOverlay(); - AddAssert("First button not selected", () => !getButton(0).Selected); + AddAssert("First button not selected", () => getButton(0).State == SelectionState.NotSelected); AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1))); - AddAssert("First button selected", () => getButton(0).Selected); + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); } /// @@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); - AddAssert("First button not selected", () => !getButton(0).Selected); - AddAssert("Second button selected", () => getButton(1).Selected); + AddAssert("First button not selected", () => getButton(0).State == SelectionState.NotSelected); + AddAssert("Second button selected", () => getButton(1).State == SelectionState.Selected); } /// @@ -196,8 +196,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Second button not selected", () => !getButton(1).Selected); - AddAssert("First button selected", () => getButton(0).Selected); + AddAssert("Second button not selected", () => getButton(1).State == SelectionState.NotSelected); + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); } /// @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected); // Initial state condition + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); // Initial state condition } /// @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddAssert("No button selected", - () => pauseOverlay.Buttons.All(button => !button.Selected)); + () => pauseOverlay.Buttons.All(button => button.State == SelectionState.NotSelected)); } private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show()); diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index b48a697903..3dbe1f8f47 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -69,7 +69,9 @@ namespace osu.Game.Graphics.Containers { // This event is used to update selection state when modified within the drawable itself. // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - drawable.StateChanged += handlerMap[drawable] = state => selectionChanged(drawable, state); + handlerMap[drawable] = state => selectionChanged(drawable, state); + + drawable.StateChanged += handlerMap[drawable]; base.Add(drawable); } @@ -79,8 +81,12 @@ namespace osu.Game.Graphics.Containers if (!base.Remove(drawable)) return false; - drawable.StateChanged -= handlerMap[drawable]; - handlerMap.Remove(drawable); + if (handlerMap.TryGetValue(drawable, out var action)) + { + drawable.StateChanged -= action; + handlerMap.Remove(drawable); + } + return true; } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index c71dd2ce65..6fcc70e5f2 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -213,6 +213,7 @@ namespace osu.Game.Screens.Play public void OnReleased(GlobalAction action) { } + private void updateRetryCount() { // "You've retried 1,065 times in this session" From 4b1b5a88fe0dbef52f1297a06f584468fba3ad54 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 14:39:53 +0200 Subject: [PATCH 51/74] Add null check to supress quality errors --- .../SelectionCycleFillFlowContainer.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 3dbe1f8f47..4f20c0039b 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -18,6 +18,8 @@ namespace osu.Game.Graphics.Containers { private int? selectedIndex; + public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; + private void setSelected(int? value) { if (selectedIndex == value) @@ -51,6 +53,7 @@ namespace osu.Game.Graphics.Containers } public void Deselect() => setSelected(null); + public void Select(T item) { var newIndex = IndexOf(item); @@ -61,19 +64,20 @@ namespace osu.Game.Graphics.Containers setSelected(IndexOf(item)); } - public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; - private readonly Dictionary> handlerMap = new Dictionary>(); public override void Add(T drawable) { - // This event is used to update selection state when modified within the drawable itself. - // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - handlerMap[drawable] = state => selectionChanged(drawable, state); - - drawable.StateChanged += handlerMap[drawable]; - base.Add(drawable); + + if (drawable != null) + { + // This event is used to update selection state when modified within the drawable itself. + // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container + handlerMap[drawable] = state => selectionChanged(drawable, state); + + drawable.StateChanged += handlerMap[drawable]; + } } public override bool Remove(T drawable) @@ -81,7 +85,7 @@ namespace osu.Game.Graphics.Containers if (!base.Remove(drawable)) return false; - if (handlerMap.TryGetValue(drawable, out var action)) + if (drawable != null && handlerMap.TryGetValue(drawable, out var action)) { drawable.StateChanged -= action; handlerMap.Remove(drawable); From 4451598bcfe8f5b62b3f19c0b108290bfd448d09 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 15:17:19 +0200 Subject: [PATCH 52/74] Fix remaining quality complaints --- osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs | 2 +- osu.Game/Graphics/UserInterface/DialogButton.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index d6a50fc346..ed40a83831 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.State == SelectionState.Selected)); + AddAssert("Overlay state is reset", () => failOverlay.Buttons.All(b => b.State == SelectionState.NotSelected)); } /// diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index f9fbec2b48..2d75dad828 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -3,7 +3,6 @@ using System; using osu.Framework; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; From 255f7b7b532a25276272331a4e224c9389a6931a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 6 Jul 2021 16:32:02 +0300 Subject: [PATCH 53/74] Add failing test scene --- .../Skins/TestSceneSkinProvidingContainer.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs new file mode 100644 index 0000000000..cfc4ccd208 --- /dev/null +++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . 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 NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Skins +{ + public class TestSceneSkinProvidingContainer : OsuTestScene + { + /// + /// Ensures that the first inserted skin after resetting (via source change) + /// is always prioritised over others when providing the same resource. + /// + [Test] + public void TestPriorityPreservation() + { + TestSkinProvidingContainer provider = null; + TestSkin mostPrioritisedSource = null; + + AddStep("setup sources", () => + { + var sources = new List(); + for (int i = 0; i < 10; i++) + sources.Add(new TestSkin()); + + mostPrioritisedSource = sources.First(); + + Child = provider = new TestSkinProvidingContainer(sources); + }); + + AddAssert("texture provided by expected skin", () => + { + return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource; + }); + + AddStep("trigger source change", () => provider.TriggerSourceChanged()); + + AddAssert("texture still provided by expected skin", () => + { + return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource; + }); + } + + private class TestSkinProvidingContainer : SkinProvidingContainer + { + private readonly IEnumerable sources; + + public TestSkinProvidingContainer(IEnumerable sources) + { + this.sources = sources; + } + + public new void TriggerSourceChanged() => base.TriggerSourceChanged(); + + protected override void OnSourceChanged() + { + ResetSources(); + sources.ForEach(AddSource); + } + } + + private class TestSkin : ISkin + { + public const string TEXTURE_NAME = "virtual-texture"; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new System.NotImplementedException(); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + if (componentName == TEXTURE_NAME) + return Texture.WhitePixel; + + return null; + } + + public ISample GetSample(ISampleInfo sampleInfo) => throw new System.NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) => throw new System.NotImplementedException(); + } + } +} From 523546d8b636239a6e6213c0104439eefc9ecbb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 22:51:56 +0900 Subject: [PATCH 54/74] Use List to guarantee lookup order --- osu.Game/Skinning/SkinProvidingContainer.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d2f38b58a4..a628e97578 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Skinning /// /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// - private readonly Dictionary skinSources = new Dictionary(); + private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>(); /// /// Constructs a new initialised with a single skin source. @@ -70,7 +70,7 @@ namespace osu.Game.Skinning /// The skin to add. protected void AddSource(ISkin skin) { - skinSources.Add(skin, new DisableableSkinSource(skin, this)); + skinSources.Add((skin, new DisableableSkinSource(skin, this))); if (skin is ISkinSource source) source.SourceChanged += TriggerSourceChanged; @@ -82,7 +82,7 @@ namespace osu.Game.Skinning /// The skin to remove. protected void RemoveSource(ISkin skin) { - if (!skinSources.Remove(skin)) + if (skinSources.RemoveAll(s => s.skin == skin) == 0) return; if (skin is ISkinSource source) @@ -116,8 +116,8 @@ namespace osu.Game.Skinning { get { - foreach (var skin in skinSources.Keys) - yield return skin; + foreach (var i in skinSources) + yield return i.skin; if (AllowFallingBackToParent && ParentSource != null) { @@ -226,8 +226,11 @@ namespace osu.Game.Skinning if (ParentSource != null) ParentSource.SourceChanged -= TriggerSourceChanged; - foreach (var source in skinSources.Keys.OfType()) - source.SourceChanged -= TriggerSourceChanged; + foreach (var i in skinSources) + { + if (i.skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; + } } private class DisableableSkinSource : ISkin From f45418dde7b50ee63bd7c12d686cde40022c0617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Jul 2021 00:15:17 +0200 Subject: [PATCH 55/74] Replace game-side directory/file selector with framework extensions --- .../Settings/TestSceneDirectorySelector.cs | 2 +- .../Visual/Settings/TestSceneFileSelector.cs | 4 +- .../Screens/Setup/StablePathSelectScreen.cs | 4 +- .../UserInterfaceV2/DirectorySelector.cs | 297 ------------------ .../Graphics/UserInterfaceV2/FileSelector.cs | 94 ------ .../UserInterfaceV2/OsuDirectorySelector.cs | 140 +++++++++ .../UserInterfaceV2/OsuFileSelector.cs | 90 ++++++ .../Maintenance/DirectorySelectScreen.cs | 4 +- .../Edit/Setup/FileChooserLabelledTextBox.cs | 4 +- osu.Game/Screens/Import/FileImportScreen.cs | 4 +- 10 files changed, 241 insertions(+), 402 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs delete mode 100644 osu.Game/Graphics/UserInterfaceV2/FileSelector.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs index 082d85603e..227bce0c60 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Settings [BackgroundDependencyLoader] private void load() { - Add(new DirectorySelector { RelativeSizeAxes = Axes.Both }); + Add(new OsuDirectorySelector { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs index 311e4c3362..84a0fc6e4c 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs @@ -12,13 +12,13 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestAllFiles() { - AddStep("create", () => Child = new FileSelector { RelativeSizeAxes = Axes.Both }); + AddStep("create", () => Child = new OsuFileSelector { RelativeSizeAxes = Axes.Both }); } [Test] public void TestJpgFilesOnly() { - AddStep("create", () => Child = new FileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both }); + AddStep("create", () => Child = new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 03f79b644f..3752d9d3be 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Setup [Resolved] private MatchIPCInfo ipc { get; set; } - private DirectorySelector directorySelector; + private OsuDirectorySelector directorySelector; private DialogOverlay overlay; [BackgroundDependencyLoader(true)] @@ -79,7 +79,7 @@ namespace osu.Game.Tournament.Screens.Setup }, new Drawable[] { - directorySelector = new DirectorySelector(initialPath) + directorySelector = new OsuDirectorySelector(initialPath) { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs deleted file mode 100644 index a1cd074619..0000000000 --- a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.IO; -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.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Framework.Platform; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Graphics.UserInterfaceV2 -{ - public class DirectorySelector : CompositeDrawable - { - private FillFlowContainer directoryFlow; - - [Resolved] - private GameHost host { get; set; } - - [Cached] - public readonly Bindable CurrentPath = new Bindable(); - - public DirectorySelector(string initialPath = null) - { - CurrentPath.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); - } - - [BackgroundDependencyLoader] - private void load() - { - Padding = new MarginPadding(10); - - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 50), - new Dimension(), - }, - Content = new[] - { - new Drawable[] - { - new CurrentDirectoryDisplay - { - RelativeSizeAxes = Axes.Both, - }, - }, - new Drawable[] - { - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = directoryFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2), - } - } - } - } - }; - - CurrentPath.BindValueChanged(updateDisplay, true); - } - - private void updateDisplay(ValueChangedEvent directory) - { - directoryFlow.Clear(); - - try - { - if (directory.NewValue == null) - { - var drives = DriveInfo.GetDrives(); - - foreach (var drive in drives) - directoryFlow.Add(new DirectoryPiece(drive.RootDirectory)); - } - else - { - directoryFlow.Add(new ParentDirectoryPiece(CurrentPath.Value.Parent)); - - directoryFlow.AddRange(GetEntriesForPath(CurrentPath.Value)); - } - } - catch (Exception) - { - CurrentPath.Value = directory.OldValue; - this.FlashColour(Color4.Red, 300); - } - } - - protected virtual IEnumerable GetEntriesForPath(DirectoryInfo path) - { - foreach (var dir in path.GetDirectories().OrderBy(d => d.Name)) - { - if ((dir.Attributes & FileAttributes.Hidden) == 0) - yield return new DirectoryPiece(dir); - } - } - - private class CurrentDirectoryDisplay : CompositeDrawable - { - [Resolved] - private Bindable currentDirectory { get; set; } - - private FillFlowContainer flow; - - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - flow = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Spacing = new Vector2(5), - Height = DisplayPiece.HEIGHT, - Direction = FillDirection.Horizontal, - }, - }; - - currentDirectory.BindValueChanged(updateDisplay, true); - } - - private void updateDisplay(ValueChangedEvent dir) - { - flow.Clear(); - - List pathPieces = new List(); - - DirectoryInfo ptr = dir.NewValue; - - while (ptr != null) - { - pathPieces.Insert(0, new CurrentDisplayPiece(ptr)); - ptr = ptr.Parent; - } - - flow.ChildrenEnumerable = new Drawable[] - { - new OsuSpriteText { Text = "Current Directory: ", Font = OsuFont.Default.With(size: DisplayPiece.HEIGHT), }, - new ComputerPiece(), - }.Concat(pathPieces); - } - - private class ComputerPiece : CurrentDisplayPiece - { - protected override IconUsage? Icon => null; - - public ComputerPiece() - : base(null, "Computer") - { - } - } - - private class CurrentDisplayPiece : DirectoryPiece - { - public CurrentDisplayPiece(DirectoryInfo directory, string displayName = null) - : base(directory, displayName) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Flow.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(FONT_SIZE / 2) - }); - } - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; - } - } - - private class ParentDirectoryPiece : DirectoryPiece - { - protected override IconUsage? Icon => FontAwesome.Solid.Folder; - - public ParentDirectoryPiece(DirectoryInfo directory) - : base(directory, "..") - { - } - } - - protected class DirectoryPiece : DisplayPiece - { - protected readonly DirectoryInfo Directory; - - [Resolved] - private Bindable currentDirectory { get; set; } - - public DirectoryPiece(DirectoryInfo directory, string displayName = null) - : base(displayName) - { - Directory = directory; - } - - protected override bool OnClick(ClickEvent e) - { - currentDirectory.Value = Directory; - return true; - } - - protected override string FallbackName => Directory.Name; - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) - ? FontAwesome.Solid.Database - : FontAwesome.Regular.Folder; - } - - protected abstract class DisplayPiece : CompositeDrawable - { - public const float HEIGHT = 20; - - protected const float FONT_SIZE = 16; - - private readonly string displayName; - - protected FillFlowContainer Flow; - - protected DisplayPiece(string displayName = null) - { - this.displayName = displayName; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.Both; - - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.GreySeafoamDarker, - RelativeSizeAxes = Axes.Both, - }, - Flow = new FillFlowContainer - { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Vertical = 2, Horizontal = 5 }, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - } - }; - - if (Icon.HasValue) - { - Flow.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = Icon.Value, - Size = new Vector2(FONT_SIZE) - }); - } - - Flow.Add(new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = displayName ?? FallbackName, - Font = OsuFont.Default.With(size: FONT_SIZE) - }); - } - - protected abstract string FallbackName { get; } - - protected abstract IconUsage? Icon { get; } - } - } -} diff --git a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs deleted file mode 100644 index e10b8f7033..0000000000 --- a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.IO; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; - -namespace osu.Game.Graphics.UserInterfaceV2 -{ - public class FileSelector : DirectorySelector - { - private readonly string[] validFileExtensions; - - [Cached] - public readonly Bindable CurrentFile = new Bindable(); - - public FileSelector(string initialPath = null, string[] validFileExtensions = null) - : base(initialPath) - { - this.validFileExtensions = validFileExtensions ?? Array.Empty(); - } - - protected override IEnumerable GetEntriesForPath(DirectoryInfo path) - { - foreach (var dir in base.GetEntriesForPath(path)) - yield return dir; - - IEnumerable files = path.GetFiles(); - - if (validFileExtensions.Length > 0) - files = files.Where(f => validFileExtensions.Contains(f.Extension)); - - foreach (var file in files.OrderBy(d => d.Name)) - { - if ((file.Attributes & FileAttributes.Hidden) == 0) - yield return new FilePiece(file); - } - } - - protected class FilePiece : DisplayPiece - { - private readonly FileInfo file; - - [Resolved] - private Bindable currentFile { get; set; } - - public FilePiece(FileInfo file) - { - this.file = file; - } - - protected override bool OnClick(ClickEvent e) - { - currentFile.Value = file; - return true; - } - - protected override string FallbackName => file.Name; - - protected override IconUsage? Icon - { - get - { - switch (file.Extension) - { - case ".ogg": - case ".mp3": - case ".wav": - return FontAwesome.Regular.FileAudio; - - case ".jpg": - case ".jpeg": - case ".png": - return FontAwesome.Regular.FileImage; - - case ".mp4": - case ".avi": - case ".mov": - case ".flv": - return FontAwesome.Regular.FileVideo; - - default: - return FontAwesome.Regular.File; - } - } - } - } - } -} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs new file mode 100644 index 0000000000..9bc66f6c9f --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs @@ -0,0 +1,140 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuDirectorySelector : DirectorySelector + { + public const float ITEM_HEIGHT = 20; + + public OsuDirectorySelector(string initialPath = null) + : base(initialPath) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Padding = new MarginPadding(10); + } + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + + protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); + + protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); + + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); + + protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); + + internal class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay + { + protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); + + [BackgroundDependencyLoader] + private void load() + { + Height = 50; + } + + private class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory + { + protected override IconUsage? Icon => null; + + public OsuBreadcrumbDisplayComputer() + : base(null, "Computer") + { + } + } + + private class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory + { + public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.Add(new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(FONT_SIZE / 2) + }); + } + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; + } + } + + internal class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory + { + protected override IconUsage? Icon => FontAwesome.Solid.Folder; + + public OsuDirectorySelectorParentDirectory(DirectoryInfo directory) + : base(directory, "..") + { + } + } + + internal class OsuDirectorySelectorDirectory : DirectorySelectorDirectory + { + public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.AutoSizeAxes = Axes.X; + Flow.Height = ITEM_HEIGHT; + + AddInternal(new OsuDirectorySelectorItemBackground + { + Depth = 1 + }); + } + + protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) + ? FontAwesome.Solid.Database + : FontAwesome.Regular.Folder; + } + + internal class OsuDirectorySelectorItemBackground : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 5; + + InternalChild = new Box + { + Colour = colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs new file mode 100644 index 0000000000..c50178100e --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuFileSelector : FileSelector + { + public OsuFileSelector(string initialPath = null, string[] validFileExtensions = null) + : base(initialPath, validFileExtensions) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Padding = new MarginPadding(10); + } + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + + protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelector.OsuDirectorySelectorBreadcrumbDisplay(); + + protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelector.OsuDirectorySelectorParentDirectory(directory); + + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelector.OsuDirectorySelectorDirectory(directory, displayName); + + protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file); + + protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); + + protected class OsuDirectoryListingFile : DirectoryListingFile + { + public OsuDirectoryListingFile(FileInfo file) + : base(file) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.AutoSizeAxes = Axes.X; + Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; + + AddInternal(new OsuDirectorySelector.OsuDirectorySelectorItemBackground + { + Depth = 1 + }); + } + + protected override IconUsage? Icon + { + get + { + switch (File.Extension) + { + case @".ogg": + case @".mp3": + case @".wav": + return FontAwesome.Regular.FileAudio; + + case @".jpg": + case @".jpeg": + case @".png": + return FontAwesome.Regular.FileImage; + + case @".mp4": + case @".avi": + case @".mov": + case @".flv": + return FontAwesome.Regular.FileVideo; + + default: + return FontAwesome.Regular.File; + } + } + } + + protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 349a112477..5392ba5d93 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private TriangleButton selectionButton; - private DirectorySelector directorySelector; + private OsuDirectorySelector directorySelector; /// /// Text to display in the header to inform the user of what they are selecting. @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }, new Drawable[] { - directorySelector = new DirectorySelector + directorySelector = new OsuDirectorySelector { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index a33a70af65..69c27702f8 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -56,9 +56,9 @@ namespace osu.Game.Screens.Edit.Setup public void DisplayFileChooser() { - FileSelector fileSelector; + OsuFileSelector fileSelector; - Target.Child = fileSelector = new FileSelector(currentFile.Value?.DirectoryName, handledExtensions) + Target.Child = fileSelector = new OsuFileSelector(currentFile.Value?.DirectoryName, handledExtensions) { RelativeSizeAxes = Axes.X, Height = 400, diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs index ee8ef6926d..7e1d55b3e2 100644 --- a/osu.Game/Screens/Import/FileImportScreen.cs +++ b/osu.Game/Screens/Import/FileImportScreen.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Import { public override bool HideOverlaysOnEnter => true; - private FileSelector fileSelector; + private OsuFileSelector fileSelector; private Container contentContainer; private TextFlowContainer currentFileText; @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Import Colour = colours.GreySeafoamDark, RelativeSizeAxes = Axes.Both, }, - fileSelector = new FileSelector(validFileExtensions: game.HandledExtensions.ToArray()) + fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray()) { RelativeSizeAxes = Axes.Both, Width = 0.65f From e94e283ee4083c82652302f04817346b39b085d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Jul 2021 00:20:58 +0200 Subject: [PATCH 56/74] Move shared inner classes to separate files --- .../UserInterfaceV2/OsuDirectorySelector.cs | 102 ------------------ .../OsuDirectorySelectorBreadcrumbDisplay.cs | 64 +++++++++++ .../OsuDirectorySelectorDirectory.cs | 58 ++++++++++ .../OsuDirectorySelectorParentDirectory.cs | 18 ++++ .../UserInterfaceV2/OsuFileSelector.cs | 8 +- 5 files changed, 144 insertions(+), 106 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs index 9bc66f6c9f..1ce4d97fdf 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs @@ -5,12 +5,8 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -38,103 +34,5 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); - - internal class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay - { - protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); - protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); - - [BackgroundDependencyLoader] - private void load() - { - Height = 50; - } - - private class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory - { - protected override IconUsage? Icon => null; - - public OsuBreadcrumbDisplayComputer() - : base(null, "Computer") - { - } - } - - private class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory - { - public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) - : base(directory, displayName) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Flow.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(FONT_SIZE / 2) - }); - } - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; - } - } - - internal class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory - { - protected override IconUsage? Icon => FontAwesome.Solid.Folder; - - public OsuDirectorySelectorParentDirectory(DirectoryInfo directory) - : base(directory, "..") - { - } - } - - internal class OsuDirectorySelectorDirectory : DirectorySelectorDirectory - { - public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) - : base(directory, displayName) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Flow.AutoSizeAxes = Axes.X; - Flow.Height = ITEM_HEIGHT; - - AddInternal(new OsuDirectorySelectorItemBackground - { - Depth = 1 - }); - } - - protected override SpriteText CreateSpriteText() => new OsuSpriteText(); - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) - ? FontAwesome.Solid.Database - : FontAwesome.Regular.Folder; - } - - internal class OsuDirectorySelectorItemBackground : CompositeDrawable - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.Both; - - Masking = true; - CornerRadius = 5; - - InternalChild = new Box - { - Colour = colours.GreySeafoamDarker, - RelativeSizeAxes = Axes.Both, - }; - } - } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs new file mode 100644 index 0000000000..cb5ff242a1 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + internal class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay + { + protected override Drawable CreateCaption() => new OsuSpriteText + { + Text = "Current Directory: ", + Font = OsuFont.Default.With(size: OsuDirectorySelector.ITEM_HEIGHT), + }; + + protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); + + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); + + [BackgroundDependencyLoader] + private void load() + { + Height = 50; + } + + private class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory + { + protected override IconUsage? Icon => null; + + public OsuBreadcrumbDisplayComputer() + : base(null, "Computer") + { + } + } + + private class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory + { + public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.Add(new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(FONT_SIZE / 2) + }); + } + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs new file mode 100644 index 0000000000..8a420cdcfb --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + internal class OsuDirectorySelectorDirectory : DirectorySelectorDirectory + { + public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.AutoSizeAxes = Axes.X; + Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; + + AddInternal(new Background + { + Depth = 1 + }); + } + + protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) + ? FontAwesome.Solid.Database + : FontAwesome.Regular.Folder; + + internal class Background : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 5; + + InternalChild = new Box + { + Colour = colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs new file mode 100644 index 0000000000..481d811adb --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + internal class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory + { + protected override IconUsage? Icon => FontAwesome.Solid.Folder; + + public OsuDirectorySelectorParentDirectory(DirectoryInfo directory) + : base(directory, "..") + { + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index c50178100e..b9fb642cbe 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -27,11 +27,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); - protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelector.OsuDirectorySelectorBreadcrumbDisplay(); + protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); - protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelector.OsuDirectorySelectorParentDirectory(directory); + protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); - protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelector.OsuDirectorySelectorDirectory(directory, displayName); + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file); @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; - AddInternal(new OsuDirectorySelector.OsuDirectorySelectorItemBackground + AddInternal(new OsuDirectorySelectorDirectory.Background { Depth = 1 }); From ddb1da5a6611b11769675f7558343c2b441b0e8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:48:35 +0900 Subject: [PATCH 57/74] Tidy up class (although it's not in a good state logically) --- .../SelectionCycleFillFlowContainer.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 4f20c0039b..656f489772 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,25 +17,11 @@ namespace osu.Game.Graphics.Containers /// public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, IStateful { - private int? selectedIndex; - public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; - private void setSelected(int? value) - { - if (selectedIndex == value) - return; + private int? selectedIndex; - // Deselect the previously-selected button - if (selectedIndex.HasValue) - this[selectedIndex.Value].State = SelectionState.NotSelected; - - selectedIndex = value; - - // Select the newly-selected button - if (selectedIndex.HasValue) - this[selectedIndex.Value].State = SelectionState.Selected; - } + private readonly Dictionary> handlerMap = new Dictionary>(); public void SelectNext() { @@ -64,20 +51,17 @@ namespace osu.Game.Graphics.Containers setSelected(IndexOf(item)); } - private readonly Dictionary> handlerMap = new Dictionary>(); - public override void Add(T drawable) { base.Add(drawable); - if (drawable != null) - { - // This event is used to update selection state when modified within the drawable itself. - // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - handlerMap[drawable] = state => selectionChanged(drawable, state); + Debug.Assert(drawable != null); - drawable.StateChanged += handlerMap[drawable]; - } + // This event is used to update selection state when modified within the drawable itself. + // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container + handlerMap[drawable] = state => selectionChanged(drawable, state); + + drawable.StateChanged += handlerMap[drawable]; } public override bool Remove(T drawable) @@ -85,7 +69,9 @@ namespace osu.Game.Graphics.Containers if (!base.Remove(drawable)) return false; - if (drawable != null && handlerMap.TryGetValue(drawable, out var action)) + Debug.Assert(drawable != null); + + if (handlerMap.TryGetValue(drawable, out var action)) { drawable.StateChanged -= action; handlerMap.Remove(drawable); @@ -94,6 +80,22 @@ namespace osu.Game.Graphics.Containers return true; } + private void setSelected(int? value) + { + if (selectedIndex == value) + return; + + // Deselect the previously-selected button + if (selectedIndex.HasValue) + this[selectedIndex.Value].State = SelectionState.NotSelected; + + selectedIndex = value; + + // Select the newly-selected button + if (selectedIndex.HasValue) + this[selectedIndex.Value].State = SelectionState.Selected; + } + private void selectionChanged(T drawable, SelectionState state) { if (state == SelectionState.NotSelected) From eb8b14a931cffd9fb99858aecfc06ffaaef30ee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:51:51 +0900 Subject: [PATCH 58/74] Reorder methods to make more sense --- osu.Game/Skinning/SkinProvidingContainer.cs | 84 ++++++++++----------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a628e97578..f386900f64 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,38 +64,19 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - /// - /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. - /// - /// The skin to add. - protected void AddSource(ISkin skin) + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - skinSources.Add((skin, new DisableableSkinSource(skin, this))); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - if (skin is ISkinSource source) - source.SourceChanged += TriggerSourceChanged; - } + ParentSource = dependencies.Get(); + if (ParentSource != null) + ParentSource.SourceChanged += TriggerSourceChanged; - /// - /// Remove a skin from this provider. - /// - /// The skin to remove. - protected void RemoveSource(ISkin skin) - { - if (skinSources.RemoveAll(s => s.skin == skin) == 0) - return; + dependencies.CacheAs(this); - if (skin is ISkinSource source) - source.SourceChanged -= TriggerSourceChanged; - } + TriggerSourceChanged(); - /// - /// Clears all skin sources. - /// - protected void ResetSources() - { - foreach (var skin in AllSources.ToArray()) - RemoveSource(skin); + return dependencies; } public ISkin FindProvider(Func lookupFunction) @@ -187,27 +168,46 @@ namespace osu.Game.Skinning return ParentSource?.GetConfig(lookup); } + /// + /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. + /// + /// The skin to add. + protected void AddSource(ISkin skin) + { + skinSources.Add((skin, new DisableableSkinSource(skin, this))); + + if (skin is ISkinSource source) + source.SourceChanged += TriggerSourceChanged; + } + + /// + /// Remove a skin from this provider. + /// + /// The skin to remove. + protected void RemoveSource(ISkin skin) + { + if (skinSources.RemoveAll(s => s.skin == skin) == 0) + return; + + if (skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; + } + + /// + /// Clears all skin sources. + /// + protected void ResetSources() + { + foreach (var skin in AllSources.ToArray()) + RemoveSource(skin); + } + /// /// Invoked when any source has changed (either or a source registered via ). /// This is also invoked once initially during to ensure sources are ready for children consumption. /// protected virtual void OnSourceChanged() { } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - ParentSource = dependencies.Get(); - if (ParentSource != null) - ParentSource.SourceChanged += TriggerSourceChanged; - - dependencies.CacheAs(this); - - TriggerSourceChanged(); - - return dependencies; - } - protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. From 35d4b12a4f7bb0bb42b388d6c70cdb2c3d58c4b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:52:52 +0900 Subject: [PATCH 59/74] Remove single local usage of `AllSources` --- osu.Game/Skinning/SkinProvidingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index f386900f64..d8f931da5e 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -198,8 +198,8 @@ namespace osu.Game.Skinning /// protected void ResetSources() { - foreach (var skin in AllSources.ToArray()) - RemoveSource(skin); + foreach (var i in skinSources) + RemoveSource(i.skin); } /// From ca791c2afa550c44b85de3380fa6dfaf6e41f4c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:53:00 +0900 Subject: [PATCH 60/74] Remove unused using statement --- osu.Game/Skinning/SkinProvidingContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d8f931da5e..d1a0187f7b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; From c18b8ca86c5095fcec39eae075eec175f6648eff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:08:29 +0900 Subject: [PATCH 61/74] Add missing `ToArray()` call --- osu.Game/Skinning/SkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d1a0187f7b..7c26fdaf03 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -197,7 +197,7 @@ namespace osu.Game.Skinning /// protected void ResetSources() { - foreach (var i in skinSources) + foreach (var i in skinSources.ToArray()) RemoveSource(i.skin); } From 35672f372a11fd69aa9e3ee67af5b7296131f32e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:58:01 +0900 Subject: [PATCH 62/74] Shorten test beatmap to avoid timeouts in score submission test --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index d9c0544d3c..c3a46ec4ac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -4,14 +4,18 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -25,6 +29,15 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset); + + beatmap.HitObjects = beatmap.HitObjects.Take(10).ToList(); + + return beatmap; + } + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); [Test] From faf95c7161b168c734a7d64d42e88b28566180be Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Jul 2021 15:35:14 +0900 Subject: [PATCH 63/74] Remove unused usings --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index c3a46ec4ac..f9ccb10778 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -11,11 +11,9 @@ using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; -using osuTK; namespace osu.Game.Tests.Visual.Gameplay { From 115376c53825adb05cddd3af0d056163e04fc003 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 16:10:24 +0900 Subject: [PATCH 64/74] Add playfield border to catch editor --- .../Edit/CatchHitObjectComposer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index d9712bc8e9..d360274aa6 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -2,6 +2,8 @@ // 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.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -20,6 +22,16 @@ namespace osu.Game.Rulesets.Catch.Edit { } + [BackgroundDependencyLoader] + private void load() + { + LayerBelowRuleset.Add(new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } + }); + } + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchEditorRuleset(ruleset, beatmap, mods); From 7d76fcf2b64766c14dce7c54c6af03ab0cdb4b37 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 16:13:34 +0900 Subject: [PATCH 65/74] Fix hit object placement not receiving input when outside playfield The input area is vertical infinite, but horizontally restricted to the playfield due to `CatchPlayfield`'s `ReceivePositionalInputAt` override. --- .../Edit/Blueprints/CatchPlacementBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs index 69054e2c81..5a32d241ad 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Catch.Edit.Blueprints { @@ -23,5 +24,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints : base(new THitObject()) { } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; } } From 09925dffef48d0e20232cbd38d260792a4420246 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 16:30:23 +0900 Subject: [PATCH 66/74] Add missing `HeadlessTest` flag on new test scene --- osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs index cfc4ccd208..ab47067411 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs @@ -10,12 +10,14 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Skins { + [HeadlessTest] public class TestSceneSkinProvidingContainer : OsuTestScene { /// From 8b1876bc2a3341e4151123f9e56e1e8eec6e63a9 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 7 Jul 2021 11:43:54 +0200 Subject: [PATCH 67/74] Disallow removing items from SelectionCycleFillFlowContainer --- .../SelectionCycleFillFlowContainer.cs | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 656f489772..cef903f63e 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using osu.Framework; using osu.Framework.Graphics; @@ -21,8 +20,6 @@ namespace osu.Game.Graphics.Containers private int? selectedIndex; - private readonly Dictionary> handlerMap = new Dictionary>(); - public void SelectNext() { if (!selectedIndex.HasValue || selectedIndex == Count - 1) @@ -57,28 +54,12 @@ namespace osu.Game.Graphics.Containers Debug.Assert(drawable != null); - // This event is used to update selection state when modified within the drawable itself. - // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - handlerMap[drawable] = state => selectionChanged(drawable, state); - - drawable.StateChanged += handlerMap[drawable]; + drawable.StateChanged += state => selectionChanged(drawable, state); } public override bool Remove(T drawable) - { - if (!base.Remove(drawable)) - return false; + => throw new NotSupportedException($"Cannot remove drawables from {nameof(SelectionCycleFillFlowContainer)}"); - Debug.Assert(drawable != null); - - if (handlerMap.TryGetValue(drawable, out var action)) - { - drawable.StateChanged -= action; - handlerMap.Remove(drawable); - } - - return true; - } private void setSelected(int? value) { From f53f6690e3982fecc1b4b5dcdfea4ef808b0b80c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 7 Jul 2021 12:01:47 +0200 Subject: [PATCH 68/74] Remove extra blank line --- osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index cef903f63e..90b2d20e4d 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -60,7 +60,6 @@ namespace osu.Game.Graphics.Containers public override bool Remove(T drawable) => throw new NotSupportedException($"Cannot remove drawables from {nameof(SelectionCycleFillFlowContainer)}"); - private void setSelected(int? value) { if (selectedIndex == value) From 4d7c7441016aff5dc5f8e2b6f7476716e234a36c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 7 Jul 2021 12:59:31 +0200 Subject: [PATCH 69/74] Fix failing test --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 6fcc70e5f2..2608c93fa1 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Selected?.Click(); + protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); /// /// Action that is invoked when is triggered. From 83283a706eca6bc8c39d8b775ac8ce5d2a8db13e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 20:51:13 +0900 Subject: [PATCH 70/74] Add test scene --- .../UserInterface/TestSceneVolumeOverlay.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs new file mode 100644 index 0000000000..64708c4858 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Volume; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneVolumeOverlay : OsuTestScene + { + private VolumeOverlay volume; + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddRange(new Drawable[] + { + volume = new VolumeOverlay(), + new VolumeControlReceptor + { + RelativeSizeAxes = Axes.Both, + ActionRequested = action => volume.Adjust(action), + ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), + }, + }); + + AddStep("show controls", () => volume.Show()); + } + } +} From f1aa99e1033c8115259f4b9dcbc7c9ecf49932b0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 21:01:55 +0900 Subject: [PATCH 71/74] Fix catch selection blueprint not displayed after copy-pasted --- .../Edit/Blueprints/CatchSelectionBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs index 298f9474b0..720d730858 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints public abstract class CatchSelectionBlueprint : HitObjectSelectionBlueprint where THitObject : CatchHitObject { + protected override bool AlwaysShowWhenSelected => true; + public override Vector2 ScreenSpaceSelectionPoint { get From cbe4114e90d8a16f960e6a3583139bbf7072ffc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 21:02:48 +0900 Subject: [PATCH 72/74] Adjust visuals and make base opacity 100% --- osu.Game/Overlays/Volume/VolumeMeter.cs | 75 ++++++++++++------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index a7c4fb6e7d..82ab45428a 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; - private Container focusGlowContainer; + private Container selectedGlowContainer; private Sample sample; private double sampleLastPlaybackTime; @@ -59,6 +59,8 @@ namespace osu.Game.Overlays.Volume state = value; StateChanged?.Invoke(value); + + updateSelectedState(); } } @@ -94,28 +96,8 @@ namespace osu.Game.Overlays.Volume Size = new Vector2(circleSize), Children = new Drawable[] { - focusGlowContainer = new CircularContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = meterColour.Opacity(0.5f), - Radius = 5, - Hollow = true, - } - }, new BufferedContainer { - Alpha = 0.9f, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -187,6 +169,24 @@ namespace osu.Game.Overlays.Volume }, }, }, + selectedGlowContainer = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = meterColour.Opacity(0.1f), + Radius = 10, + } + }, maxGlow = (text = new OsuSpriteText { Anchor = Anchor.Centre, @@ -211,7 +211,6 @@ namespace osu.Game.Overlays.Volume { new Box { - Alpha = 0.9f, RelativeSizeAxes = Axes.Both, Colour = backgroundColour, }, @@ -229,8 +228,6 @@ namespace osu.Game.Overlays.Volume Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); bgProgress.Current.Value = 0.75f; - - StateChanged += stateChanged; } private int? displayVolumeInt; @@ -359,20 +356,6 @@ namespace osu.Game.Overlays.Volume { } - private void stateChanged(SelectionState newState) - { - if (newState == SelectionState.Selected) - { - this.ScaleTo(1.04f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); - } - else - { - this.ScaleTo(1f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); - } - } - public bool OnPressed(GlobalAction action) { if (!IsHovered) @@ -397,5 +380,21 @@ namespace osu.Game.Overlays.Volume public void OnReleased(GlobalAction action) { } + + private void updateSelectedState() + { + switch (state) + { + case SelectionState.Selected: + this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + selectedGlowContainer.FadeIn(transition_length, Easing.OutExpo); + break; + + case SelectionState.NotSelected: + this.ScaleTo(1f, transition_length, Easing.OutExpo); + selectedGlowContainer.FadeOut(transition_length, Easing.OutExpo); + break; + } + } } } From 7d405f04fbff8d449864958e2ddb4b3c65511ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 21:17:31 +0900 Subject: [PATCH 73/74] Fix selected volume control not updating correctly on mouse move --- osu.Game/Overlays/Volume/VolumeMeter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 82ab45428a..eed6f5c2f3 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -64,6 +64,8 @@ namespace osu.Game.Overlays.Volume } } + private const float transition_length = 500; + public VolumeMeter(string name, float circleSize, Color4 meterColour) { this.circleSize = circleSize; @@ -344,12 +346,10 @@ namespace osu.Game.Overlays.Volume return true; } - private const float transition_length = 500; - - protected override bool OnHover(HoverEvent e) + protected override bool OnMouseMove(MouseMoveEvent e) { State = SelectionState.Selected; - return false; + return base.OnMouseMove(e); } protected override void OnHoverLost(HoverLostEvent e) From 341cb09c6eb5399b3ba113cf92ef2c620e4dc43a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 12:06:52 +0900 Subject: [PATCH 74/74] Update terminology in README At some point we'll want to replace the link to the outdated blog post (or just remove it?) with the gantt or otherwise. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2213b42121..e95c12cfdc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A free-to-win rhythm game. Rhythm is just a *click* away! -The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew. +The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge. ## Status @@ -23,7 +23,7 @@ We are accepting bug reports (please report with as much detail as possible and - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). -- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where lazer is currently and the roadmap going forward. +- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where the project is currently and the roadmap going forward. ## Running osu!