From 508b26ac6973a07a7edb6870ec13e065b055a21c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 07:31:07 +0300 Subject: [PATCH 01/76] Implement basic layout --- .../UserInterface/TestScenePageSelector.cs | 29 +++++ .../Graphics/UserInterface/PageSelector.cs | 100 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs create mode 100644 osu.Game/Graphics/UserInterface/PageSelector.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs new file mode 100644 index 0000000000..9911a23bf1 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -0,0 +1,29 @@ +// 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.Game.Graphics.UserInterface; +using osu.Framework.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestScenePageSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PageSelector) + }; + + public TestScenePageSelector() + { + PageSelector pageSelector; + + Child = pageSelector = new PageSelector(10) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs new file mode 100644 index 0000000000..53f112ed68 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -0,0 +1,100 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + public class PageSelector : CompositeDrawable + { + private BindableInt currentPage = new BindableInt(1); + + private readonly int maxPages; + private readonly FillFlowContainer pillsFlow; + + public PageSelector(int maxPages) + { + this.maxPages = maxPages; + + AutoSizeAxes = Axes.Both; + InternalChild = pillsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + currentPage.BindValueChanged(page => redraw(page.NewValue), true); + } + + private void redraw(int newPage) + { + pillsFlow.Clear(); + + for (int i = 0; i < maxPages; i++) + { + addPagePill(i); + } + } + + private void addPagePill(int page) + { + var pill = new Pill(page); + + if (page != currentPage.Value) + pill.Action = () => currentPage.Value = page; + + pillsFlow.Add(pill); + } + + private class Pill : OsuClickableContainer + { + private const int height = 20; + + private readonly Box background; + + public Pill(int page) + { + AutoSizeAxes = Axes.X; + Height = height; + Child = new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = page.ToString(), + Margin = new MarginPadding { Horizontal = 8 } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Seafoam; + } + } + } +} From f77cd6582d94811874d74315265d4e91b5f81321 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 08:20:09 +0300 Subject: [PATCH 02/76] Implement CurrentPage class --- .../Graphics/UserInterface/PageSelector.cs | 150 ++++++++++++++---- 1 file changed, 119 insertions(+), 31 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 53f112ed68..fef35ab229 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -8,6 +8,9 @@ using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Extensions.Color4Extensions; +using System; namespace osu.Game.Graphics.UserInterface { @@ -42,59 +45,144 @@ namespace osu.Game.Graphics.UserInterface { pillsFlow.Clear(); - for (int i = 0; i < maxPages; i++) + for (int i = 1; i <= maxPages; i++) { - addPagePill(i); + if (i == currentPage.Value) + addCurrentPagePill(); + else + addPagePill(i); } } private void addPagePill(int page) { - var pill = new Pill(page); - - if (page != currentPage.Value) - pill.Action = () => currentPage.Value = page; - - pillsFlow.Add(pill); + pillsFlow.Add(new Page(page.ToString(), () => currentPage.Value = page)); } - private class Pill : OsuClickableContainer + private void addCurrentPagePill() + { + pillsFlow.Add(new CurrentPage(currentPage.Value.ToString())); + } + + private abstract class DrawablePage : CompositeDrawable { private const int height = 20; + private const int margin = 8; - private readonly Box background; + protected readonly string Text; - public Pill(int page) + protected DrawablePage(string text) { + Text = text; + AutoSizeAxes = Axes.X; Height = height; - Child = new CircularContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = page.ToString(), - Margin = new MarginPadding { Horizontal = 8 } - } - } - }; + + var background = CreateBackground(); + + if (background != null) + AddInternal(background); + + var content = CreateContent(); + content.Margin = new MarginPadding { Horizontal = margin }; + + AddInternal(content); + } + + protected abstract Drawable CreateContent(); + + protected virtual Drawable CreateBackground() => null; + } + + private abstract class ActivatedDrawablePage : DrawablePage + { + private readonly Action action; + + public ActivatedDrawablePage(string text, Action action) + : base(text) + { + this.action = action; + } + + protected override bool OnClick(ClickEvent e) + { + action?.Invoke(); + return base.OnClick(e); + } + } + + private class Page : ActivatedDrawablePage + { + private SpriteText text; + + private OsuColour colours; + + public Page(string text, Action action) + : base(text, action) + { } [BackgroundDependencyLoader] private void load(OsuColour colours) { + this.colours = colours; + text.Colour = colours.Seafoam; + } + + protected override bool OnHover(HoverEvent e) + { + text.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + text.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + } + + private class CurrentPage : DrawablePage + { + private SpriteText text; + + private Box background; + + public CurrentPage(string text) + : base(text) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; } } } From cea26baaefb58171ca76b1b3b39a70772d8eb7a5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 08:46:16 +0300 Subject: [PATCH 03/76] Implement placeholder and correct redraw algorithm --- .../UserInterface/TestScenePageSelector.cs | 4 +- .../Graphics/UserInterface/PageSelector.cs | 63 +++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 9911a23bf1..e5efa65c91 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -17,9 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestScenePageSelector() { - PageSelector pageSelector; - - Child = pageSelector = new PageSelector(10) + Child = new PageSelector(200) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index fef35ab229..244e87f023 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; @@ -16,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface { public class PageSelector : CompositeDrawable { - private BindableInt currentPage = new BindableInt(1); + public readonly BindableInt CurrentPage = new BindableInt(); private readonly int maxPages; private readonly FillFlowContainer pillsFlow; @@ -38,30 +37,47 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - currentPage.BindValueChanged(page => redraw(page.NewValue), true); + CurrentPage.BindValueChanged(_ => redraw(), true); } - private void redraw(int newPage) + private void redraw() { pillsFlow.Clear(); - for (int i = 1; i <= maxPages; i++) + if (CurrentPage.Value > 3) + addDrawablePage(1); + + if (CurrentPage.Value > 4) + addPlaceholder(); + + for (int i = Math.Max(CurrentPage.Value - 2, 1); i <= Math.Min(CurrentPage.Value + 2, maxPages); i++) { - if (i == currentPage.Value) + if (i == CurrentPage.Value) addCurrentPagePill(); else - addPagePill(i); + addDrawablePage(i); } + + if (CurrentPage.Value + 2 < maxPages - 1) + addPlaceholder(); + + if (CurrentPage.Value + 2 < maxPages) + addDrawablePage(maxPages); } - private void addPagePill(int page) + private void addDrawablePage(int page) { - pillsFlow.Add(new Page(page.ToString(), () => currentPage.Value = page)); + pillsFlow.Add(new Page(page.ToString(), () => CurrentPage.Value = page)); + } + + private void addPlaceholder() + { + pillsFlow.Add(new Placeholder()); } private void addCurrentPagePill() { - pillsFlow.Add(new CurrentPage(currentPage.Value.ToString())); + pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } private abstract class DrawablePage : CompositeDrawable @@ -149,13 +165,13 @@ namespace osu.Game.Graphics.UserInterface }; } - private class CurrentPage : DrawablePage + private class SelectedPage : DrawablePage { private SpriteText text; private Box background; - public CurrentPage(string text) + public SelectedPage(string text) : base(text) { } @@ -184,5 +200,28 @@ namespace osu.Game.Graphics.UserInterface } }; } + + private class Placeholder : DrawablePage + { + private SpriteText text; + + public Placeholder() + : base("...") + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.Seafoam; + } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + } } } From 9bd4220e9f6817e3200926c7abce593b6f8e52aa Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 09:20:11 +0300 Subject: [PATCH 04/76] Add prev/next buttons --- .../Graphics/UserInterface/PageSelector.cs | 206 ++++++++++++++++-- 1 file changed, 184 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 244e87f023..d93dcdace9 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -10,12 +10,13 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; +using osuTK; namespace osu.Game.Graphics.UserInterface { public class PageSelector : CompositeDrawable { - public readonly BindableInt CurrentPage = new BindableInt(); + public readonly BindableInt CurrentPage = new BindableInt(1); private readonly int maxPages; private readonly FillFlowContainer pillsFlow; @@ -44,6 +45,11 @@ namespace osu.Game.Graphics.UserInterface { pillsFlow.Clear(); + if (CurrentPage.Value == 1) + addPreviousPageButton(); + else + addPreviousPageButton(() => CurrentPage.Value -= 1); + if (CurrentPage.Value > 3) addDrawablePage(1); @@ -63,6 +69,11 @@ namespace osu.Game.Graphics.UserInterface if (CurrentPage.Value + 2 < maxPages) addDrawablePage(maxPages); + + if (CurrentPage.Value == maxPages) + addNextPageButton(); + else + addNextPageButton(() => CurrentPage.Value += 1); } private void addDrawablePage(int page) @@ -80,12 +91,23 @@ namespace osu.Game.Graphics.UserInterface pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } + private void addPreviousPageButton(Action action = null) + { + pillsFlow.Add(new PreviousPageButton(action)); + } + + private void addNextPageButton(Action action = null) + { + pillsFlow.Add(new NextPageButton(action)); + } + private abstract class DrawablePage : CompositeDrawable { private const int height = 20; private const int margin = 8; protected readonly string Text; + protected readonly Drawable Content; protected DrawablePage(string text) { @@ -99,10 +121,10 @@ namespace osu.Game.Graphics.UserInterface if (background != null) AddInternal(background); - var content = CreateContent(); - content.Margin = new MarginPadding { Horizontal = margin }; + Content = CreateContent(); + Content.Margin = new MarginPadding { Horizontal = margin }; - AddInternal(content); + AddInternal(Content); } protected abstract Drawable CreateContent(); @@ -112,25 +134,23 @@ namespace osu.Game.Graphics.UserInterface private abstract class ActivatedDrawablePage : DrawablePage { - private readonly Action action; + protected readonly Action Action; - public ActivatedDrawablePage(string text, Action action) + public ActivatedDrawablePage(string text, Action action = null) : base(text) { - this.action = action; + Action = action; } protected override bool OnClick(ClickEvent e) { - action?.Invoke(); + Action?.Invoke(); return base.OnClick(e); } } private class Page : ActivatedDrawablePage { - private SpriteText text; - private OsuColour colours; public Page(string text, Action action) @@ -142,22 +162,22 @@ namespace osu.Game.Graphics.UserInterface private void load(OsuColour colours) { this.colours = colours; - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; } protected override bool OnHover(HoverEvent e) { - text.Colour = colours.Seafoam.Lighten(30f); + Content.Colour = colours.Seafoam.Lighten(30f); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; base.OnHoverLost(e); } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -167,8 +187,6 @@ namespace osu.Game.Graphics.UserInterface private class SelectedPage : DrawablePage { - private SpriteText text; - private Box background; public SelectedPage(string text) @@ -179,11 +197,11 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - text.Colour = colours.GreySeafoam; + Content.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -203,8 +221,6 @@ namespace osu.Game.Graphics.UserInterface private class Placeholder : DrawablePage { - private SpriteText text; - public Placeholder() : base("...") { @@ -213,15 +229,161 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = Text }; } + + private class PreviousPageButton : ActivatedDrawablePage + { + private OsuColour colours; + private Box background; + + public PreviousPageButton(Action action) + : base("prev", action) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + Content.Colour = colours.Seafoam; + background.Colour = colours.GreySeafoam; + + if (Action == null) + { + Content.FadeColour(colours.GrayA); + background.FadeColour(colours.GrayA); + } + } + + protected override bool OnHover(HoverEvent e) + { + Content.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Content.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.CaretLeft, + Size = new Vector2(10), + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Text.ToUpper(), + } + } + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + private class NextPageButton : ActivatedDrawablePage + { + private OsuColour colours; + private Box background; + + public NextPageButton(Action action) + : base("next", action) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + Content.Colour = colours.Seafoam; + background.Colour = colours.GreySeafoam; + + if (Action == null) + { + Content.FadeColour(colours.GrayA); + background.FadeColour(colours.GrayA); + } + } + + protected override bool OnHover(HoverEvent e) + { + Content.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Content.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Text.ToUpper(), + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.CaretRight, + Size = new Vector2(10), + }, + } + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } } } From ba18f77b628496569cb5d5718081ea271b51e4f3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 22:31:08 +0300 Subject: [PATCH 05/76] Use OsuHoverContainer for prev/next buttons --- .../Graphics/UserInterface/PageSelector.cs | 231 ++++++------------ 1 file changed, 78 insertions(+), 153 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index d93dcdace9..3ca133d64b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -11,6 +11,8 @@ using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; using osuTK; +using osu.Game.Graphics.Containers; +using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface { @@ -21,16 +23,34 @@ namespace osu.Game.Graphics.UserInterface private readonly int maxPages; private readonly FillFlowContainer pillsFlow; + private readonly Button previousPageButton; + private readonly Button nextPageButton; + public PageSelector(int maxPages) { this.maxPages = maxPages; AutoSizeAxes = Axes.Both; - InternalChild = pillsFlow = new FillFlowContainer + InternalChild = new FillFlowContainer { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + previousPageButton = new Button(false, "prev") + { + Action = () => CurrentPage.Value -= 1, + }, + pillsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + nextPageButton = new Button(true, "next") + { + Action = () => CurrentPage.Value += 1 + } + } }; } @@ -43,12 +63,10 @@ namespace osu.Game.Graphics.UserInterface private void redraw() { - pillsFlow.Clear(); + previousPageButton.Enabled.Value = CurrentPage.Value != 1; + nextPageButton.Enabled.Value = CurrentPage.Value != maxPages; - if (CurrentPage.Value == 1) - addPreviousPageButton(); - else - addPreviousPageButton(() => CurrentPage.Value -= 1); + pillsFlow.Clear(); if (CurrentPage.Value > 3) addDrawablePage(1); @@ -69,11 +87,6 @@ namespace osu.Game.Graphics.UserInterface if (CurrentPage.Value + 2 < maxPages) addDrawablePage(maxPages); - - if (CurrentPage.Value == maxPages) - addNextPageButton(); - else - addNextPageButton(() => CurrentPage.Value += 1); } private void addDrawablePage(int page) @@ -91,16 +104,6 @@ namespace osu.Game.Graphics.UserInterface pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } - private void addPreviousPageButton(Action action = null) - { - pillsFlow.Add(new PreviousPageButton(action)); - } - - private void addNextPageButton(Action action = null) - { - pillsFlow.Add(new NextPageButton(action)); - } - private abstract class DrawablePage : CompositeDrawable { private const int height = 20; @@ -240,150 +243,72 @@ namespace osu.Game.Graphics.UserInterface }; } - private class PreviousPageButton : ActivatedDrawablePage + private class Button : OsuHoverContainer { - private OsuColour colours; - private Box background; + private const int height = 20; + private const int margin = 8; - public PreviousPageButton(Action action) - : base("prev", action) + private readonly Anchor alignment; + private readonly Box background; + + protected override IEnumerable EffectTargets => new[] { background }; + + public Button(bool rightAligned, string text) { - } + alignment = rightAligned ? Anchor.x0 : Anchor.x2; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - this.colours = colours; - Content.Colour = colours.Seafoam; - background.Colour = colours.GreySeafoam; + AutoSizeAxes = Axes.X; + Height = height; - if (Action == null) + Child = new CircularContainer { - Content.FadeColour(colours.GrayA); - background.FadeColour(colours.GrayA); - } - } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3), - Children = new Drawable[] - { - new SpriteIcon + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.CaretLeft, - Size = new Vector2(10), - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = Text.ToUpper(), + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Horizontal = margin }, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Text = text.ToUpper(), + }, + new SpriteIcon + { + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Size = new Vector2(10), + }, + } + } + } } - } - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; - } - - private class NextPageButton : ActivatedDrawablePage - { - private OsuColour colours; - private Box background; - - public NextPageButton(Action action) - : base("next", action) - { + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - Content.Colour = colours.Seafoam; - background.Colour = colours.GreySeafoam; - - if (Action == null) - { - Content.FadeColour(colours.GrayA); - background.FadeColour(colours.GrayA); - } + IdleColour = colours.GreySeafoamDark; + HoverColour = colours.GrayA; } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3), - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = Text.ToUpper(), - }, - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.CaretRight, - Size = new Vector2(10), - }, - } - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; } } } From ec8298ac53108d65a7901228bf524ebcae794356 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 22:33:26 +0300 Subject: [PATCH 06/76] Simplify redraw function --- .../Graphics/UserInterface/PageSelector.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 3ca133d64b..0b0c2eec83 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -58,34 +58,34 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw(), true); + CurrentPage.BindValueChanged(page => redraw(page.NewValue), true); } - private void redraw() + private void redraw(int newPage) { - previousPageButton.Enabled.Value = CurrentPage.Value != 1; - nextPageButton.Enabled.Value = CurrentPage.Value != maxPages; + previousPageButton.Enabled.Value = newPage != 1; + nextPageButton.Enabled.Value = newPage != maxPages; pillsFlow.Clear(); - if (CurrentPage.Value > 3) + if (newPage > 3) addDrawablePage(1); - if (CurrentPage.Value > 4) + if (newPage > 4) addPlaceholder(); - for (int i = Math.Max(CurrentPage.Value - 2, 1); i <= Math.Min(CurrentPage.Value + 2, maxPages); i++) + for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) { - if (i == CurrentPage.Value) + if (i == newPage) addCurrentPagePill(); else addDrawablePage(i); } - if (CurrentPage.Value + 2 < maxPages - 1) + if (newPage + 2 < maxPages - 1) addPlaceholder(); - if (CurrentPage.Value + 2 < maxPages) + if (newPage + 2 < maxPages) addDrawablePage(maxPages); } From df29465ba44c974290bdaa775d78b736a2340f8d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 23:42:30 +0300 Subject: [PATCH 07/76] Implement a single PageItem component --- .../Graphics/UserInterface/PageSelector.cs | 237 +++++++----------- 1 file changed, 86 insertions(+), 151 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 0b0c2eec83..9dea9232ac 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -7,12 +7,12 @@ using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Graphics.UserInterface { @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface public readonly BindableInt CurrentPage = new BindableInt(1); private readonly int maxPages; - private readonly FillFlowContainer pillsFlow; + private readonly FillFlowContainer itemsFlow; private readonly Button previousPageButton; private readonly Button nextPageButton; @@ -41,7 +41,7 @@ namespace osu.Game.Graphics.UserInterface { Action = () => CurrentPage.Value -= 1, }, - pillsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; - pillsFlow.Clear(); + itemsFlow.Clear(); if (newPage > 3) addDrawablePage(1); @@ -77,7 +77,7 @@ namespace osu.Game.Graphics.UserInterface for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) { if (i == newPage) - addCurrentPagePill(); + addDrawableCurrentPage(); else addDrawablePage(i); } @@ -89,137 +89,101 @@ namespace osu.Game.Graphics.UserInterface addDrawablePage(maxPages); } - private void addDrawablePage(int page) + private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page.ToString()) { - pillsFlow.Add(new Page(page.ToString(), () => CurrentPage.Value = page)); - } + Action = () => CurrentPage.Value = page, + }); - private void addPlaceholder() - { - pillsFlow.Add(new Placeholder()); - } + private void addPlaceholder() => itemsFlow.Add(new Placeholder()); - private void addCurrentPagePill() - { - pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - } + private void addDrawableCurrentPage() => itemsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - private abstract class DrawablePage : CompositeDrawable + private abstract class PageItem : OsuHoverContainer { - private const int height = 20; private const int margin = 8; + private const int height = 20; - protected readonly string Text; - protected readonly Drawable Content; - - protected DrawablePage(string text) + protected PageItem(string text) { - Text = text; - AutoSizeAxes = Axes.X; Height = height; + var contentContainer = new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + }; + var background = CreateBackground(); - if (background != null) - AddInternal(background); + contentContainer.Add(background); - Content = CreateContent(); - Content.Margin = new MarginPadding { Horizontal = margin }; + var drawableText = CreateText(text); + if (drawableText != null) + { + drawableText.Margin = new MarginPadding { Horizontal = margin }; + contentContainer.Add(drawableText); + } - AddInternal(Content); + Add(contentContainer); } - protected abstract Drawable CreateContent(); + protected abstract Drawable CreateText(string text); - protected virtual Drawable CreateBackground() => null; + protected abstract Drawable CreateBackground(); } - private abstract class ActivatedDrawablePage : DrawablePage + private class DrawablePage : PageItem { - protected readonly Action Action; + protected SpriteText SpriteText; - public ActivatedDrawablePage(string text, Action action = null) + protected override IEnumerable EffectTargets => new[] { SpriteText }; + + public DrawablePage(string text) : base(text) { - Action = action; } - protected override bool OnClick(ClickEvent e) + protected override Drawable CreateBackground() => null; + + protected override Drawable CreateText(string text) => SpriteText = new SpriteText { - Action?.Invoke(); - return base.OnClick(e); - } - } - - private class Page : ActivatedDrawablePage - { - private OsuColour colours; - - public Page(string text, Action action) - : base(text, action) - { - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text, + }; [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - Content.Colour = colours.Seafoam; + IdleColour = colours.Seafoam; + HoverColour = colours.Seafoam.Lighten(30f); } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; } private class SelectedPage : DrawablePage { private Box background; + protected override IEnumerable EffectTargets => null; + public SelectedPage(string text) : base(text) { } + protected override Drawable CreateBackground() => background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - Content.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; + SpriteText.Colour = colours.GreySeafoamDark; } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; } private class Placeholder : DrawablePage @@ -228,79 +192,28 @@ namespace osu.Game.Graphics.UserInterface : base("...") { } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Content.Colour = colours.Seafoam; - } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; } - private class Button : OsuHoverContainer + private class Button : PageItem { - private const int height = 20; - private const int margin = 8; - - private readonly Anchor alignment; - private readonly Box background; + private Box background; + private FillFlowContainer textContainer; + private SpriteIcon icon; protected override IEnumerable EffectTargets => new[] { background }; public Button(bool rightAligned, string text) + : base(text) { - alignment = rightAligned ? Anchor.x0 : Anchor.x2; + var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - AutoSizeAxes = Axes.X; - Height = height; - - Child = new CircularContainer + textContainer.ForEach(drawable => { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Horizontal = margin }, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Text = text.ToUpper(), - }, - new SpriteIcon - { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, - Size = new Vector2(10), - }, - } - } - } - } - }; + drawable.Anchor = Anchor.y1 | alignment; + drawable.Origin = Anchor.y1 | alignment; + }); + + icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; } [BackgroundDependencyLoader] @@ -309,6 +222,28 @@ namespace osu.Game.Graphics.UserInterface IdleColour = colours.GreySeafoamDark; HoverColour = colours.GrayA; } + + protected override Drawable CreateBackground() => background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + + protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SpriteText + { + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Size = new Vector2(10), + }, + } + }; } } } From b0884d16fbcc4a540af86f40aa359a577d39d988 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:13:23 +0300 Subject: [PATCH 08/76] Visual adjustments --- .../Graphics/UserInterface/PageSelector.cs | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9dea9232ac..9a5ffad0d4 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -100,38 +101,47 @@ namespace osu.Game.Graphics.UserInterface private abstract class PageItem : OsuHoverContainer { - private const int margin = 8; + private const int margin = 10; private const int height = 20; + protected override Container Content => contentContainer; + + private readonly CircularContainer contentContainer; + protected PageItem(string text) { AutoSizeAxes = Axes.X; Height = height; - var contentContainer = new CircularContainer + base.Content.Add(contentContainer = new CircularContainer { AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Masking = true, - }; + }); var background = CreateBackground(); if (background != null) - contentContainer.Add(background); + Add(background); var drawableText = CreateText(text); if (drawableText != null) { drawableText.Margin = new MarginPadding { Horizontal = margin }; - contentContainer.Add(drawableText); + Add(drawableText); } + } - Add(contentContainer); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.Seafoam; + HoverColour = colours.Seafoam.Lighten(30f); } protected abstract Drawable CreateText(string text); - protected abstract Drawable CreateBackground(); + protected virtual Drawable CreateBackground() => null; } private class DrawablePage : PageItem @@ -145,28 +155,20 @@ namespace osu.Game.Graphics.UserInterface { } - protected override Drawable CreateBackground() => null; - protected override Drawable CreateText(string text) => SpriteText = new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = text, + Font = OsuFont.GetFont(size: 12), }; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.Seafoam; - HoverColour = colours.Seafoam.Lighten(30f); - } } private class SelectedPage : DrawablePage { private Box background; - protected override IEnumerable EffectTargets => null; + protected override IEnumerable EffectTargets => new[] { background }; public SelectedPage(string text) : base(text) @@ -181,7 +183,6 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.Seafoam; SpriteText.Colour = colours.GreySeafoamDark; } } @@ -196,11 +197,14 @@ namespace osu.Game.Graphics.UserInterface private class Button : PageItem { + private const int duration = 100; + private Box background; private FillFlowContainer textContainer; private SpriteIcon icon; + private readonly Box fadeBox; - protected override IEnumerable EffectTargets => new[] { background }; + protected override IEnumerable EffectTargets => new[] { textContainer }; public Button(bool rightAligned, string text) : base(text) @@ -214,13 +218,29 @@ namespace osu.Game.Graphics.UserInterface }); icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; + + Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.GreySeafoamDark; - HoverColour = colours.GrayA; + background.Colour = colours.GreySeafoamDark; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Enabled.BindValueChanged(onEnabledChanged, true); + } + + private void onEnabledChanged(ValueChangedEvent enabled) + { + fadeBox.FadeTo(enabled.NewValue ? 0 : 1, duration); } protected override Drawable CreateBackground() => background = new Box @@ -231,16 +251,19 @@ namespace osu.Game.Graphics.UserInterface protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer { AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Horizontal, Children = new Drawable[] { new SpriteText { Text = text.ToUpper(), + Font = OsuFont.GetFont(size: 12), }, icon = new SpriteIcon { - Size = new Vector2(10), + Size = new Vector2(8), }, } }; From b97f4a81db6a35ba5fac734bbdb0f8b42a948419 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:27:40 +0300 Subject: [PATCH 09/76] Add more testing --- .../UserInterface/TestScenePageSelector.cs | 22 +++++++++++++++++++ .../Graphics/UserInterface/PageSelector.cs | 18 +++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index e5efa65c91..cb83fbd028 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -22,6 +22,28 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, }; + + AddStep("1 max pages", () => redraw(1)); + AddStep("10 max pages", () => redraw(10)); + AddStep("200 max pages, current 199", () => redraw(200, 199)); + AddStep("200 max pages, current 201", () => redraw(200, 201)); + AddStep("200 max pages, current -10", () => redraw(200, -10)); + } + + private void redraw(int maxPages, int currentPage = 0) + { + Clear(); + + var selector = new PageSelector(maxPages) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + if (currentPage != 0) + selector.CurrentPage.Value = currentPage; + + Add(selector); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9a5ffad0d4..79a1680e4b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -59,11 +59,25 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(page => redraw(page.NewValue), true); + CurrentPage.BindValueChanged(_ => redraw(), true); } - private void redraw(int newPage) + private void redraw() { + if (CurrentPage.Value > maxPages) + { + CurrentPage.Value = maxPages; + return; + } + + if (CurrentPage.Value < 1) + { + CurrentPage.Value = 1; + return; + } + + int newPage = CurrentPage.Value; + previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; From 0451b45fab4ed1a377f99e7f508e7b9c0b451ef6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:37:11 +0300 Subject: [PATCH 10/76] Add missing blank line --- osu.Game/Graphics/UserInterface/PageSelector.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 79a1680e4b..9db6366ad6 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -139,6 +139,7 @@ namespace osu.Game.Graphics.UserInterface Add(background); var drawableText = CreateText(text); + if (drawableText != null) { drawableText.Margin = new MarginPadding { Horizontal = margin }; From bee7760d29e884dff532828e3bbb1f1d71fd3d6d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 01:36:25 +0300 Subject: [PATCH 11/76] Make MaxPages value a bindable --- .../UserInterface/TestScenePageSelector.cs | 47 ++++++++++--------- .../Graphics/UserInterface/PageSelector.cs | 40 ++++++++++++---- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index cb83fbd028..14cb27c97e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -15,35 +15,40 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(PageSelector) }; + private readonly PageSelector pageSelector; + public TestScenePageSelector() { - Child = new PageSelector(200) + Child = pageSelector = new PageSelector { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - AddStep("1 max pages", () => redraw(1)); - AddStep("10 max pages", () => redraw(10)); - AddStep("200 max pages, current 199", () => redraw(200, 199)); - AddStep("200 max pages, current 201", () => redraw(200, 201)); - AddStep("200 max pages, current -10", () => redraw(200, -10)); - } - - private void redraw(int maxPages, int currentPage = 0) - { - Clear(); - - var selector = new PageSelector(maxPages) + AddStep("10 max pages", () => setMaxPages(10)); + AddStep("200 max pages, current 199", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - - if (currentPage != 0) - selector.CurrentPage.Value = currentPage; - - Add(selector); + setMaxPages(200); + setCurrentPage(199); + }); + AddStep("200 max pages, current 201", () => + { + setMaxPages(200); + setCurrentPage(201); + }); + AddStep("200 max pages, current -10", () => + { + setMaxPages(200); + setCurrentPage(-10); + }); + AddStep("-10 max pages", () => + { + setMaxPages(-10); + }); } + + private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; + + private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9db6366ad6..66f51a6dad 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -20,17 +20,15 @@ namespace osu.Game.Graphics.UserInterface public class PageSelector : CompositeDrawable { public readonly BindableInt CurrentPage = new BindableInt(1); + public readonly BindableInt MaxPages = new BindableInt(1); - private readonly int maxPages; private readonly FillFlowContainer itemsFlow; private readonly Button previousPageButton; private readonly Button nextPageButton; - public PageSelector(int maxPages) + public PageSelector() { - this.maxPages = maxPages; - AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer { @@ -59,24 +57,48 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw(), true); + MaxPages.BindValueChanged(pagesAmount => onMaxPagesChanged(pagesAmount.NewValue), true); + CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue), true); } - private void redraw() + private void onMaxPagesChanged(int pagesAmount) { - if (CurrentPage.Value > maxPages) + if (pagesAmount < 1) { - CurrentPage.Value = maxPages; + MaxPages.Value = 1; return; } - if (CurrentPage.Value < 1) + if (CurrentPage.Value > pagesAmount) + { + CurrentPage.Value = pagesAmount; + return; + } + + redraw(); + } + + private void onCurrentPageChanged(int newPage) + { + if (newPage > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + if (newPage < 1) { CurrentPage.Value = 1; return; } + redraw(); + } + + private void redraw() + { int newPage = CurrentPage.Value; + int maxPages = MaxPages.Value; previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; From eb683b079b5c8dd20888a8dab9ce5ac8daf6077a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 05:12:50 +0300 Subject: [PATCH 12/76] Adjust colours --- osu.Game/Graphics/UserInterface/PageSelector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 66f51a6dad..25e6ed654c 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -172,8 +172,8 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.Seafoam; - HoverColour = colours.Seafoam.Lighten(30f); + IdleColour = colours.Lime; + HoverColour = colours.Lime.Lighten(20f); } protected abstract Drawable CreateText(string text); From 41be9b3f8f08dd691a38ab1ff50872d126fe9f2e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 16:45:38 +0300 Subject: [PATCH 13/76] Add asserts to the test scene --- osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 14cb27c97e..3516e23f98 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -36,15 +36,18 @@ namespace osu.Game.Tests.Visual.UserInterface setMaxPages(200); setCurrentPage(201); }); + AddAssert("Current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); AddStep("200 max pages, current -10", () => { setMaxPages(200); setCurrentPage(-10); }); + AddAssert("Current is 1", () => pageSelector.CurrentPage.Value == 1); AddStep("-10 max pages", () => { setMaxPages(-10); }); + AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; From 2c0694257cd82e1dd539e0fb88424db7a6e1daba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:44:34 +0900 Subject: [PATCH 14/76] Fix incorrect spritetext usage --- osu.Game/Graphics/UserInterface/PageSelector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 25e6ed654c..c9f0f3c74d 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Graphics.Sprites; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -192,7 +193,7 @@ namespace osu.Game.Graphics.UserInterface { } - protected override Drawable CreateText(string text) => SpriteText = new SpriteText + protected override Drawable CreateText(string text) => SpriteText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -293,7 +294,7 @@ namespace osu.Game.Graphics.UserInterface Direction = FillDirection.Horizontal, Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Text = text.ToUpper(), Font = OsuFont.GetFont(size: 12), From 6fbbee3093a124021ee80bb053807dba8ec71832 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 19:03:39 +0300 Subject: [PATCH 15/76] Move PageSelector to another namespace and organize TestScene --- .../UserInterface/TestScenePageSelector.cs | 18 +++++++++++++----- .../{ => PageSelector}/PageSelector.cs | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) rename osu.Game/Graphics/UserInterface/{ => PageSelector}/PageSelector.cs (99%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 3516e23f98..59491b7f90 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; -using osu.Game.Graphics.UserInterface; +using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface.PageSelector; namespace osu.Game.Tests.Visual.UserInterface { @@ -19,12 +20,19 @@ namespace osu.Game.Tests.Visual.UserInterface public TestScenePageSelector() { - Child = pageSelector = new PageSelector + AddRange(new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + pageSelector = new PageSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + } + [Test] + public void TestPageSelectorValues() + { AddStep("10 max pages", () => setMaxPages(10)); AddStep("200 max pages, current 199", () => { diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs similarity index 99% rename from osu.Game/Graphics/UserInterface/PageSelector.cs rename to osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index c9f0f3c74d..6767cb3a75 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -16,7 +16,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Graphics.UserInterface +namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { From 70387c19f3f4be9413541b2588d1cf0d9de49d60 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 19:36:05 +0300 Subject: [PATCH 16/76] Implement proper DrawablePage component --- .../UserInterface/TestScenePageSelector.cs | 17 ++- .../PageSelector/DrawablePage.cs | 108 ++++++++++++++++++ .../PageSelector/PageSelector.cs | 2 + 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 59491b7f90..aad640ab58 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -13,10 +13,12 @@ namespace osu.Game.Tests.Visual.UserInterface { public override IReadOnlyList RequiredTypes => new[] { - typeof(PageSelector) + typeof(PageSelector), + typeof(DrawablePage) }; private readonly PageSelector pageSelector; + private readonly DrawablePage drawablePage; public TestScenePageSelector() { @@ -26,6 +28,12 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + }, + drawablePage = new DrawablePage(1234) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 50 }, } }); } @@ -58,6 +66,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } + [Test] + public void TestDrawablePage() + { + AddStep("Select", () => drawablePage.Selected = true); + AddStep("Deselect", () => drawablePage.Selected = false); + } + private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs new file mode 100644 index 0000000000..fe91874159 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -0,0 +1,108 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Framework.Input.Events; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class DrawablePage : OsuClickableContainer + { + private const int duration = 200; + + private readonly BindableBool selected = new BindableBool(); + + public bool Selected + { + get => selected.Value; + set => selected.Value = value; + } + + [Resolved] + private OsuColour colours { get; set; } + + private readonly Box background; + private readonly OsuSpriteText text; + + public DrawablePage(int page) + { + AutoSizeAxes = Axes.X; + Height = PageSelector.HEIGHT; + Child = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = page.ToString(), + Font = OsuFont.GetFont(size: 12), + Margin = new MarginPadding { Horizontal = 10 } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + selected.BindValueChanged(onSelectedChanged, true); + } + + private void onSelectedChanged(ValueChangedEvent selected) + { + background.FadeTo(selected.NewValue ? 1 : 0, duration, Easing.OutQuint); + text.FadeColour(selected.NewValue ? colours.GreySeafoamDarker : colours.Lime, duration, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + if (!selected.Value) + selected.Value = true; + + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + updateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateHoverState(); + } + + private void updateHoverState() + { + if (selected.Value) + return; + + text.FadeColour(IsHovered ? colours.Lime.Lighten(20f) : colours.Lime, duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 6767cb3a75..54e3a035ec 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -20,6 +20,8 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { + public const int HEIGHT = 20; + public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); From 9af9da039da0c696a3277f4f19d5251a7561883f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 21:14:56 +0300 Subject: [PATCH 17/76] Implement proper PageSelectorItem --- .../UserInterface/TestScenePageSelector.cs | 5 +- .../PageSelector/DrawablePage.cs | 79 ++--- .../PageSelector/PageSelector.cs | 284 +++--------------- .../PageSelector/PageSelectorButton.cs | 77 +++++ .../PageSelector/PageSelectorItem.cs | 75 +++++ 5 files changed, 222 insertions(+), 298 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index aad640ab58..33deff58dc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -14,7 +14,9 @@ namespace osu.Game.Tests.Visual.UserInterface public override IReadOnlyList RequiredTypes => new[] { typeof(PageSelector), - typeof(DrawablePage) + typeof(DrawablePage), + typeof(PageSelectorButton), + typeof(PageSelectorItem) }; private readonly PageSelector pageSelector; @@ -42,6 +44,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestPageSelectorValues() { AddStep("10 max pages", () => setMaxPages(10)); + AddStep("11 max pages", () => setMaxPages(11)); AddStep("200 max pages, current 199", () => { setMaxPages(200); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs index fe91874159..20f418085d 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -1,22 +1,16 @@ // 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.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface.PageSelector { - public class DrawablePage : OsuClickableContainer + public class DrawablePage : PageSelectorItem { - private const int duration = 200; - private readonly BindableBool selected = new BindableBool(); public bool Selected @@ -25,44 +19,33 @@ namespace osu.Game.Graphics.UserInterface.PageSelector set => selected.Value = value; } - [Resolved] - private OsuColour colours { get; set; } + public int Page { get; private set; } - private readonly Box background; - private readonly OsuSpriteText text; + private OsuSpriteText text; public DrawablePage(int page) { - AutoSizeAxes = Axes.X; - Height = PageSelector.HEIGHT; - Child = new CircularContainer + Page = page; + text.Text = page.ToString(); + + Background.Alpha = 0; + + Action = () => { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = page.ToString(), - Font = OsuFont.GetFont(size: 12), - Margin = new MarginPadding { Horizontal = 10 } - } - } + if (!selected.Value) + selected.Value = true; }; } + protected override Drawable CreateContent() => text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + }; + [BackgroundDependencyLoader] private void load() { - background.Colour = colours.Lime; + Background.Colour = Colours.Lime; } protected override void LoadComplete() @@ -73,36 +56,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { - background.FadeTo(selected.NewValue ? 1 : 0, duration, Easing.OutQuint); - text.FadeColour(selected.NewValue ? colours.GreySeafoamDarker : colours.Lime, duration, Easing.OutQuint); + Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? Colours.GreySeafoamDarker : Colours.Lime, DURATION, Easing.OutQuint); } - protected override bool OnClick(ClickEvent e) - { - if (!selected.Value) - selected.Value = true; - - return base.OnClick(e); - } - - protected override bool OnHover(HoverEvent e) - { - updateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateHoverState(); - } - - private void updateHoverState() + protected override void UpdateHoverState() { if (selected.Value) return; - text.FadeColour(IsHovered ? colours.Lime.Lighten(20f) : colours.Lime, duration, Easing.OutQuint); + text.FadeColour(IsHovered ? Colours.Lime.Lighten(20f) : Colours.Lime, DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 54e3a035ec..ae6fc2b500 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -4,17 +4,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using System; -using osuTK; -using osu.Game.Graphics.Containers; -using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface.PageSelector { @@ -25,10 +15,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); - private readonly FillFlowContainer itemsFlow; + private readonly FillFlowContainer itemsFlow; - private readonly Button previousPageButton; - private readonly Button nextPageButton; + private readonly PageSelectorButton previousPageButton; + private readonly PageSelectorButton nextPageButton; public PageSelector() { @@ -39,16 +29,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector Direction = FillDirection.Horizontal, Children = new Drawable[] { - previousPageButton = new Button(false, "prev") + previousPageButton = new PageSelectorButton(false, "prev") { Action = () => CurrentPage.Value -= 1, }, - itemsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, }, - nextPageButton = new Button(true, "next") + nextPageButton = new PageSelectorButton(true, "next") { Action = () => CurrentPage.Value += 1 } @@ -60,253 +50,69 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - MaxPages.BindValueChanged(pagesAmount => onMaxPagesChanged(pagesAmount.NewValue), true); - CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue), true); - } - - private void onMaxPagesChanged(int pagesAmount) - { - if (pagesAmount < 1) - { - MaxPages.Value = 1; - return; - } - - if (CurrentPage.Value > pagesAmount) - { - CurrentPage.Value = pagesAmount; - return; - } - + MaxPages.BindValueChanged(_ => redraw()); + CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue)); redraw(); } private void onCurrentPageChanged(int newPage) { - if (newPage > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } - if (newPage < 1) { CurrentPage.Value = 1; return; } - redraw(); + if (newPage > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + itemsFlow.ForEach(page => page.Selected = page.Page == newPage ? true : false); + updateButtonsState(); } private void redraw() + { + itemsFlow.Clear(); + + if (MaxPages.Value < 1) + { + MaxPages.Value = 1; + return; + } + + for (int i = 1; i <= MaxPages.Value; i++) + addDrawablePage(i); + + if (CurrentPage.Value > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + if (CurrentPage.Value < 1) + { + CurrentPage.Value = 1; + return; + } + + CurrentPage.TriggerChange(); + } + + private void updateButtonsState() { int newPage = CurrentPage.Value; int maxPages = MaxPages.Value; previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; - - itemsFlow.Clear(); - - if (newPage > 3) - addDrawablePage(1); - - if (newPage > 4) - addPlaceholder(); - - for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) - { - if (i == newPage) - addDrawableCurrentPage(); - else - addDrawablePage(i); - } - - if (newPage + 2 < maxPages - 1) - addPlaceholder(); - - if (newPage + 2 < maxPages) - addDrawablePage(maxPages); } - private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page.ToString()) + private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page) { Action = () => CurrentPage.Value = page, }); - - private void addPlaceholder() => itemsFlow.Add(new Placeholder()); - - private void addDrawableCurrentPage() => itemsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - - private abstract class PageItem : OsuHoverContainer - { - private const int margin = 10; - private const int height = 20; - - protected override Container Content => contentContainer; - - private readonly CircularContainer contentContainer; - - protected PageItem(string text) - { - AutoSizeAxes = Axes.X; - Height = height; - - base.Content.Add(contentContainer = new CircularContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - }); - - var background = CreateBackground(); - if (background != null) - Add(background); - - var drawableText = CreateText(text); - - if (drawableText != null) - { - drawableText.Margin = new MarginPadding { Horizontal = margin }; - Add(drawableText); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.Lime; - HoverColour = colours.Lime.Lighten(20f); - } - - protected abstract Drawable CreateText(string text); - - protected virtual Drawable CreateBackground() => null; - } - - private class DrawablePage : PageItem - { - protected SpriteText SpriteText; - - protected override IEnumerable EffectTargets => new[] { SpriteText }; - - public DrawablePage(string text) - : base(text) - { - } - - protected override Drawable CreateText(string text) => SpriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = text, - Font = OsuFont.GetFont(size: 12), - }; - } - - private class SelectedPage : DrawablePage - { - private Box background; - - protected override IEnumerable EffectTargets => new[] { background }; - - public SelectedPage(string text) - : base(text) - { - } - - protected override Drawable CreateBackground() => background = new Box - { - RelativeSizeAxes = Axes.Both, - }; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SpriteText.Colour = colours.GreySeafoamDark; - } - } - - private class Placeholder : DrawablePage - { - public Placeholder() - : base("...") - { - } - } - - private class Button : PageItem - { - private const int duration = 100; - - private Box background; - private FillFlowContainer textContainer; - private SpriteIcon icon; - private readonly Box fadeBox; - - protected override IEnumerable EffectTargets => new[] { textContainer }; - - public Button(bool rightAligned, string text) - : base(text) - { - var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - - textContainer.ForEach(drawable => - { - drawable.Anchor = Anchor.y1 | alignment; - drawable.Origin = Anchor.y1 | alignment; - }); - - icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; - - Add(fadeBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreySeafoamDark; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Enabled.BindValueChanged(onEnabledChanged, true); - } - - private void onEnabledChanged(ValueChangedEvent enabled) - { - fadeBox.FadeTo(enabled.NewValue ? 0 : 1, duration); - } - - protected override Drawable CreateBackground() => background = new Box - { - RelativeSizeAxes = Axes.Both, - }; - - protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = text.ToUpper(), - Font = OsuFont.GetFont(size: 12), - }, - icon = new SpriteIcon - { - Size = new Vector2(8), - }, - } - }; - } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs new file mode 100644 index 0000000000..df007b32e0 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -0,0 +1,77 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelectorButton : PageSelectorItem + { + private readonly Box fadeBox; + private SpriteIcon icon; + private OsuSpriteText name; + private FillFlowContainer buttonContent; + + public PageSelectorButton(bool rightAligned, string text) + { + Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); + + var alignment = rightAligned ? Anchor.x0 : Anchor.x2; + + buttonContent.ForEach(drawable => + { + drawable.Anchor = Anchor.y1 | alignment; + drawable.Origin = Anchor.y1 | alignment; + }); + + icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; + + name.Text = text.ToUpper(); + } + + protected override Drawable CreateContent() => buttonContent = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + }, + icon = new SpriteIcon + { + Size = new Vector2(8), + }, + } + }; + + [BackgroundDependencyLoader] + private void load() + { + Background.Colour = Colours.GreySeafoamDark; + name.Colour = icon.Colour = Colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); + } + + protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeafoam : Colours.GreySeafoamDark, DURATION, Easing.OutQuint); + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs new file mode 100644 index 0000000000..d457b7ea0e --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -0,0 +1,75 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Extensions.IEnumerableExtensions; +using JetBrains.Annotations; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public abstract class PageSelectorItem : OsuClickableContainer + { + protected const int DURATION = 200; + + [Resolved] + protected OsuColour Colours { get; private set; } + + protected override Container Content => content; + + protected readonly Box Background; + private readonly CircularContainer content; + + protected PageSelectorItem() + { + AutoSizeAxes = Axes.X; + Height = PageSelector.HEIGHT; + base.Content.Add(content = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + CreateContent().With(content => + { + content.Anchor = Anchor.Centre; + content.Origin = Anchor.Centre; + content.Margin = new MarginPadding { Horizontal = 10 }; + }) + } + }); + } + + [NotNull] + protected abstract Drawable CreateContent(); + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected abstract void UpdateHoverState(); + } +} From b1c5e437ccf2ca260d5b8f177d78144651b1d24d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 21:22:45 +0300 Subject: [PATCH 18/76] Remove usings --- .../UserInterface/PageSelector/PageSelectorItem.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index d457b7ea0e..63eb87a638 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -3,17 +3,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Graphics.Sprites; -using osuTK.Graphics; -using osuTK; -using osu.Framework.Extensions.IEnumerableExtensions; using JetBrains.Annotations; namespace osu.Game.Graphics.UserInterface.PageSelector From 37482b2ad474379ed941a5ab3a8c40f813427eaf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:05:34 +0300 Subject: [PATCH 19/76] CI fixes --- osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs | 2 +- .../Graphics/UserInterface/PageSelector/PageSelectorItem.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index ae6fc2b500..eaa102bdd2 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector return; } - itemsFlow.ForEach(page => page.Selected = page.Page == newPage ? true : false); + itemsFlow.ForEach(page => page.Selected = page.Page == newPage); updateButtonsState(); } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index 63eb87a638..5f0bfcdfdb 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - Children = new Drawable[] + Children = new[] { Background = new Box { From d3c2dc43bd28812bafffb6d81a596268ed5ddca1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:25:08 +0300 Subject: [PATCH 20/76] TestScene improvements --- .../UserInterface/TestScenePageSelector.cs | 48 +++++++++---------- .../PageSelector/PageSelector.cs | 15 ++---- .../PageSelector/PageSelectorButton.cs | 1 + 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 33deff58dc..5e1105c834 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -41,32 +41,30 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestPageSelectorValues() + public void TestCurrentPageReset() { - AddStep("10 max pages", () => setMaxPages(10)); - AddStep("11 max pages", () => setMaxPages(11)); - AddStep("200 max pages, current 199", () => - { - setMaxPages(200); - setCurrentPage(199); - }); - AddStep("200 max pages, current 201", () => - { - setMaxPages(200); - setCurrentPage(201); - }); - AddAssert("Current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); - AddStep("200 max pages, current -10", () => - { - setMaxPages(200); - setCurrentPage(-10); - }); - AddAssert("Current is 1", () => pageSelector.CurrentPage.Value == 1); - AddStep("-10 max pages", () => - { - setMaxPages(-10); - }); - AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); + AddStep("Set 10 pages", () => setMaxPages(10)); + AddStep("Select 5 page", () => setCurrentPage(5)); + AddStep("Set 11 pages", () => setMaxPages(11)); + AddAssert("Check 1 page is current", () => pageSelector.CurrentPage.Value == 1); + } + + [Test] + public void TestUnexistingPageSelection() + { + AddStep("Set 10 pages", () => setMaxPages(10)); + AddStep("Select 11 page", () => setCurrentPage(11)); + AddAssert("Check current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + + AddStep("Select -1 page", () => setCurrentPage(-1)); + AddAssert("Check current is 1", () => pageSelector.CurrentPage.Value == 1); + } + + [Test] + public void TestNegativeMaxPages() + { + AddStep("Set -10 pages", () => setMaxPages(-10)); + AddAssert("Check current and max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } [Test] diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index eaa102bdd2..c2482d6330 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -86,19 +86,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector for (int i = 1; i <= MaxPages.Value; i++) addDrawablePage(i); - if (CurrentPage.Value > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } - - if (CurrentPage.Value < 1) - { + if (CurrentPage.Value == 1) + CurrentPage.TriggerChange(); + else CurrentPage.Value = 1; - return; - } - - CurrentPage.TriggerChange(); } private void updateButtonsState() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index df007b32e0..e81ce20d27 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -46,6 +46,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), Children = new Drawable[] { name = new OsuSpriteText From 753db9599a225784c19e3459b6d830d5f62683c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:29:11 +0300 Subject: [PATCH 21/76] Move items height out of PageSelector --- osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs | 2 -- .../Graphics/UserInterface/PageSelector/PageSelectorItem.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index c2482d6330..8e055faea3 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -10,8 +10,6 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { - public const int HEIGHT = 20; - public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index 5f0bfcdfdb..cd61961dbe 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected PageSelectorItem() { AutoSizeAxes = Axes.X; - Height = PageSelector.HEIGHT; + Height = 20; base.Content.Add(content = new CircularContainer { RelativeSizeAxes = Axes.Y, From 20ab415838fab4cca9fcf4675d908670032d1b56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jan 2020 18:04:27 +0900 Subject: [PATCH 22/76] Reword tests --- .../UserInterface/TestScenePageSelector.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 5e1105c834..6494486d4e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -41,30 +41,31 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestCurrentPageReset() + public void TestResetCurrentPage() { AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select 5 page", () => setCurrentPage(5)); + AddStep("Select page 5", () => setCurrentPage(5)); AddStep("Set 11 pages", () => setMaxPages(11)); - AddAssert("Check 1 page is current", () => pageSelector.CurrentPage.Value == 1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); } [Test] - public void TestUnexistingPageSelection() + public void TestOutOfBoundsSelection() { AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select 11 page", () => setCurrentPage(11)); - AddAssert("Check current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + AddStep("Select page 11", () => setCurrentPage(11)); + AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); - AddStep("Select -1 page", () => setCurrentPage(-1)); - AddAssert("Check current is 1", () => pageSelector.CurrentPage.Value == 1); + AddStep("Select page -1", () => setCurrentPage(-1)); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); } [Test] public void TestNegativeMaxPages() { AddStep("Set -10 pages", () => setMaxPages(-10)); - AddAssert("Check current and max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); + AddAssert("Max is 1", () => pageSelector.MaxPages.Value == 1); } [Test] From eaa464e548582da39515ded5d9421146eabdcfc6 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 02:58:08 +0100 Subject: [PATCH 23/76] Initial implementation of adjustable positional hitobject audio strength --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Localisation/AudioSettingsStrings.cs | 5 ++++ .../Sections/Gameplay/AudioSettings.cs | 28 ++++++++++++++++++- .../Objects/Drawables/DrawableHitObject.cs | 4 ++- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 84da3f666d..a124c1c5ae 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -97,6 +97,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0.1f, 1f); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); @@ -251,6 +252,7 @@ namespace osu.Game.Configuration BlurLevel, LightenDuringBreaks, ShowStoryboard, + PositionalHitsoundsLevel, KeyOverlay, PositionalHitSounds, AlwaysPlayFirstComboBreak, diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 008781c2e5..a55816b401 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -32,6 +32,11 @@ namespace osu.Game.Localisation /// /// "Master" /// + public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level."); + + /// + /// "Level" + /// public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master"); /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index dba64d695a..c239fd282a 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Localisation; +using osu.Framework.Graphics.Containers; + namespace osu.Game.Overlays.Settings.Sections.Gameplay { @@ -13,9 +16,15 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader; + private Bindable positionalHitsoundsLevel; + + private FillFlowContainer> positionalHitsoundsSettings; + + [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config,OsuConfigManager osuConfig) { + positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] { new SettingsCheckbox @@ -23,6 +32,23 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = GameplaySettingsStrings.PositionalHitsounds, Current = config.GetBindable(OsuSetting.PositionalHitSounds) }, + positionalHitsoundsSettings = new FillFlowContainer> + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + Children = new[] + { + new SettingsSlider + { + LabelText = AudioSettingsStrings.PositionalLevel, + Current = positionalHitsoundsLevel, + KeyboardStep = 0.01f, + DisplayAsPercentage = true, + }, + } + }, new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak, diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 01817147ae..1a0fe8d004 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK.Graphics; +using osu.Game.Overlays.Settings.Sections.Gameplay; namespace osu.Game.Rulesets.Objects.Drawables { @@ -124,6 +125,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); private readonly Bindable userPositionalHitSounds = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); @@ -532,7 +534,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { - const float balance_adjust_amount = 0.4f; + float balance_adjust_amount = positionalHitsoundsLevel.Value; return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); } From e83115ad5e617843937eb751a74bc60538fa0796 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 03:25:11 +0100 Subject: [PATCH 24/76] Binding logic fix, nullification of unnecessary path --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a124c1c5ae..ed0fe17e27 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -97,7 +97,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay - SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0.1f, 1f); + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0, 1); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1a0fe8d004..601756485b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -171,6 +171,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load(OsuConfigManager config, ISkinSource skinSource) { config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); + config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel); // Explicit non-virtual function call. base.AddInternal(Samples = new PausableSkinnableSound()); @@ -534,9 +535,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { - float balance_adjust_amount = positionalHitsoundsLevel.Value; + float balance_adjust_amount = positionalHitsoundsLevel.Value*2; - return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); + return balance_adjust_amount * (true ? position - 0.5f : 0); } /// From ff9c68dd6aafcb0103e33f8f735674a6b1cb2b11 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 03:28:35 +0100 Subject: [PATCH 25/76] cleanup --- osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index c239fd282a..9e677943c2 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -9,7 +9,6 @@ using osu.Game.Configuration; using osu.Game.Localisation; using osu.Framework.Graphics.Containers; - namespace osu.Game.Overlays.Settings.Sections.Gameplay { public class AudioSettings : SettingsSubsection @@ -20,7 +19,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay private FillFlowContainer> positionalHitsoundsSettings; - [BackgroundDependencyLoader] private void load(OsuConfigManager config,OsuConfigManager osuConfig) { From c3fb7937627301cb92359ff2939cd9e3197cae31 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 14:06:53 +0100 Subject: [PATCH 26/76] Fixed the problems that were brought up and deleted the old bind logic --- osu.Game/Configuration/OsuConfigManager.cs | 4 +--- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- osu.Game/Localisation/GameplaySettingsStrings.cs | 5 ----- .../Settings/Sections/Gameplay/AudioSettings.cs | 7 +------ .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 11 +++++------ 5 files changed, 8 insertions(+), 21 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ed0fe17e27..90cf09dce1 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -97,7 +97,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay - SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0, 1); + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); @@ -109,7 +109,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.KeyOverlay, false); - SetDefault(OsuSetting.PositionalHitSounds, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); SetDefault(OsuSetting.FloatingComments, false); @@ -254,7 +253,6 @@ namespace osu.Game.Configuration ShowStoryboard, PositionalHitsoundsLevel, KeyOverlay, - PositionalHitSounds, AlwaysPlayFirstComboBreak, FloatingComments, HUDVisibilityMode, diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index a55816b401..ba48c412c4 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Localisation /// /// "Master" /// - public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level."); + public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level"); /// /// "Level" diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index fa92187650..84c3704e26 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -84,11 +84,6 @@ namespace osu.Game.Localisation /// public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay"); - /// - /// "Positional hitsounds" - /// - public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds"); - /// /// "Always play first combo break sound" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index 9e677943c2..a5c5399e98 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -24,12 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = GameplaySettingsStrings.PositionalHitsounds, - Current = config.GetBindable(OsuSetting.PositionalHitSounds) - }, + { positionalHitsoundsSettings = new FillFlowContainer> { Direction = FillDirection.Vertical, diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 601756485b..ea153bd0e6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -124,10 +124,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); - private readonly Bindable userPositionalHitSounds = new Bindable(); - private readonly Bindable positionalHitsoundsLevel = new Bindable(); - private readonly Bindable comboIndexBindable = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; @@ -170,7 +168,6 @@ namespace osu.Game.Rulesets.Objects.Drawables [BackgroundDependencyLoader] private void load(OsuConfigManager config, ISkinSource skinSource) { - config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel); // Explicit non-virtual function call. @@ -535,9 +532,11 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { + double returnedvalue; float balance_adjust_amount = positionalHitsoundsLevel.Value*2; - - return balance_adjust_amount * (true ? position - 0.5f : 0); + returnedvalue = balance_adjust_amount *(position - 0.5f ); + + return returnedvalue; } /// From 9065179c523eb1f1cfdfa02559da4f9bdfddde60 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 14:09:30 +0100 Subject: [PATCH 27/76] Fix --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ea153bd0e6..152f92985e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); + private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); From 9c42cc0c05e643c807fb8fc05afba98a95e0cd3c Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 14:09:30 +0100 Subject: [PATCH 28/76] Fix --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ea153bd0e6..01c573eb94 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -125,7 +125,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); - private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); + private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; From fd5af1fbe7e0f776d7b5ff5bd5ac7b4e3479fd70 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Fri, 17 Dec 2021 13:16:06 +0100 Subject: [PATCH 29/76] Code refactor and name changes cleaned code up with Jetbrains i hope it suffices --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- .../Settings/Sections/Gameplay/AudioSettings.cs | 10 +++++----- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 11 ++++------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index ba48c412c4..f298717c99 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Localisation /// /// "Master" /// - public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level"); + public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation"); /// /// "Level" diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index a5c5399e98..4238ad1605 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -20,11 +20,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay private FillFlowContainer> positionalHitsoundsSettings; [BackgroundDependencyLoader] - private void load(OsuConfigManager config,OsuConfigManager osuConfig) + private void load(OsuConfigManager config, OsuConfigManager osuConfig) { positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] - { + { positionalHitsoundsSettings = new FillFlowContainer> { Direction = FillDirection.Vertical, @@ -38,15 +38,15 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = AudioSettingsStrings.PositionalLevel, Current = positionalHitsoundsLevel, KeyboardStep = 0.01f, - DisplayAsPercentage = true, - }, + DisplayAsPercentage = true + } } }, new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak, Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak) - }, + } }; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 56e02e3ddd..4ab513bf19 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK.Graphics; -using osu.Game.Overlays.Settings.Sections.Gameplay; namespace osu.Game.Rulesets.Objects.Drawables { @@ -126,8 +125,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly BindableList samplesBindable = new BindableList(); private readonly Bindable comboIndexBindable = new Bindable(); - private readonly Bindable positionalHitsoundsLevel = new Bindable(); - private readonly Bindable comboIndexBindable = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; @@ -534,10 +532,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { - double returnedvalue; - float balance_adjust_amount = positionalHitsoundsLevel.Value*2; - returnedvalue = balance_adjust_amount *(position - 0.5f ); - + float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2; + double returnedvalue = balanceAdjustAmount * (position - 0.5f); + return returnedvalue; } From e448c28cade0b45e9660d5b76969e9951fbacd76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 26 Dec 2021 16:26:33 +0900 Subject: [PATCH 30/76] Change `IdleHandler` to not consider mouse movement as active unless focused --- osu.Game/Input/IdleTracker.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index bfe21f650a..4ef676b3b6 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -1,11 +1,13 @@ // 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; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Input.Bindings; namespace osu.Game.Input @@ -63,8 +65,14 @@ namespace osu.Game.Input public void OnReleased(KeyBindingReleaseEvent e) => updateLastInteractionTime(); + [Resolved] + private GameHost host { get; set; } + protected override bool Handle(UIEvent e) { + if (!host.IsActive.Value) + return base.Handle(e); + switch (e) { case KeyDownEvent _: From 7c25ce81e16d8cd546c2b6eab562e6cf9acf63ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 26 Dec 2021 16:26:47 +0900 Subject: [PATCH 31/76] Further reduce chat poll rate when idle or not visible --- osu.Game/Online/Chat/ChannelManager.cs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index edaf135e93..4889ad0a3b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Database; +using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -67,11 +68,33 @@ namespace osu.Game.Online.Chat public readonly BindableBool HighPollRate = new BindableBool(); + private IBindable isIdle; + public ChannelManager() { CurrentChannel.ValueChanged += currentChannelChanged; + } - HighPollRate.BindValueChanged(enabled => TimeBetweenPolls.Value = enabled.NewValue ? 1000 : 6000, true); + [BackgroundDependencyLoader] + private void load(IdleTracker idleTracker) + { + HighPollRate.BindValueChanged(updatePollRate); + + isIdle = idleTracker.IsIdle.GetBoundCopy(); + isIdle.BindValueChanged(updatePollRate, true); + } + + private void updatePollRate(ValueChangedEvent valueChangedEvent) + { + // Polling will eventually be replaced with websocket, but let's avoid doing these background operations as much as possible for now. + // The only loss will be delayed PM/message highlight notifications. + + if (HighPollRate.Value) + TimeBetweenPolls.Value = 1000; + else if (!isIdle.Value) + TimeBetweenPolls.Value = 60000; + else + TimeBetweenPolls.Value = 600000; } /// From 8d927920cb89dae62a6dbee7b4ecf8442be610b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Dec 2021 13:31:07 +0900 Subject: [PATCH 32/76] Add comment explainng why to block idle updates when host is not active --- osu.Game/Input/IdleTracker.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 4ef676b3b6..e2f13309cf 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -70,6 +70,9 @@ namespace osu.Game.Input protected override bool Handle(UIEvent e) { + // Even when not active, `MouseMoveEvent`s will arrive. + // We don't want these to trigger a non-idle state as it's quite often the user interacting + // with other windows while osu! is in the background. if (!host.IsActive.Value) return base.Handle(e); From 448c6ed515572a667524792761056b13889afe70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 Dec 2021 15:50:43 +0100 Subject: [PATCH 33/76] Generalise `KiaiFlashingSprite` for arbitrary drawables --- .../Skinning/Legacy/KiaiFlashingDrawable.cs | 50 +++++++++++++++ .../Skinning/Legacy/KiaiFlashingSprite.cs | 61 ------------------- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 7 +-- 3 files changed, 53 insertions(+), 65 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs new file mode 100644 index 0000000000..cd1d05c985 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs @@ -0,0 +1,50 @@ +// 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.Audio.Track; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + internal class KiaiFlashingDrawable : BeatSyncedContainer + { + private readonly Drawable flashingDrawable; + + private const float flash_opacity = 0.3f; + + public KiaiFlashingDrawable(Func creationFunc) + { + AutoSizeAxes = Axes.Both; + + Children = new[] + { + creationFunc.Invoke().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + }), + flashingDrawable = creationFunc.Invoke().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + d.Alpha = 0; + d.Blending = BlendingParameters.Additive; + }) + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (!effectPoint.KiaiMode) + return; + + flashingDrawable + .FadeTo(flash_opacity) + .Then() + .FadeOut(timingPoint.BeatLength * 0.75f); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs deleted file mode 100644 index 4a1d69ad41..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs +++ /dev/null @@ -1,61 +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 osu.Framework.Audio.Track; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Rulesets.Osu.Skinning.Legacy -{ - internal class KiaiFlashingSprite : BeatSyncedContainer - { - private readonly Sprite mainSprite; - private readonly Sprite flashingSprite; - - public Texture Texture - { - set - { - mainSprite.Texture = value; - flashingSprite.Texture = value; - } - } - - private const float flash_opacity = 0.3f; - - public KiaiFlashingSprite() - { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - mainSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - flashingSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0, - Blending = BlendingParameters.Additive, - } - }; - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - if (!effectPoint.KiaiMode) - return; - - flashingSprite - .FadeTo(flash_opacity) - .Then() - .FadeOut(timingPoint.BeatLength * 0.75f); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index d2f84dcf84..9cda85c20c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -72,9 +73,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChildren = new[] { - hitCircleSprite = new KiaiFlashingSprite + hitCircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = baseTexture }) { - Texture = baseTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -82,9 +82,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = hitCircleOverlay = new KiaiFlashingSprite + Child = hitCircleOverlay = new KiaiFlashingDrawable(() => new Sprite { Texture = overlayTexture }) { - Texture = overlayTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, From b29c2bf9f317ea770d9dd60d066a3b08b914c1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 Dec 2021 15:36:30 +0100 Subject: [PATCH 34/76] Add test resources for animated legacy hit circle overlay --- ...overlay@2x.png => hitcircleoverlay-0@2x.png} | Bin .../special-skin/hitcircleoverlay-1@2x.png | Bin 0 -> 40858 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Osu.Tests/Resources/special-skin/{hitcircleoverlay@2x.png => hitcircleoverlay-0@2x.png} (100%) create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png similarity index 100% rename from osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png rename to osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..3b5e886933d2f7a8803a44304c5439058ef86fa2 GIT binary patch literal 40858 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�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 U^GtX>4gf! Date: Tue, 28 Dec 2021 15:53:54 +0100 Subject: [PATCH 35/76] Add support for animated legacy hit circle overlay --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 9cda85c20c..c6007885be 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -69,7 +69,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it. // the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist. // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin). - Texture overlayTexture = getTextureWithFallback("overlay"); InternalChildren = new[] { @@ -82,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = hitCircleOverlay = new KiaiFlashingDrawable(() => new Sprite { Texture = overlayTexture }) + Child = hitCircleOverlay = new KiaiFlashingDrawable(() => getAnimationWithFallback(@"overlay", 1000 / 2d)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -125,6 +124,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return tex ?? skin.GetTexture($"hitcircle{name}"); } + + Drawable getAnimationWithFallback(string name, double frameLength) + { + Drawable animation = null; + + if (!string.IsNullOrEmpty(priorityLookup)) + { + animation = skin.GetAnimation($"{priorityLookup}{name}", true, true, frameLength: frameLength); + + if (!allowFallback) + return animation; + } + + return animation ?? skin.GetAnimation($"hitcircle{name}", true, true, frameLength: frameLength); + } } protected override void LoadComplete() From 471eea750af41ec00b34fe4a22f928b2524d87e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Dec 2021 21:17:44 +0900 Subject: [PATCH 36/76] Fix calling `SkinEditorOverlay.Show` before the overlay is loaded causing an exception As seen at https://github.com/ppy/osu/runs/4652969942?check_suite_focus=true. --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 340c6ed931..6699e0f658 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -73,9 +73,13 @@ namespace osu.Game.Skinning.Editor skinEditor = new SkinEditor(target); skinEditor.State.BindValueChanged(editorVisibilityChanged); - Debug.Assert(skinEditor != null); - - LoadComponentAsync(skinEditor, AddInternal); + // Schedule ensures that if `Show` is called before this overlay is loaded, + // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). + Schedule(() => + { + Debug.Assert(skinEditor != null); + LoadComponentAsync(skinEditor, AddInternal); + }); } else skinEditor.Show(); From b1a444180fda72619ae33667411d28989d674cda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Dec 2021 21:46:34 +0900 Subject: [PATCH 37/76] Fix `Show` then `Reset` potentially resulting in incorrect load target --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 6699e0f658..ebf0a1214e 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -70,16 +70,18 @@ namespace osu.Game.Skinning.Editor // base call intentionally omitted. if (skinEditor == null) { - skinEditor = new SkinEditor(target); - skinEditor.State.BindValueChanged(editorVisibilityChanged); + var editor = new SkinEditor(target); + editor.State.BindValueChanged(editorVisibilityChanged); // Schedule ensures that if `Show` is called before this overlay is loaded, // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). Schedule(() => { - Debug.Assert(skinEditor != null); - LoadComponentAsync(skinEditor, AddInternal); + Debug.Assert(editor != null); + LoadComponentAsync(editor, AddInternal); }); + + skinEditor = editor; } else skinEditor.Show(); From ef49f2ed0e4835498d3d6b9b92431a59977d0700 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 16:02:08 +0900 Subject: [PATCH 38/76] Add extra extra safety against attempting to load a previously expired editor --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index ebf0a1214e..87da67dae0 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -73,15 +73,23 @@ namespace osu.Game.Skinning.Editor var editor = new SkinEditor(target); editor.State.BindValueChanged(editorVisibilityChanged); + skinEditor = editor; + // Schedule ensures that if `Show` is called before this overlay is loaded, // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). Schedule(() => { - Debug.Assert(editor != null); - LoadComponentAsync(editor, AddInternal); - }); + if (editor != skinEditor) + return; - skinEditor = editor; + LoadComponentAsync(editor, _ => + { + if (editor != skinEditor) + return; + + AddInternal(editor); + }); + }); } else skinEditor.Show(); From 089b756f932b1b59a4b66380553a9d0254d8a121 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 16:03:16 +0900 Subject: [PATCH 39/76] Invert logic to make reading easier --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 87da67dae0..86854ab6ff 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -67,32 +66,34 @@ namespace osu.Game.Skinning.Editor public override void Show() { - // base call intentionally omitted. - if (skinEditor == null) + // base call intentionally omitted as we have custom behaviour. + + if (skinEditor != null) { - var editor = new SkinEditor(target); - editor.State.BindValueChanged(editorVisibilityChanged); + skinEditor.Show(); + return; + } - skinEditor = editor; + var editor = new SkinEditor(target); + editor.State.BindValueChanged(editorVisibilityChanged); - // Schedule ensures that if `Show` is called before this overlay is loaded, - // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). - Schedule(() => + skinEditor = editor; + + // Schedule ensures that if `Show` is called before this overlay is loaded, + // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). + Schedule(() => + { + if (editor != skinEditor) + return; + + LoadComponentAsync(editor, _ => { if (editor != skinEditor) return; - LoadComponentAsync(editor, _ => - { - if (editor != skinEditor) - return; - - AddInternal(editor); - }); + AddInternal(editor); }); - } - else - skinEditor.Show(); + }); } private void editorVisibilityChanged(ValueChangedEvent visibility) From 408e8d57109ed44498601f02c829d6b4faa8c05d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 22:21:37 +0900 Subject: [PATCH 40/76] Fix null reference causing crash in `KiaiFlashingDrawable` Can occur if there is no fallback graphics available. Previously would work as it was only setting the `Texture`. As reported in https://github.com/ppy/osu/discussions/16281. --- .../Skinning/Legacy/KiaiFlashingDrawable.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs index cd1d05c985..4ee28d05b5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; +#nullable enable + namespace osu.Game.Rulesets.Osu.Skinning.Legacy { internal class KiaiFlashingDrawable : BeatSyncedContainer @@ -15,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private const float flash_opacity = 0.3f; - public KiaiFlashingDrawable(Func creationFunc) + public KiaiFlashingDrawable(Func creationFunc) { AutoSizeAxes = Axes.Both; Children = new[] { - creationFunc.Invoke().With(d => + (creationFunc.Invoke() ?? Empty()).With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; }), - flashingDrawable = creationFunc.Invoke().With(d => + flashingDrawable = (creationFunc.Invoke() ?? Empty()).With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; From f53a675ca3e9c04c1ba7378cbd8e1cab72e0e7a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 23:35:18 +0900 Subject: [PATCH 41/76] Fix `TestSceneMultiSpectatorLeaderboard` not waiting for user population --- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index a61e505970..543e6a91d0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -50,6 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); + AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType().Count() == 2); AddStep("add clock sources", () => { From e38e1bb1d7edd5a20ff401309cc1e38eb2eb199d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 13:22:49 +0900 Subject: [PATCH 42/76] Enable a couple of missing async related inspections --- osu.sln.DotSettings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 90bf4f09eb..44d75f265c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -19,6 +19,7 @@ HINT DO_NOT_SHOW WARNING + WARNING HINT HINT WARNING @@ -231,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING From 670a30b64bdf1f315898c7a931c0dccc3da7af70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 23:08:05 +0900 Subject: [PATCH 43/76] Remove usage of `.Result` in `ArchiveReader` --- osu.Game/IO/Archives/ArchiveReader.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index f787534e2d..1d8da16c72 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -32,7 +32,18 @@ namespace osu.Game.IO.Archives public abstract IEnumerable Filenames { get; } - public virtual byte[] Get(string name) => GetAsync(name).Result; + public virtual byte[] Get(string name) + { + using (Stream input = GetStream(name)) + { + if (input == null) + return null; + + byte[] buffer = new byte[input.Length]; + input.Read(buffer); + return buffer; + } + } public async Task GetAsync(string name, CancellationToken cancellationToken = default) { From 1262e76a584c4b1e748f9309ad1efcfaf6e8ee55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Dec 2021 23:18:03 +0900 Subject: [PATCH 44/76] Fix test failure due to missing DI cached `IdleTracker` --- osu.Game/Online/Chat/ChannelManager.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 4889ad0a3b..e9e145e2ab 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -68,20 +68,21 @@ namespace osu.Game.Online.Chat public readonly BindableBool HighPollRate = new BindableBool(); - private IBindable isIdle; + private readonly IBindable isIdle = new BindableBool(); public ChannelManager() { CurrentChannel.ValueChanged += currentChannelChanged; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(permitNulls: true)] private void load(IdleTracker idleTracker) { HighPollRate.BindValueChanged(updatePollRate); - - isIdle = idleTracker.IsIdle.GetBoundCopy(); isIdle.BindValueChanged(updatePollRate, true); + + if (idleTracker != null) + isIdle.BindTo(idleTracker.IsIdle); } private void updatePollRate(ValueChangedEvent valueChangedEvent) From 04d8fd3a58f59909b5ee1800a61f41182c7d0197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Jan 2022 15:31:50 +0900 Subject: [PATCH 45/76] Improve reliability of `TestStoryboardSkipOutro` Aims to resolve failures as seen at https://github.com/peppy/osu/runs/4677353822?check_suite_focus=true. Have run quite a lot locally with no failures (while removing the skip step 100% fails). --- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 48a97d54f7..69798dcb82 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -53,8 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen()); AddUntilStep("wait for score shown", () => Player.IsScoreShown); - AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration); } [Test] From 5dd024aab7d8add3a143725b1a21beab946da70e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 12:46:02 +0900 Subject: [PATCH 46/76] Remove outdated settings migration --- osu.Game/Configuration/OsuConfigManager.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 616e749c9b..6efbaa4d36 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -174,11 +174,6 @@ namespace osu.Game.Configuration if (!int.TryParse(pieces[1], out int monthDay)) return; int combined = (year * 10000) + monthDay; - - if (combined < 20210413) - { - SetValue(OsuSetting.EditorWaveformOpacity, 0.25f); - } } public override TrackedSettings CreateTrackedSettings() From 623d6d6d2d0635b55da3d99d499b0ef5a085b618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 12:46:20 +0900 Subject: [PATCH 47/76] Add migration of positional hitsounds setting to new level based setting --- osu.Game/Configuration/OsuConfigManager.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6efbaa4d36..07d2026c65 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -98,6 +98,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay + SetDefault(OsuSetting.PositionalHitsounds, true); // replaced by level setting below, can be removed 20220703. SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -174,6 +175,13 @@ namespace osu.Game.Configuration if (!int.TryParse(pieces[1], out int monthDay)) return; int combined = (year * 10000) + monthDay; + + if (combined < 20220103) + { + var positionalHitsoundsEnabled = GetBindable(OsuSetting.PositionalHitsounds); + if (!positionalHitsoundsEnabled.Value) + SetValue(OsuSetting.PositionalHitsoundsLevel, 0); + } } public override TrackedSettings CreateTrackedSettings() @@ -250,8 +258,9 @@ namespace osu.Game.Configuration BlurLevel, LightenDuringBreaks, ShowStoryboard, - PositionalHitsoundsLevel, KeyOverlay, + PositionalHitsounds, + PositionalHitsoundsLevel, AlwaysPlayFirstComboBreak, FloatingComments, HUDVisibilityMode, From 6356180b6abd5a11ab32508ad3dcd2984c561aee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 12:53:58 +0900 Subject: [PATCH 48/76] Remove unnecessary code and fix double nesting causing filtering to not work --- .../Sections/Gameplay/AudioSettings.cs | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index 4238ad1605..5029c6a617 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Localisation; -using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.Settings.Sections.Gameplay { @@ -15,32 +13,18 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader; - private Bindable positionalHitsoundsLevel; - - private FillFlowContainer> positionalHitsoundsSettings; - [BackgroundDependencyLoader] private void load(OsuConfigManager config, OsuConfigManager osuConfig) { - positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] { - positionalHitsoundsSettings = new FillFlowContainer> + new SettingsSlider { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Masking = true, - Children = new[] - { - new SettingsSlider - { - LabelText = AudioSettingsStrings.PositionalLevel, - Current = positionalHitsoundsLevel, - KeyboardStep = 0.01f, - DisplayAsPercentage = true - } - } + LabelText = AudioSettingsStrings.PositionalLevel, + Keywords = new[] { @"positional", @"balance" }, + Current = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel), + KeyboardStep = 0.01f, + DisplayAsPercentage = true }, new SettingsCheckbox { From b9851b278d8711532087714bb9612d7e3c8e0b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 13:18:27 +0900 Subject: [PATCH 49/76] Add padding to the bottom of the beatmap listing overlay to avoid hovered panels exceeding visible bounds Closes https://github.com/ppy/osu/issues/16120. --- .../Drawables/Cards/BeatmapCardContent.cs | 51 ---------------- .../Cards/ExpandedContentScrollContainer.cs | 61 +++++++++++++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +- 3 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 497283bc64..d43b3291be 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -3,16 +3,13 @@ #nullable enable -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; @@ -157,53 +154,5 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } - private class ExpandedContentScrollContainer : OsuScrollContainer - { - public ExpandedContentScrollContainer() - { - ScrollbarVisible = false; - } - - protected override void Update() - { - base.Update(); - - Height = Math.Min(Content.DrawHeight, 400); - } - - private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); - - protected override bool OnDragStart(DragStartEvent e) - { - if (!allowScroll) - return false; - - return base.OnDragStart(e); - } - - protected override void OnDrag(DragEvent e) - { - if (!allowScroll) - return; - - base.OnDrag(e); - } - - protected override void OnDragEnd(DragEndEvent e) - { - if (!allowScroll) - return; - - base.OnDragEnd(e); - } - - protected override bool OnScroll(ScrollEvent e) - { - if (!allowScroll) - return false; - - return base.OnScroll(e); - } - } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs new file mode 100644 index 0000000000..7c3fbdaf1c --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -0,0 +1,61 @@ +// 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.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class ExpandedContentScrollContainer : OsuScrollContainer + { + public const float HEIGHT = 400; + + public ExpandedContentScrollContainer() + { + ScrollbarVisible = false; + } + + protected override void Update() + { + base.Update(); + + Height = Math.Min(Content.DrawHeight, HEIGHT); + } + + private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); + + protected override bool OnDragStart(DragStartEvent e) + { + if (!allowScroll) + return false; + + return base.OnDragStart(e); + } + + protected override void OnDrag(DragEvent e) + { + if (!allowScroll) + return; + + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (!allowScroll) + return; + + base.OnDragEnd(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (!allowScroll) + return false; + + return base.OnScroll(e); + } + } +} diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c2bad95d6..f06945d85b 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -186,7 +186,11 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Spacing = new Vector2(10), Alpha = 0, - Margin = new MarginPadding { Vertical = 15 }, + Margin = new MarginPadding + { + Vertical = 15, + Bottom = ExpandedContentScrollContainer.HEIGHT + }, ChildrenEnumerable = newCards }; return content; From 0ad555e9f7821181a00b9aaeba77f0c9fad309c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 18:33:32 +0100 Subject: [PATCH 50/76] Remove surplus blank line --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index d43b3291be..1aaa72f5f0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -153,6 +153,5 @@ namespace osu.Game.Beatmaps.Drawables.Cards Hollow = true, }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } - } } From 7c246670b450cb3daa312d5017e3b1b8c71821c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 18:43:20 +0100 Subject: [PATCH 51/76] Add padding to bottom of spotlights ranking view to avoid hovered panels exceeding visible bounds --- osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index fb01656c24..7f5d096fe2 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -140,6 +140,7 @@ namespace osu.Game.Overlays.Rankings { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Bottom = ExpandedContentScrollContainer.HEIGHT }, Spacing = new Vector2(10), Children = response.BeatmapSets.Select(b => new BeatmapCardNormal(b) { From 2660f413391a8b5934f918c332c1c6370bba0c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:27:46 +0100 Subject: [PATCH 52/76] Add failing test case for old cards not expiring correctly --- .../Online/TestSceneBeatmapListingOverlay.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index ee8794ae87..d0e3340f2a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -107,19 +107,31 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); } + [Test] + public void TestCorrectOldContentExpiration() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); + assertAllCardsOfType(100); + + AddStep("show more results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 30).ToArray())); + assertAllCardsOfType(30); + } + [Test] public void TestCardSizeSwitching() { AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); - assertAllCardsOfType(); + assertAllCardsOfType(100); setCardSize(BeatmapCardSize.Extra); - assertAllCardsOfType(); + assertAllCardsOfType(100); setCardSize(BeatmapCardSize.Normal); - assertAllCardsOfType(); + assertAllCardsOfType(100); AddStep("fetch for 0 beatmaps", () => fetchFor()); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); @@ -323,13 +335,12 @@ namespace osu.Game.Tests.Visual.Online private void setCardSize(BeatmapCardSize cardSize) => AddStep($"set card size to {cardSize}", () => overlay.ChildrenOfType().Single().Current.Value = cardSize); - private void assertAllCardsOfType() + private void assertAllCardsOfType(int expectedCount) where T : BeatmapCard => AddUntilStep($"all loaded beatmap cards are {typeof(T)}", () => { int loadedCorrectCount = this.ChildrenOfType().Count(card => card.IsLoaded && card.GetType() == typeof(T)); - int totalCount = this.ChildrenOfType().Count(); - return loadedCorrectCount > 0 && loadedCorrectCount == totalCount; + return loadedCorrectCount > 0 && loadedCorrectCount == expectedCount; }); } } From 97439c3df15879e29163e8b5f3b7861f7a1b6d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:30:17 +0100 Subject: [PATCH 53/76] Rename method to reflect what it actually does --- osu.Game/Overlays/BeatmapListingOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c2bad95d6..d9c2d90e05 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -140,7 +140,7 @@ namespace osu.Game.Overlays if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); - addContentToPlaceholder(supporterRequiredContent); + addContentToResultsArea(supporterRequiredContent); return; } @@ -151,13 +151,13 @@ namespace osu.Game.Overlays //No matches case if (!newCards.Any()) { - addContentToPlaceholder(notFoundContent); + addContentToResultsArea(notFoundContent); return; } var content = createCardContainerFor(newCards); - panelLoadTask = LoadComponentAsync(foundContent = content, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + panelLoadTask = LoadComponentAsync(foundContent = content, addContentToResultsArea, (cancellationToken = new CancellationTokenSource()).Token); } else { @@ -192,7 +192,7 @@ namespace osu.Game.Overlays return content; } - private void addContentToPlaceholder(Drawable content) + private void addContentToResultsArea(Drawable content) { Loading.Hide(); lastFetchDisplayedTime = Time.Current; From ef9f56e5850b2d76fc960b7b070a0c9b08601140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:33:01 +0100 Subject: [PATCH 54/76] Fix bad check if content is placeholder The `lastContent == foundContent` check, last touched in a49a4329, is terminally broken, as it would always be false. `foundContent` is mutated when a new card load task is started in `onSearchFinished()`, which is *before* the aforementioned check. The code prior to a49a4329 was checking against the two static reused placeholder drawables which was the correct check to apply, and this commit reverts to using a variant of that check. --- osu.Game/Overlays/BeatmapListingOverlay.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index d9c2d90e05..ea3c08ea61 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -212,7 +212,7 @@ namespace osu.Game.Overlays // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); - if (lastContent == foundContent) + if (!isPlaceholderContent(lastContent)) { sequence.Then().Schedule(() => { @@ -232,6 +232,12 @@ namespace osu.Game.Overlays currentContent.BypassAutoSizeAxes = Axes.None; } + /// + /// Whether is a static placeholder reused multiple times by this overlay. + /// + private bool isPlaceholderContent(Drawable drawable) + => drawable == notFoundContent || drawable == supporterRequiredContent; + private void onCardSizeChanged() { if (foundContent == null || !foundContent.Any()) From 6650a468e0d7d7a010c16093ce5450d5ae08d6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:39:07 +0100 Subject: [PATCH 55/76] Fix and simplify very broken beatmap listing content swap-out logic The beatmap listing content swap-out logic was already a source of several problems, and several attempts of fixing it were made. But as it turns out it was terminally broken in several aspects. * The `BypassAutoSizeAxes` juggling was finicky and ugly, and didn't really look much different than an instant fade. Therefore, all fade durations and manipulations of `BypassAutoSizeAxes` are removed. * The transform sequence juggling the `BypassAutoSizeAxes` manipulations was enqueued on the content which is being in the process of fading out. That was partially fixed in 25e38560, but as it turns out, that only works if `lastContent` is one of the two placeholder drawables (results not found / supporter required to use filter). It would not work if `lastContent` is a `ReverseChildIDFillFlowContainer` with cards from a previous search in it. --- osu.Game/Overlays/BeatmapListingOverlay.cs | 30 +++------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ea3c08ea61..55a99d21ef 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -204,32 +204,16 @@ namespace osu.Game.Overlays if (lastContent != null) { - lastContent.FadeOut(100, Easing.OutQuint); - - // Consider the case when the new content is smaller than the last content. - // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. - // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. - // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); - + lastContent.FadeOut(); if (!isPlaceholderContent(lastContent)) - { - sequence.Then().Schedule(() => - { - foundContent.Expire(); - foundContent = null; - }); - } + lastContent.Expire(); } if (!content.IsAlive) panelTarget.Add(content); - content.FadeInFromZero(200, Easing.OutQuint); + content.FadeInFromZero(); currentContent = content; - // currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out. - // restore to the initial state. - currentContent.BypassAutoSizeAxes = Axes.None; } /// @@ -265,10 +249,6 @@ namespace osu.Game.Overlays public class NotFoundDrawable : CompositeDrawable { - // required for scheduled tasks to complete correctly - // (see `addContentToPlaceholder()` and the scheduled `BypassAutoSizeAxes` set during fade-out in outer class above) - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - public NotFoundDrawable() { RelativeSizeAxes = Axes.X; @@ -313,10 +293,6 @@ namespace osu.Game.Overlays // (https://github.com/ppy/osu-framework/issues/4530) public class SupporterRequiredDrawable : CompositeDrawable { - // required for scheduled tasks to complete correctly - // (see `addContentToPlaceholder()` and the scheduled `BypassAutoSizeAxes` set during fade-out in outer class above) - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private LinkFlowContainer supporterRequiredText; public SupporterRequiredDrawable() From 586f158920b011233fba348d500cfb7c0265da11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:52:42 +0100 Subject: [PATCH 56/76] Remove initial `foundContent` value It always is replaced on the first search anyway, and just remains forever in the overlay otherwise. --- osu.Game/Overlays/BeatmapListingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 55a99d21ef..c2f2c1dec2 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -79,7 +79,6 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { - foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), supporterRequiredContent = new SupporterRequiredDrawable(), } From de33b420abb157bf7c6e62edbc18d1a720bea82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 20:02:46 +0100 Subject: [PATCH 57/76] Add safety against performing operation on non-alive `foundContent` --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index c2f2c1dec2..b5b12391e8 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -223,7 +223,7 @@ namespace osu.Game.Overlays private void onCardSizeChanged() { - if (foundContent == null || !foundContent.Any()) + if (foundContent?.IsAlive != true || !foundContent.Any()) return; Loading.Show(); From 7cdba2f7c3a915f8f8d8c9996d8689c1ecb4d399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 21:50:00 +0100 Subject: [PATCH 58/76] Add test coverage of score submission if player is exited during import --- .../TestScenePlayerScoreSubmission.cs | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 324a132120..25808d307d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Screens; @@ -36,7 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new NonImportingPlayer(false); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeImportingPlayer(false); + + protected new FakeImportingPlayer Player => (FakeImportingPlayer)base.Player; protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset(); @@ -207,6 +210,25 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } + [Test] + public void TestSubmissionOnExitDuringImport() + { + prepareTokenResponse(true); + + createPlayerTest(); + AddStep("block imports", () => Player.AllowImportCompletion.Wait()); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + + AddUntilStep("wait for import to start", () => Player.ScoreImportStarted); + + AddStep("exit", () => Player.Exit()); + AddStep("allow import to proceed", () => Player.AllowImportCompletion.Release(1)); + AddAssert("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null); + } + [Test] public void TestNoSubmissionOnLocalBeatmap() { @@ -288,15 +310,26 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private class NonImportingPlayer : TestPlayer + protected class FakeImportingPlayer : TestPlayer { - public NonImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + public bool ScoreImportStarted { get; set; } + public SemaphoreSlim AllowImportCompletion { get; } + public Score ImportedScore { get; private set; } + + public FakeImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults, pauseOnFocusLost) { + AllowImportCompletion = new SemaphoreSlim(1); } - protected override Task ImportScore(Score score) + protected override async Task ImportScore(Score score) { + ScoreImportStarted = true; + + await AllowImportCompletion.WaitAsync().ConfigureAwait(false); + + ImportedScore = score; + // It was discovered that Score members could sometimes be half-populated. // In particular, the RulesetID property could be set to 0 even on non-osu! maps. // We want to test that the state of that property is consistent in this test. @@ -311,8 +344,7 @@ namespace osu.Game.Tests.Visual.Gameplay // In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context, // RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3. // - // For the above reasons, importing is disabled in this test. - return Task.CompletedTask; + // For the above reasons, actual importing is disabled in this test. } } } From 374dac57f2b3c4ea34a24929700119fa9a4eb52a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 13:22:00 +0900 Subject: [PATCH 59/76] Change expanded card content height to 200 --- .../Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs index 7c3fbdaf1c..edf4c5328c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -10,7 +10,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public class ExpandedContentScrollContainer : OsuScrollContainer { - public const float HEIGHT = 400; + public const float HEIGHT = 200; public ExpandedContentScrollContainer() { From 1c899e4402bcc891f3e5a46126fae65151372928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 16:46:42 +0900 Subject: [PATCH 60/76] Fix post-merge issues --- .../Visual/UserInterface/TestScenePageSelector.cs | 10 ---------- .../UserInterface/PageSelector/DrawablePage.cs | 2 +- .../UserInterface/PageSelector/PageSelector.cs | 1 + .../UserInterface/PageSelector/PageSelectorButton.cs | 4 ++-- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 6494486d4e..8a15dd9b46 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -1,8 +1,6 @@ // 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 NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface.PageSelector; @@ -11,14 +9,6 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePageSelector : OsuTestScene { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(PageSelector), - typeof(DrawablePage), - typeof(PageSelectorButton), - typeof(PageSelectorItem) - }; - private readonly PageSelector pageSelector; private readonly DrawablePage drawablePage; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs index 20f418085d..4ea610bd89 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); - text.FadeColour(selected.NewValue ? Colours.GreySeafoamDarker : Colours.Lime, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? Colours.GreySeaFoamDarker : Colours.Lime, DURATION, Easing.OutQuint); } protected override void UpdateHoverState() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 8e055faea3..612cf82b9f 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -21,6 +21,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public PageSelector() { AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index e81ce20d27..4eba549dc3 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -63,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.GreySeafoamDark; + Background.Colour = Colours.GreySeaFoamDark; name.Colour = icon.Colour = Colours.Lime; } @@ -73,6 +73,6 @@ namespace osu.Game.Graphics.UserInterface.PageSelector Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); } - protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeafoam : Colours.GreySeafoamDark, DURATION, Easing.OutQuint); + protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); } } From 5736b7d978521ebc2010f295ee238f55b1d78356 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 17:12:34 +0900 Subject: [PATCH 61/76] Fix cursors sent to osu-web being potentially string formatted in incorrect culture Fixed as per solution at https://github.com/JamesNK/Newtonsoft.Json/issues/874. Note that due to the use of `JsonExtensionDataAttribute` it's not feasible to change the actual specification to `JValue` in the `Dictionary`. In discussion with the osu-web team, it may be worthwhile to change the cursor to a string format where parsing is not required at our end. We could already do this in fact, but there are tests that rely on it being a `JToken` so the switch to `JValue` seems like the easier path right now. --- osu.Game/Extensions/WebRequestExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Extensions/WebRequestExtensions.cs b/osu.Game/Extensions/WebRequestExtensions.cs index b940c7498b..50837a648d 100644 --- a/osu.Game/Extensions/WebRequestExtensions.cs +++ b/osu.Game/Extensions/WebRequestExtensions.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.Globalization; +using Newtonsoft.Json.Linq; using osu.Framework.IO.Network; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Online.API.Requests; @@ -16,7 +18,7 @@ namespace osu.Game.Extensions { cursor?.Properties.ForEach(x => { - webRequest.AddParameter("cursor[" + x.Key + "]", x.Value.ToString()); + webRequest.AddParameter("cursor[" + x.Key + "]", (x.Value as JValue)?.ToString(CultureInfo.InvariantCulture) ?? x.Value.ToString()); }); } } From db58f5de8e6ea8c6dd14695b59a39aed33c82c9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 16:58:32 +0900 Subject: [PATCH 62/76] Clean up unnecessary complexity --- .../PageSelector/DrawablePage.cs | 8 +- .../PageSelector/PageSelectorButton.cs | 80 +++++++++++-------- .../PageSelector/PageSelectorItem.cs | 12 ++- 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs index 4ea610bd89..2610ae7571 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -15,20 +15,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public bool Selected { - get => selected.Value; set => selected.Value = value; } - public int Page { get; private set; } + public int Page { get; } private OsuSpriteText text; public DrawablePage(int page) { Page = page; - text.Text = page.ToString(); - - Background.Alpha = 0; Action = () => { @@ -40,12 +36,14 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override Drawable CreateContent() => text = new OsuSpriteText { Font = OsuFont.GetFont(size: 12), + Text = Page.ToString(), }; [BackgroundDependencyLoader] private void load() { Background.Colour = Colours.Lime; + Background.Alpha = 0; } protected override void LoadComplete() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index 4eba549dc3..1492319fc9 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -1,61 +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 osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osuTK.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; -using osu.Framework.Extensions.IEnumerableExtensions; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelectorButton : PageSelectorItem { - private readonly Box fadeBox; + private readonly string text; + + private Box fadeBox; private SpriteIcon icon; private OsuSpriteText name; - private FillFlowContainer buttonContent; + + private readonly Anchor alignment; public PageSelectorButton(bool rightAligned, string text) { - Add(fadeBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) - }); - - var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - - buttonContent.ForEach(drawable => - { - drawable.Anchor = Anchor.y1 | alignment; - drawable.Origin = Anchor.y1 | alignment; - }); - - icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; - - name.Text = text.ToUpper(); + this.text = text; + alignment = rightAligned ? Anchor.x0 : Anchor.x2; } - protected override Drawable CreateContent() => buttonContent = new FillFlowContainer + protected override Drawable CreateContent() => new Container { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, Children = new Drawable[] { - name = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 12), - }, - icon = new SpriteIcon - { - Size = new Vector2(8), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(3, 0), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Size = new Vector2(8), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } }, } }; @@ -70,6 +73,13 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override void LoadComplete() { base.LoadComplete(); + + CircularContent.Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); + Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index cd61961dbe..92bf958ca9 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -18,16 +18,20 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [Resolved] protected OsuColour Colours { get; private set; } - protected override Container Content => content; + protected Box Background; - protected readonly Box Background; - private readonly CircularContainer content; + public CircularContainer CircularContent { get; private set; } protected PageSelectorItem() { AutoSizeAxes = Axes.X; Height = 20; - base.Content.Add(content = new CircularContainer + } + + [BackgroundDependencyLoader] + private void load() + { + Add(CircularContent = new CircularContainer { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, From ee4f5c0e79e11c2f8e9a9c574beddb16a2174652 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 17:52:40 +0900 Subject: [PATCH 63/76] Rename button classes to make more sense --- .../UserInterface/TestScenePageSelector.cs | 8 +- .../PageSelector/PageSelector.cs | 14 +-- .../PageSelector/PageSelectorButton.cs | 112 ++++++++---------- .../PageSelector/PageSelectorItem.cs | 72 ----------- ...wablePage.cs => PageSelectorPageButton.cs} | 4 +- .../PageSelectorPrevNextButton.cs | 88 ++++++++++++++ 6 files changed, 149 insertions(+), 149 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs rename osu.Game/Graphics/UserInterface/PageSelector/{DrawablePage.cs => PageSelectorPageButton.cs} (94%) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 8a15dd9b46..000e75be31 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestScenePageSelector : OsuTestScene { private readonly PageSelector pageSelector; - private readonly DrawablePage drawablePage; + private readonly PageSelectorPageButton pageSelectorPageButton; public TestScenePageSelector() { @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - drawablePage = new DrawablePage(1234) + pageSelectorPageButton = new PageSelectorPageButton(1234) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDrawablePage() { - AddStep("Select", () => drawablePage.Selected = true); - AddStep("Deselect", () => drawablePage.Selected = false); + AddStep("Select", () => pageSelectorPageButton.Selected = true); + AddStep("Deselect", () => pageSelectorPageButton.Selected = false); } private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 612cf82b9f..4089fb5511 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -13,10 +13,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); - private readonly FillFlowContainer itemsFlow; + private readonly FillFlowContainer itemsFlow; - private readonly PageSelectorButton previousPageButton; - private readonly PageSelectorButton nextPageButton; + private readonly PageSelectorPrevNextButton previousPageButton; + private readonly PageSelectorPrevNextButton nextPageButton; public PageSelector() { @@ -28,16 +28,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector Direction = FillDirection.Horizontal, Children = new Drawable[] { - previousPageButton = new PageSelectorButton(false, "prev") + previousPageButton = new PageSelectorPrevNextButton(false, "prev") { Action = () => CurrentPage.Value -= 1, }, - itemsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, }, - nextPageButton = new PageSelectorButton(true, "next") + nextPageButton = new PageSelectorPrevNextButton(true, "next") { Action = () => CurrentPage.Value += 1 } @@ -100,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector nextPageButton.Enabled.Value = newPage != maxPages; } - private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page) + private void addDrawablePage(int page) => itemsFlow.Add(new PageSelectorPageButton(page) { Action = () => CurrentPage.Value = page, }); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index 1492319fc9..bdfe66f1d0 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -1,88 +1,72 @@ // 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; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; +using osu.Game.Graphics.Containers; +using osu.Framework.Input.Events; +using JetBrains.Annotations; namespace osu.Game.Graphics.UserInterface.PageSelector { - public class PageSelectorButton : PageSelectorItem + public abstract class PageSelectorButton : OsuClickableContainer { - private readonly string text; + protected const int DURATION = 200; - private Box fadeBox; - private SpriteIcon icon; - private OsuSpriteText name; + [Resolved] + protected OsuColour Colours { get; private set; } - private readonly Anchor alignment; + protected Box Background; - public PageSelectorButton(bool rightAligned, string text) + public CircularContainer CircularContent { get; private set; } + + protected PageSelectorButton() { - this.text = text; - alignment = rightAligned ? Anchor.x0 : Anchor.x2; + AutoSizeAxes = Axes.X; + Height = 20; } - protected override Drawable CreateContent() => new Container - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(3, 0), - Children = new Drawable[] - { - name = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = text.ToUpper(), - }, - icon = new SpriteIcon - { - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, - Size = new Vector2(8), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - }, - } - }; - [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.GreySeaFoamDark; - name.Colour = icon.Colour = Colours.Lime; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - CircularContent.Add(fadeBox = new Box + Add(CircularContent = new CircularContainer { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + CreateContent().With(content => + { + content.Anchor = Anchor.Centre; + content.Origin = Anchor.Centre; + content.Margin = new MarginPadding { Horizontal = 10 }; + }) + } }); - - Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); } - protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); + [NotNull] + protected abstract Drawable CreateContent(); + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected abstract void UpdateHoverState(); } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs deleted file mode 100644 index 92bf958ca9..0000000000 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ /dev/null @@ -1,72 +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 osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osu.Framework.Input.Events; -using JetBrains.Annotations; - -namespace osu.Game.Graphics.UserInterface.PageSelector -{ - public abstract class PageSelectorItem : OsuClickableContainer - { - protected const int DURATION = 200; - - [Resolved] - protected OsuColour Colours { get; private set; } - - protected Box Background; - - public CircularContainer CircularContent { get; private set; } - - protected PageSelectorItem() - { - AutoSizeAxes = Axes.X; - Height = 20; - } - - [BackgroundDependencyLoader] - private void load() - { - Add(CircularContent = new CircularContainer - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new[] - { - Background = new Box - { - RelativeSizeAxes = Axes.Both - }, - CreateContent().With(content => - { - content.Anchor = Anchor.Centre; - content.Origin = Anchor.Centre; - content.Margin = new MarginPadding { Horizontal = 10 }; - }) - } - }); - } - - [NotNull] - protected abstract Drawable CreateContent(); - - protected override bool OnHover(HoverEvent e) - { - UpdateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - UpdateHoverState(); - } - - protected abstract void UpdateHoverState(); - } -} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs similarity index 94% rename from osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs rename to osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 2610ae7571..81ab7c7155 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface.PageSelector { - public class DrawablePage : PageSelectorItem + public class PageSelectorPageButton : PageSelectorButton { private readonly BindableBool selected = new BindableBool(); @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private OsuSpriteText text; - public DrawablePage(int page) + public PageSelectorPageButton(int page) { Page = page; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs new file mode 100644 index 0000000000..eaa3714fea --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs @@ -0,0 +1,88 @@ +// 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; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelectorPrevNextButton : PageSelectorButton + { + private readonly string text; + + private Box fadeBox; + private SpriteIcon icon; + private OsuSpriteText name; + + private readonly Anchor alignment; + + public PageSelectorPrevNextButton(bool rightAligned, string text) + { + this.text = text; + alignment = rightAligned ? Anchor.x0 : Anchor.x2; + } + + protected override Drawable CreateContent() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(3, 0), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Size = new Vector2(8), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + } + }; + + [BackgroundDependencyLoader] + private void load() + { + Background.Colour = Colours.GreySeaFoamDark; + name.Colour = icon.Colour = Colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + CircularContent.Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); + + Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); + } + + protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); + } +} From d10b8c79b39682f144f7c117a7c8278822485bfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 17:53:23 +0900 Subject: [PATCH 64/76] Remove pointless test coverage of `DrawablePage` --- .../Visual/UserInterface/TestScenePageSelector.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 000e75be31..b65af6b1f5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -10,7 +10,6 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestScenePageSelector : OsuTestScene { private readonly PageSelector pageSelector; - private readonly PageSelectorPageButton pageSelectorPageButton; public TestScenePageSelector() { @@ -21,12 +20,6 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - pageSelectorPageButton = new PageSelectorPageButton(1234) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 50 }, - } }); } @@ -58,13 +51,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Max is 1", () => pageSelector.MaxPages.Value == 1); } - [Test] - public void TestDrawablePage() - { - AddStep("Select", () => pageSelectorPageButton.Selected = true); - AddStep("Deselect", () => pageSelectorPageButton.Selected = false); - } - private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; From 5a11ee7810b705461360c2b3daa2e27b1e3b05ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 18:14:42 +0900 Subject: [PATCH 65/76] Use `OverlayColourProvider` and fix font weight --- .../UserInterface/TestScenePageSelector.cs | 5 +++ .../PageSelector/PageSelectorButton.cs | 7 ++-- .../PageSelector/PageSelectorPageButton.cs | 9 +++-- .../PageSelectorPrevNextButton.cs | 34 +++++++------------ 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index b65af6b1f5..1595a7f22d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -2,13 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface.PageSelector; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePageSelector : OsuTestScene { + [Cached] + private OverlayColourProvider provider { get; } = new OverlayColourProvider(OverlayColourScheme.Green); + private readonly PageSelector pageSelector; public TestScenePageSelector() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index bdfe66f1d0..a2c6e8532b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; using JetBrains.Annotations; +using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterface.PageSelector { @@ -16,12 +17,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected const int DURATION = 200; [Resolved] - protected OsuColour Colours { get; private set; } + protected OverlayColourProvider ColourProvider { get; private set; } protected Box Background; - public CircularContainer CircularContent { get; private set; } - protected PageSelectorButton() { AutoSizeAxes = Axes.X; @@ -31,7 +30,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [BackgroundDependencyLoader] private void load() { - Add(CircularContent = new CircularContainer + Add(new CircularContainer { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 81ab7c7155..31aca0d2f2 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface.PageSelector @@ -35,14 +34,14 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override Drawable CreateContent() => text = new OsuSpriteText { - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = Page.ToString(), }; [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.Lime; + Background.Colour = ColourProvider.Highlight1; Background.Alpha = 0; } @@ -55,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); - text.FadeColour(selected.NewValue ? Colours.GreySeaFoamDarker : Colours.Lime, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? ColourProvider.Dark4 : ColourProvider.Light3, DURATION, Easing.OutQuint); } protected override void UpdateHoverState() @@ -63,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector if (selected.Value) return; - text.FadeColour(IsHovered ? Colours.Lime.Lighten(20f) : Colours.Lime, DURATION, Easing.OutQuint); + text.FadeColour(IsHovered ? ColourProvider.Light2 : ColourProvider.Light1, DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs index eaa3714fea..7503ab8135 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs @@ -2,31 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using osuTK; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelectorPrevNextButton : PageSelectorButton { + private readonly bool rightAligned; private readonly string text; - private Box fadeBox; private SpriteIcon icon; private OsuSpriteText name; - private readonly Anchor alignment; - public PageSelectorPrevNextButton(bool rightAligned, string text) { + this.rightAligned = rightAligned; this.text = text; - alignment = rightAligned ? Anchor.x0 : Anchor.x2; } protected override Drawable CreateContent() => new Container @@ -47,16 +42,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector name = new OsuSpriteText { Font = OsuFont.GetFont(size: 12), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Text = text.ToUpper(), }, icon = new SpriteIcon { - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, }, } }, @@ -66,23 +61,18 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.GreySeaFoamDark; - name.Colour = icon.Colour = Colours.Lime; + Background.Colour = ColourProvider.Dark4; + name.Colour = icon.Colour = ColourProvider.Light1; } protected override void LoadComplete() { base.LoadComplete(); - CircularContent.Add(fadeBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) - }); - - Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); + Enabled.BindValueChanged(enabled => Background.FadeTo(enabled.NewValue ? 1 : 0.5f, DURATION), true); } - protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); + protected override void UpdateHoverState() => + Background.FadeColour(IsHovered ? ColourProvider.Dark3 : ColourProvider.Dark4, DURATION, Easing.OutQuint); } } From e75c9519f32f8e44e1beac1e57b83798a1f8d547 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 18:19:23 +0900 Subject: [PATCH 66/76] Adjust font weighting on selection --- .../UserInterface/PageSelector/PageSelectorPageButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 31aca0d2f2..6aac75565e 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -54,7 +54,9 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? ColourProvider.Dark4 : ColourProvider.Light3, DURATION, Easing.OutQuint); + text.Font = text.Font.With(weight: IsHovered ? FontWeight.SemiBold : FontWeight.Regular); } protected override void UpdateHoverState() From 86f72b71b1229c165d30d419047165b00cdaafc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 18:46:44 +0900 Subject: [PATCH 67/76] Prepare tests and general structure to support omission of pages --- .../UserInterface/TestScenePageSelector.cs | 42 ++++++------ .../PageSelector/PageSelector.cs | 66 +++++++------------ .../PageSelector/PageSelectorPageButton.cs | 8 +-- 3 files changed, 52 insertions(+), 64 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 1595a7f22d..3b32a2a0dc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterface.PageSelector; using osu.Game.Overlays; @@ -28,36 +30,38 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestOmittedPages() + { + setAvailablePages(100); + + AddAssert("Correct page buttons", () => pageSelector.ChildrenOfType().Select(p => p.PageNumber).SequenceEqual(new[] { 1, 2, 3, 100 })); + } + [Test] public void TestResetCurrentPage() { - AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select page 5", () => setCurrentPage(5)); - AddStep("Set 11 pages", () => setMaxPages(11)); - AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); + setAvailablePages(10); + selectPage(6); + setAvailablePages(11); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } [Test] public void TestOutOfBoundsSelection() { - AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select page 11", () => setCurrentPage(11)); - AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + setAvailablePages(10); + selectPage(11); + AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.AvailablePages.Value - 1); - AddStep("Select page -1", () => setCurrentPage(-1)); - AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); + selectPage(-1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } - [Test] - public void TestNegativeMaxPages() - { - AddStep("Set -10 pages", () => setMaxPages(-10)); - AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); - AddAssert("Max is 1", () => pageSelector.MaxPages.Value == 1); - } + private void selectPage(int pageIndex) => + AddStep($"Select page {pageIndex}", () => pageSelector.CurrentPage.Value = pageIndex); - private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; - - private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; + private void setAvailablePages(int availablePages) => + AddStep($"Set available pages to {availablePages}", () => pageSelector.AvailablePages.Value = availablePages); } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 4089fb5511..ae644bb852 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -1,19 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { - public readonly BindableInt CurrentPage = new BindableInt(1); - public readonly BindableInt MaxPages = new BindableInt(1); + public readonly BindableInt CurrentPage = new BindableInt { MinValue = 0, }; - private readonly FillFlowContainer itemsFlow; + public readonly BindableInt AvailablePages = new BindableInt(1) { MinValue = 1, }; + + private readonly FillFlowContainer itemsFlow; private readonly PageSelectorPrevNextButton previousPageButton; private readonly PageSelectorPrevNextButton nextPageButton; @@ -32,7 +33,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { Action = () => CurrentPage.Value -= 1, }, - itemsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -49,60 +50,43 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - MaxPages.BindValueChanged(_ => redraw()); - CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue)); - redraw(); + CurrentPage.BindValueChanged(onCurrentPageChanged); + AvailablePages.BindValueChanged(_ => redraw(), true); } - private void onCurrentPageChanged(int newPage) + private void onCurrentPageChanged(ValueChangedEvent currentPage) { - if (newPage < 1) + if (currentPage.NewValue >= AvailablePages.Value) { - CurrentPage.Value = 1; + CurrentPage.Value = AvailablePages.Value - 1; return; } - if (newPage > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } + foreach (var page in itemsFlow.OfType()) + page.Selected = page.PageNumber == currentPage.NewValue + 1; - itemsFlow.ForEach(page => page.Selected = page.Page == newPage); - updateButtonsState(); + previousPageButton.Enabled.Value = currentPage.NewValue != 0; + nextPageButton.Enabled.Value = currentPage.NewValue < AvailablePages.Value - 1; } private void redraw() { itemsFlow.Clear(); - if (MaxPages.Value < 1) + for (int i = 0; i < AvailablePages.Value; i++) { - MaxPages.Value = 1; - return; + int pageIndex = i; + + itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1) + { + Action = () => CurrentPage.Value = pageIndex, + }); } - for (int i = 1; i <= MaxPages.Value; i++) - addDrawablePage(i); - - if (CurrentPage.Value == 1) - CurrentPage.TriggerChange(); + if (CurrentPage.Value != 0) + CurrentPage.Value = 0; else - CurrentPage.Value = 1; + CurrentPage.TriggerChange(); } - - private void updateButtonsState() - { - int newPage = CurrentPage.Value; - int maxPages = MaxPages.Value; - - previousPageButton.Enabled.Value = newPage != 1; - nextPageButton.Enabled.Value = newPage != maxPages; - } - - private void addDrawablePage(int page) => itemsFlow.Add(new PageSelectorPageButton(page) - { - Action = () => CurrentPage.Value = page, - }); } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 6aac75565e..247a003492 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -17,13 +17,13 @@ namespace osu.Game.Graphics.UserInterface.PageSelector set => selected.Value = value; } - public int Page { get; } + public int PageNumber { get; } private OsuSpriteText text; - public PageSelectorPageButton(int page) + public PageSelectorPageButton(int pageNumber) { - Page = page; + PageNumber = pageNumber; Action = () => { @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override Drawable CreateContent() => text = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Page.ToString(), + Text = PageNumber.ToString(), }; [BackgroundDependencyLoader] From 5ed69338a662165d7374e3de1940ad2997f35faa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 19:05:14 +0900 Subject: [PATCH 68/76] Add omission of pages when there are too many --- .../UserInterface/TestScenePageSelector.cs | 22 +++++++-- .../PageSelector/PageEllipsis.cs | 33 +++++++++++++ .../PageSelector/PageSelector.cs | 48 ++++++++++--------- 3 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 3b32a2a0dc..c99ac52cb1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -35,14 +35,24 @@ namespace osu.Game.Tests.Visual.UserInterface { setAvailablePages(100); - AddAssert("Correct page buttons", () => pageSelector.ChildrenOfType().Select(p => p.PageNumber).SequenceEqual(new[] { 1, 2, 3, 100 })); + selectPageIndex(0); + checkVisiblePageNumbers(new[] { 1, 2, 3, 100 }); + + selectPageIndex(6); + checkVisiblePageNumbers(new[] { 1, 5, 6, 7, 8, 9, 100 }); + + selectPageIndex(49); + checkVisiblePageNumbers(new[] { 1, 48, 49, 50, 51, 52, 100 }); + + selectPageIndex(99); + checkVisiblePageNumbers(new[] { 1, 98, 99, 100 }); } [Test] public void TestResetCurrentPage() { setAvailablePages(10); - selectPage(6); + selectPageIndex(6); setAvailablePages(11); AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } @@ -51,14 +61,16 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestOutOfBoundsSelection() { setAvailablePages(10); - selectPage(11); + selectPageIndex(11); AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.AvailablePages.Value - 1); - selectPage(-1); + selectPageIndex(-1); AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } - private void selectPage(int pageIndex) => + private void checkVisiblePageNumbers(int[] expected) => AddAssert($"Sequence is {string.Join(',', expected.Select(i => i.ToString()))}", () => pageSelector.ChildrenOfType().Select(p => p.PageNumber).SequenceEqual(expected)); + + private void selectPageIndex(int pageIndex) => AddStep($"Select page {pageIndex}", () => pageSelector.CurrentPage.Value = pageIndex); private void setAvailablePages(int availablePages) => diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs new file mode 100644 index 0000000000..d73d9f5824 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs @@ -0,0 +1,33 @@ +// 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; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + internal class PageEllipsis : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = "...", + Colour = colourProvider.Light3, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index ae644bb852..ce74259289 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -1,7 +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.Linq; +using System; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; @@ -50,43 +50,47 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - CurrentPage.BindValueChanged(onCurrentPageChanged); + CurrentPage.BindValueChanged(_ => redraw()); AvailablePages.BindValueChanged(_ => redraw(), true); } - private void onCurrentPageChanged(ValueChangedEvent currentPage) + private void redraw() { - if (currentPage.NewValue >= AvailablePages.Value) + if (CurrentPage.Value >= AvailablePages.Value) { CurrentPage.Value = AvailablePages.Value - 1; return; } - foreach (var page in itemsFlow.OfType()) - page.Selected = page.PageNumber == currentPage.NewValue + 1; + previousPageButton.Enabled.Value = CurrentPage.Value != 0; + nextPageButton.Enabled.Value = CurrentPage.Value < AvailablePages.Value - 1; - previousPageButton.Enabled.Value = currentPage.NewValue != 0; - nextPageButton.Enabled.Value = currentPage.NewValue < AvailablePages.Value - 1; - } - - private void redraw() - { itemsFlow.Clear(); - for (int i = 0; i < AvailablePages.Value; i++) + int totalPages = AvailablePages.Value; + bool lastWasEllipsis = false; + + for (int i = 0; i < totalPages; i++) { int pageIndex = i; - itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1) - { - Action = () => CurrentPage.Value = pageIndex, - }); - } + bool shouldShowPage = pageIndex == 0 || pageIndex == totalPages - 1 || Math.Abs(pageIndex - CurrentPage.Value) <= 2; - if (CurrentPage.Value != 0) - CurrentPage.Value = 0; - else - CurrentPage.TriggerChange(); + if (shouldShowPage) + { + lastWasEllipsis = false; + itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1) + { + Action = () => CurrentPage.Value = pageIndex, + Selected = CurrentPage.Value == pageIndex, + }); + } + else if (!lastWasEllipsis) + { + lastWasEllipsis = true; + itemsFlow.Add(new PageEllipsis()); + } + } } } } From 2bf6b55b19a26add1ad4f3f6e19bc4b553025b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 14:53:32 +0900 Subject: [PATCH 69/76] Fix failing test due to changed reset page logic --- .../UserInterface/PageSelector/PageSelector.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index ce74259289..005729580c 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -50,8 +50,14 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw()); - AvailablePages.BindValueChanged(_ => redraw(), true); + CurrentPage.BindValueChanged(_ => Scheduler.AddOnce(redraw)); + AvailablePages.BindValueChanged(_ => + { + CurrentPage.Value = 0; + + // AddOnce as the reset of CurrentPage may also trigger a redraw. + Scheduler.AddOnce(redraw); + }, true); } private void redraw() From ef2a4aed9a2600b3be2eb86215711c718030bde3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:05:15 +0900 Subject: [PATCH 70/76] Fix editor playfield not being centered correctly This has come up multiple times, with mappers citing that they have muscle memory for mapping based on the centre of the playfield being in the centre of the window. The original plan was to have a second toolbar on the right hand side of the screen to balance the padding, but we're not at that point yet. Easiest solution is to do what stable does and allow the left-hand toolbar items to overlap the playfield underneath it. In edge cases where the user is running at an aspect ratio that causes overlaps, they can choose to collapse the toolbars down. We can probably work on this UI/UX a bit more as we update designs to be more friendly to such cases. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4bbfe26d7b..cbc2415603 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -105,7 +105,6 @@ namespace osu.Game.Rulesets.Edit new Container { Name = "Content", - Padding = new MarginPadding { Left = toolbar_width }, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { From 13cce50fa74726c3dc016fe43e2c67e1fe630ba6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:30:42 +0900 Subject: [PATCH 71/76] Remove existing handling of flip hotkeys --- .../Screens/Edit/Compose/Components/SelectionBox.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index be52a968bb..732c6a5dd4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -174,12 +174,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { case Key.G: return CanReverse && runOperationFromHotkey(OnReverse); - - case Key.H: - return CanFlipX && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Horizontal) ?? false); - - case Key.J: - return CanFlipY && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Vertical) ?? false); } return base.OnKeyDown(e); @@ -287,12 +281,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addXFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal)); + addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal)); } private void addYFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical)); + addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical)); } private void addButton(IconUsage icon, string tooltip, Action action) From 866ae3472bb654af5bde50f79ecb0ae2cbd884e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:46:34 +0900 Subject: [PATCH 72/76] Add global flip hotkeys --- .../Edit/CatchSelectionHandler.cs | 12 ++++--- .../Edit/OsuSelectionHandler.cs | 7 +++-- .../Input/Bindings/GlobalActionContainer.cs | 10 +++++- .../GlobalActionKeyBindingStrings.cs | 10 ++++++ .../Edit/Compose/Components/SelectionBox.cs | 6 ++-- .../Compose/Components/SelectionHandler.cs | 31 +++++++++++++++++-- .../Timeline/TimelineSelectionHandler.cs | 12 +++---- .../Skinning/Editor/SkinSelectionHandler.cs | 4 +-- 8 files changed, 68 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 8cb0804ab7..41a2584acc 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Edit EditorBeatmap.PerformOnSelection(h => { if (h is CatchHitObject catchObject) - changed |= handleFlip(selectionRange, catchObject); + changed |= handleFlip(selectionRange, catchObject, flipOverOrigin); }); return changed; } @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.Edit return Math.Clamp(deltaX, lowerBound, upperBound); } - private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject) + private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject, bool flipOverOrigin) { switch (hitObject) { @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Catch.Edit return false; case JuiceStream juiceStream: - juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX); + juiceStream.OriginalX = getFlippedPosition(juiceStream.OriginalX); foreach (var point in juiceStream.Path.ControlPoints) point.Position *= new Vector2(-1, 1); @@ -133,9 +133,11 @@ namespace osu.Game.Rulesets.Catch.Edit return true; default: - hitObject.OriginalX = selectionRange.GetFlippedPosition(hitObject.OriginalX); + hitObject.OriginalX = getFlippedPosition(hitObject.OriginalX); return true; } + + float getFlippedPosition(float original) => flipOverOrigin ? CatchPlayfield.WIDTH - original : selectionRange.GetFlippedPosition(original); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 4a57d36eb4..d172fa5398 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -10,6 +10,7 @@ using osu.Game.Extensions; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -84,15 +85,15 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var hitObjects = selectedMovableObjects; - var selectedObjectsQuad = getSurroundingQuad(hitObjects); + var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); foreach (var h in hitObjects) { - h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position); + h.Position = GetFlippedPosition(direction, flipQuad, h.Position); if (h is Slider slider) { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c71cb6a00a..47cb7be2cf 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -77,6 +77,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), + new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), + new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically), }; public IEnumerable InGameKeyBindings => new[] @@ -292,6 +294,12 @@ namespace osu.Game.Input.Bindings EditorCycleGridDisplayMode, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] - EditorTestGameplay + EditorTestGameplay, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipHorizontally))] + EditorFlipHorizontally, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipVertically))] + EditorFlipVertically, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 35a0c2ae74..777e97d1e3 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -229,6 +229,16 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorNudgeRight => new TranslatableString(getKey(@"editor_nudge_right"), @"Nudge selection right"); + /// + /// "Flip selection horizontally" + /// + public static LocalisableString EditorFlipHorizontally => new TranslatableString(getKey(@"editor_flip_horizontally"), @"Flip selection horizontally"); + + /// + /// "Flip selection vertically" + /// + public static LocalisableString EditorFlipVertically => new TranslatableString(getKey(@"editor_flip_vertically"), @"Flip selection vertically"); + /// /// "Toggle skin editor" /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 732c6a5dd4..3a31f6ea8c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public Func OnRotation; public Func OnScale; - public Func OnFlip; + public Func OnFlip; public Func OnReverse; public Action OperationStarted; @@ -281,12 +281,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addXFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal)); + addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal, false)); } private void addYFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical)); + addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false)); } private void addButton(IconUsage icon, string tooltip, Action action) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ee35b6a47c..d9d310c72c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines items and handles movement of selections. /// - public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IKeyBindingHandler, IHasContextMenu { /// /// The currently selected blueprints. @@ -127,9 +128,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Handles the selected items being flipped. /// - /// The direction to flip + /// The direction to flip. + /// Whether the flip operation should be global to the playfield's origin or local to the selected pattern. /// Whether any items could be flipped. - public virtual bool HandleFlip(Direction direction) => false; + public virtual bool HandleFlip(Direction direction, bool flipOverOrigin) => false; /// /// Handles the selected items being reversed pattern-wise. @@ -137,6 +139,29 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any items could be reversed. public virtual bool HandleReverse() => false; + public virtual bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.EditorFlipHorizontally: + HandleFlip(Direction.Horizontal, true); + return true; + + case GlobalAction.EditorFlipVertically: + HandleFlip(Direction.Vertical, true); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index 845a671e2c..8b7ff45765 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -17,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler + internal class TimelineSelectionHandler : EditorSelectionHandler { [Resolved] private Timeline timeline { get; set; } @@ -27,8 +26,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; - public bool OnPressed(KeyBindingPressEvent e) + public override bool OnPressed(KeyBindingPressEvent e) { + // Importantly, we block the base call here. + // Other key operations will be handled by the composer view's SelectionHandler instead. + switch (e.Action) { case GlobalAction.EditorNudgeLeft: @@ -43,10 +45,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return false; } - public void OnReleased(KeyBindingReleaseEvent e) - { - } - /// /// Nudge the current selection by the specified multiple of beat divisor lengths, /// based on the timing at the first object in the selection. diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 01bd5e8196..a54590a0ea 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -126,9 +126,9 @@ namespace osu.Game.Skinning.Editor return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - var selectionQuad = getSelectionQuad(); + var selectionQuad = flipOverOrigin ? ScreenSpaceDrawQuad : getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); foreach (var b in SelectedBlueprints) From 6779503e574e717c571d27ddef15939b6255945f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:56:54 +0900 Subject: [PATCH 73/76] Refactor logic to avoid `TimelineSelectionHandler` having to block base calls --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 5 +++++ osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 +++ .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 ++---- .../Compose/Components/Timeline/TimelineSelectionHandler.cs | 5 +---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 41a2584acc..d39f1d3c86 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -54,14 +54,19 @@ namespace osu.Game.Rulesets.Catch.Edit public override bool HandleFlip(Direction direction, bool flipOverOrigin) { + if (SelectedItems.Count == 0 && !flipOverOrigin) + return false; + var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); bool changed = false; + EditorBeatmap.PerformOnSelection(h => { if (h is CatchHitObject catchObject) changed |= handleFlip(selectionRange, catchObject, flipOverOrigin); }); + return changed; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index d172fa5398..d51f08ad68 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -89,6 +89,9 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; + if (hitObjects.Length == 1 && !flipOverOrigin) + return false; + var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); foreach (var h in hitObjects) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index d9d310c72c..39de13899d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -147,12 +147,10 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Action) { case GlobalAction.EditorFlipHorizontally: - HandleFlip(Direction.Horizontal, true); - return true; + return HandleFlip(Direction.Horizontal, true); case GlobalAction.EditorFlipVertically: - HandleFlip(Direction.Vertical, true); - return true; + return HandleFlip(Direction.Vertical, true); } return false; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index 8b7ff45765..e98cf8332f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -28,9 +28,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override bool OnPressed(KeyBindingPressEvent e) { - // Importantly, we block the base call here. - // Other key operations will be handled by the composer view's SelectionHandler instead. - switch (e.Action) { case GlobalAction.EditorNudgeLeft: @@ -42,7 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } - return false; + return base.OnPressed(e); } /// From ee24713002d91838e4531428517de38e5251ec66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 14:37:13 +0900 Subject: [PATCH 74/76] Fix single sliders not being flippable due to incorrect precondition --- .../Edit/OsuSelectionHandler.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index d51f08ad68..071ecf6329 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -89,17 +89,24 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; - if (hitObjects.Length == 1 && !flipOverOrigin) - return false; - var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); + bool didFlip = false; + foreach (var h in hitObjects) { - h.Position = GetFlippedPosition(direction, flipQuad, h.Position); + var flippedPosition = GetFlippedPosition(direction, flipQuad, h.Position); + + if (!Precision.AlmostEquals(flippedPosition, h.Position)) + { + h.Position = flippedPosition; + didFlip = true; + } if (h is Slider slider) { + didFlip = true; + foreach (var point in slider.Path.ControlPoints) { point.Position = new Vector2( @@ -110,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Edit } } - return true; + return didFlip; } public override bool HandleScale(Vector2 scale, Anchor reference) From 5c0494f3bae6468e0b7a2791f99c3e02b4a3216b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 14:39:00 +0900 Subject: [PATCH 75/76] Remove unnecessary precondition check and disallow vertical catch flips for now --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index d39f1d3c86..dd5835b4ed 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -54,7 +54,11 @@ namespace osu.Game.Rulesets.Catch.Edit public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - if (SelectedItems.Count == 0 && !flipOverOrigin) + if (SelectedItems.Count == 0) + return false; + + // This could be implemented in the future if there's a requirement for it. + if (direction == Direction.Vertical) return false; var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); From 243a1a3cf7ce26f11f6aea7305a04996be916dcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 14:47:44 +0900 Subject: [PATCH 76/76] Fix incorrect origin specification for `SkinSelectionHandler` flips --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index a54590a0ea..bd6d097eb2 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -128,14 +128,14 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - var selectionQuad = flipOverOrigin ? ScreenSpaceDrawQuad : getSelectionQuad(); + var selectionQuad = getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); foreach (var b in SelectedBlueprints) { var drawableItem = (Drawable)b.Item; - var flippedPosition = GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint); + var flippedPosition = GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint); updateDrawablePosition(drawableItem, flippedPosition);