diff --git a/osu.Android.props b/osu.Android.props index 43c1302e54..8b31be3f12 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 62c4ba5ee3..7c94568835 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Skinning; @@ -30,17 +29,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - new CircularContainer + new Container { Masking = true, - Origin = Anchor.Centre, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Radius = 60, Colour = Color4.White.Opacity(0.5f), }, - Child = new Box() }, number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs index 944480243d..cdfb3beb19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; @@ -20,6 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + State = { Value = Visibility.Visible } }); Add(container = new ExampleContainer()); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs index 000832b784..61fed3013e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs @@ -33,23 +33,15 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestInstantLoad() { - bool logoVisible = false; + // visual only, very impossible to test this using asserts. - AddStep("begin loading", () => + AddStep("load immediately", () => { loader = new TestLoader(); loader.AllowLoad.Set(); LoadScreen(loader); }); - - AddUntilStep("loaded", () => - { - logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; - }); - - AddAssert("logo was not visible", () => !logoVisible); } [Test] @@ -58,7 +50,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0); AddStep("finish loading", () => loader.AllowLoad.Set()); - AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); + AddUntilStep("loaded", () => loader.Logo != null && loader.ScreenLoaded); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 39c2fbfcc9..01400bf1d9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -7,6 +7,10 @@ using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; using System; +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays.Chat; namespace osu.Game.Tests.Visual.Online { @@ -42,14 +46,14 @@ namespace osu.Game.Tests.Visual.Online [Cached] private ChannelManager channelManager = new ChannelManager(); - private readonly StandAloneChatDisplay chatDisplay; - private readonly StandAloneChatDisplay chatDisplay2; + private readonly TestStandAloneChatDisplay chatDisplay; + private readonly TestStandAloneChatDisplay chatDisplay2; public TestSceneStandAloneChatDisplay() { Add(channelManager); - Add(chatDisplay = new StandAloneChatDisplay + Add(chatDisplay = new TestStandAloneChatDisplay { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -57,7 +61,7 @@ namespace osu.Game.Tests.Visual.Online Size = new Vector2(400, 80) }); - Add(chatDisplay2 = new StandAloneChatDisplay(true) + Add(chatDisplay2 = new TestStandAloneChatDisplay(true) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -119,6 +123,49 @@ namespace osu.Game.Tests.Visual.Online Content = "Message from the future!", Timestamp = DateTimeOffset.Now })); + + AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + + const int messages_per_call = 10; + AddRepeatStep("add many messages", () => + { + for (int i = 0; i < messages_per_call; i++) + testChannel.AddNewMessages(new Message(sequence++) + { + Sender = longUsernameUser, + Content = "Many messages! " + Guid.NewGuid(), + Timestamp = DateTimeOffset.Now + }); + }, Channel.MAX_HISTORY / messages_per_call + 5); + + AddAssert("Ensure no adjacent day separators", () => + { + var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); + + foreach (var i in indices) + if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator) + return false; + + return true; + }); + + AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + } + + private class TestStandAloneChatDisplay : StandAloneChatDisplay + { + public TestStandAloneChatDisplay(bool textbox = false) + : base(textbox) + { + } + + protected DrawableChannel DrawableChannel => InternalChildren.OfType().First(); + + protected OsuScrollContainer ScrollContainer => (OsuScrollContainer)((Container)DrawableChannel.Child).Child; + + public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child; + + public bool ScrolledToBottom => ScrollContainer.IsScrolledToEnd(1); } } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index f87d6ebebb..8b82567a8d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -245,6 +245,28 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); } + [Test] + public void TestSortingStability() + { + var sets = new List(); + + for (int i = 0; i < 20; i++) + { + var set = createTestBeatmapSet(i); + set.Metadata.Artist = "same artist"; + set.Metadata.Title = "same title"; + sets.Add(set); + } + + loadBeatmaps(sets); + + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.ID == index).All(b => b)); + + AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.ID == index).All(b => b)); + } + [Test] public void TestSortingWithFiltered() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs similarity index 82% rename from osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs index 700adad9cb..8179f92ffc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs @@ -11,7 +11,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneLabelledComponent : OsuTestScene + public class TestSceneLabelledDrawable : OsuTestScene { [TestCase(false)] [TestCase(true)] @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledDrawable component; Child = new Container { @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + Child = component = padded ? (LabelledDrawable)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), }; component.Label = "a sample component"; @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private class PaddedLabelledComponent : LabelledComponent + private class PaddedLabelledDrawable : LabelledDrawable { - public PaddedLabelledComponent() + public PaddedLabelledDrawable() : base(true) { } @@ -57,9 +57,9 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class NonPaddedLabelledComponent : LabelledComponent + private class NonPaddedLabelledDrawable : LabelledDrawable { - public NonPaddedLabelledComponent() + public NonPaddedLabelledDrawable() : base(false) { } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 53a2bfabbc..8208b55952 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledTextBox component; Child = new Container { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 091a837745..a67daa2756 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tournament.Screens }; } - private class ActionableInfo : LabelledComponent + private class ActionableInfo : LabelledDrawable { private OsuButton button; diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index b567f0c0e3..9fed8e03ac 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -108,7 +108,7 @@ namespace osu.Game.Database return Import(notification, paths); } - protected async Task Import(ProgressNotification notification, params string[] paths) + protected async Task> Import(ProgressNotification notification, params string[] paths) { notification.Progress = 0; notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; @@ -168,6 +168,8 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Completed; } + + return imported; } /// diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 78c0837ce9..e3c6ad25e6 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -76,7 +76,12 @@ namespace osu.Game.Database Task.Factory.StartNew(async () => { // This gets scheduled back to the update thread, but we want the import to run in the background. - await Import(notification, filename); + var imported = await Import(notification, filename); + + // for now a failed import will be marked as a failed download for simplicity. + if (!imported.Any()) + DownloadFailed?.Invoke(request); + currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index c1810800a0..4124d2ad58 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { hover.FadeIn(200); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 2e659825b7..1819b36667 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -1,132 +1,24 @@ // 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.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public abstract class LabelledComponent : CompositeDrawable - where T : Drawable + public abstract class LabelledComponent : LabelledDrawable, IHasCurrentValue + where T : Drawable, IHasCurrentValue { - protected const float CONTENT_PADDING_VERTICAL = 10; - protected const float CONTENT_PADDING_HORIZONTAL = 15; - protected const float CORNER_RADIUS = 15; - - /// - /// The component that is being displayed. - /// - protected readonly T Component; - - private readonly OsuTextFlowContainer labelText; - private readonly OsuTextFlowContainer descriptionText; - - /// - /// Creates a new . - /// - /// Whether the component should be padded or should be expanded to the bounds of this . protected LabelledComponent(bool padded) + : base(padded) { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - CornerRadius = CORNER_RADIUS; - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("1c2125"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = padded - ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } - : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, - Spacing = new Vector2(0, 12), - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - new Drawable[] - { - labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 20 } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = Component = CreateComponent().With(d => - { - d.Anchor = Anchor.CentreRight; - d.Origin = Anchor.CentreRight; - }) - } - }, - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }, - descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, - Alpha = 0, - } - } - } - }; } - [BackgroundDependencyLoader] - private void load(OsuColour osuColour) + public Bindable Current { - descriptionText.Colour = osuColour.Yellow; + get => Component.Current; + set => Component.Current = value; } - - public string Label - { - set => labelText.Text = value; - } - - public string Description - { - set - { - descriptionText.Text = value; - - if (!string.IsNullOrEmpty(value)) - descriptionText.Show(); - else - descriptionText.Hide(); - } - } - - /// - /// Creates the component that should be displayed. - /// - /// The component. - protected abstract T CreateComponent(); } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs new file mode 100644 index 0000000000..f44bd72aee --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -0,0 +1,132 @@ +// 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.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public abstract class LabelledDrawable : CompositeDrawable + where T : Drawable + { + protected const float CONTENT_PADDING_VERTICAL = 10; + protected const float CONTENT_PADDING_HORIZONTAL = 15; + protected const float CORNER_RADIUS = 15; + + /// + /// The component that is being displayed. + /// + protected readonly T Component; + + private readonly OsuTextFlowContainer labelText; + private readonly OsuTextFlowContainer descriptionText; + + /// + /// Creates a new . + /// + /// Whether the component should be padded or should be expanded to the bounds of this . + protected LabelledDrawable(bool padded) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("1c2125"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = padded + ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } + : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, + Spacing = new Vector2(0, 12), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 20 } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Component = CreateComponent().With(d => + { + d.Anchor = Anchor.CentreRight; + d.Origin = Anchor.CentreRight; + }) + } + }, + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }, + descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, + Alpha = 0, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + descriptionText.Colour = osuColour.Yellow; + } + + public string Label + { + set => labelText.Text = value; + } + + public string Description + { + set + { + descriptionText.Text = value; + + if (!string.IsNullOrEmpty(value)) + descriptionText.Show(); + else + descriptionText.Hide(); + } + } + + /// + /// Creates the component that should be displayed. + /// + /// The component. + protected abstract T CreateComponent(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs index c973f1d13e..c374d80830 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs @@ -3,7 +3,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledSwitchButton : LabelledComponent + public class LabelledSwitchButton : LabelledComponent { public LabelledSwitchButton() : base(true) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 50d2a14482..2cbe095d0b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledTextBox : LabelledComponent + public class LabelledTextBox : LabelledComponent { public event TextBox.OnCommitHandler OnCommit; diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 9ec39c5cb1..451174a73c 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.Chat { public class Channel { - public readonly int MaxHistory = 300; + public const int MAX_HISTORY = 300; /// /// Contains every joined user except the current logged in user. Currently only returned for PM channels. @@ -80,8 +80,6 @@ namespace osu.Game.Online.Chat /// public Bindable Joined = new Bindable(); - public const int MAX_HISTORY = 300; - [JsonConstructor] public Channel() { @@ -162,8 +160,8 @@ namespace osu.Game.Online.Chat { // never purge local echos int messageCount = Messages.Count - pendingMessages.Count; - if (messageCount > MaxHistory) - Messages.RemoveRange(0, messageCount - MaxHistory); + if (messageCount > MAX_HISTORY) + Messages.RemoveRange(0, messageCount - MAX_HISTORY); } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6cdbfabe0f..427bd8dcde 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -89,8 +89,10 @@ namespace osu.Game.Overlays.Chat private void newMessagesArrived(IEnumerable newMessages) { + bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage); + // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -103,19 +105,32 @@ namespace osu.Game.Overlays.Chat lastMessage = message; } - if (scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage)) - scrollToEnd(); - var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); - int count = staleMessages.Length - Channel.MaxHistory; + int count = staleMessages.Length - Channel.MAX_HISTORY; - for (int i = 0; i < count; i++) + if (count > 0) { - var d = staleMessages[i]; - if (!scroll.IsScrolledToEnd(10)) + void expireAndAdjustScroll(Drawable d) + { scroll.OffsetScrollPosition(-d.DrawHeight); - d.Expire(); + d.Expire(); + } + + for (int i = 0; i < count; i++) + expireAndAdjustScroll(staleMessages[i]); + + // remove all adjacent day separators after stale message removal + for (int i = 0; i < ChatLineFlow.Count - 1; i++) + { + if (!(ChatLineFlow[i] is DaySeparator)) break; + if (!(ChatLineFlow[i + 1] is DaySeparator)) break; + + expireAndAdjustScroll(ChatLineFlow[i]); + } } + + if (shouldScrollToEnd) + scrollToEnd(); } private void pendingMessageResolved(Message existing, Message updated) @@ -141,7 +156,7 @@ namespace osu.Game.Overlays.Chat private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - protected class DaySeparator : Container + public class DaySeparator : Container { public float TextSize { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index b02b1a5489..a8bbccb168 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -200,6 +200,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { private TextBox username; private TextBox password; + private ShakeContainer shakeSignIn; private IAPIProvider api; public Action RequestHide; @@ -208,6 +209,8 @@ namespace osu.Game.Overlays.Settings.Sections.General { if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) api.Login(username.Text, password.Text); + else + shakeSignIn.Shake(); } [BackgroundDependencyLoader(permitNulls: true)] @@ -244,10 +247,23 @@ namespace osu.Game.Overlays.Settings.Sections.General LabelText = "Stay signed in", Bindable = config.GetBindable(OsuSetting.SavePassword), }, - new SettingsButton + new Container { - Text = "Sign in", - Action = performLogin + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + shakeSignIn = new ShakeContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SettingsButton + { + Text = "Sign in", + Action = performLogin + }, + } + } }, new SettingsButton { diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index 093d01f12d..e2dd14b18c 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Menu public IntroSequence() { RelativeSizeAxes = Axes.Both; + Alpha = 0; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index b2c3952f38..d201b5d30e 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -45,8 +45,6 @@ namespace osu.Game.Screens.Play.HUD VisualSettings = new VisualSettings { Expanded = false } } }; - - Show(); } protected override void PopIn() => this.FadeIn(fade_duration); diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 6642efdf8b..3df06ebfa8 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -106,6 +106,8 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { + base.LoadComplete(); + Show(); replayLoaded.ValueChanged += loaded => AllowSeeking = loaded.NewValue; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 09b728abeb..aa48d1a04e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; namespace osu.Game.Screens.Select.Carousel { @@ -81,12 +82,10 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - var children = new List(InternalChildren); - - children.ForEach(c => c.Filter(criteria)); - children.Sort((x, y) => x.CompareTo(criteria, y)); - - InternalChildren = children; + InternalChildren.ForEach(c => c.Filter(criteria)); + // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability + var criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); + InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList(); } protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e898a001de..0cb09d9b14 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 656c60543e..719aced705 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + +