diff --git a/osu.Android.props b/osu.Android.props index 0563e5319d..ff04c7f120 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 65bed071cd..8cb7f3f4b6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) { // force completion only once to not break human interaction - Disc.RotationAbsolute = Spinner.SpinsRequired * 360; + Disc.CumulativeRotation = Spinner.SpinsRequired * 360; auto = false; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index ea006ec607..6b1394d799 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -14,6 +14,12 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; using osu.Game.Storyboards; using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; @@ -36,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Tests } private DrawableSpinner drawableSpinner; + private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single(); [SetUpSteps] public override void SetUpSteps() @@ -50,25 +57,78 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSpinnerRewindingRotation() { addSeekStep(5000); - AddAssert("is rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); + AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, 0, 100)); addSeekStep(0); - AddAssert("is rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); + AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, 0, 100)); } [Test] public void TestSpinnerMiddleRewindingRotation() { - double estimatedRotation = 0; + double finalAbsoluteDiscRotation = 0, finalRelativeDiscRotation = 0, finalSpinnerSymbolRotation = 0; addSeekStep(5000); - AddStep("retrieve rotation", () => estimatedRotation = drawableSpinner.Disc.RotationAbsolute); + AddStep("retrieve disc relative rotation", () => finalRelativeDiscRotation = drawableSpinner.Disc.Rotation); + AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.Disc.CumulativeRotation); + AddStep("retrieve spinner symbol rotation", () => finalSpinnerSymbolRotation = spinnerSymbol.Rotation); addSeekStep(2500); + AddUntilStep("disc rotation rewound", + // we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in. + () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation / 2, 100)); + AddUntilStep("symbol rotation rewound", + () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, 100)); + addSeekStep(5000); - AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100)); + AddAssert("is disc rotation almost same", + () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation, 100)); + AddAssert("is symbol rotation almost same", + () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, 100)); + AddAssert("is disc rotation absolute almost same", + () => Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, finalAbsoluteDiscRotation, 100)); } + [Test] + public void TestRotationDirection([Values(true, false)] bool clockwise) + { + if (clockwise) + { + AddStep("flip replay", () => + { + var drawableRuleset = this.ChildrenOfType().Single(); + var score = drawableRuleset.ReplayScore; + var scoreWithFlippedReplay = new Score + { + ScoreInfo = score.ScoreInfo, + Replay = flipReplay(score.Replay) + }; + drawableRuleset.SetReplayScore(scoreWithFlippedReplay); + }); + } + + addSeekStep(5000); + + AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.Disc.Rotation > 0 : drawableSpinner.Disc.Rotation < 0); + AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0); + } + + private Replay flipReplay(Replay scoreReplay) => new Replay + { + Frames = scoreReplay + .Frames + .Cast() + .Select(replayFrame => + { + var flippedPosition = new Vector2(OsuPlayfield.BASE_SIZE.X - replayFrame.Position.X, replayFrame.Position.Y); + return new OsuReplayFrame(replayFrame.Time, flippedPosition, replayFrame.Actions.ToArray()); + }) + .Cast() + .ToList() + }; + [Test] public void TestSpinPerMinuteOnRewind() { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 4d37622be5..be6766509c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => Math.Clamp(Disc.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { @@ -191,13 +191,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; - SpmCounter.SetRotation(Disc.RotationAbsolute); + SpmCounter.SetRotation(Disc.CumulativeRotation); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.RotationAbsolute / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } protected override void UpdateInitialTransforms() @@ -207,9 +207,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circleContainer.ScaleTo(Spinner.Scale * 0.3f); circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint); - Disc.RotateTo(-720); - symbol.RotateTo(-720); - mainContainer .ScaleTo(0) .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index d4ef039b79..35819cd05e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -73,6 +73,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + /// + /// The total rotation performed on the spinner disc, disregarding the spin direction. + /// + /// + /// This value is always non-negative and is monotonically increasing with time + /// (i.e. will only increase if time is passing forward, but can decrease during rewind). + /// + /// + /// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, + /// this property will return the value of 720 (as opposed to 0 for ). + /// + public float CumulativeRotation; + /// /// Whether currently in the correct time range to allow spinning. /// @@ -88,10 +101,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float lastAngle; private float currentRotation; - public float RotationAbsolute; private int completeTick; - private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); + private bool updateCompleteTick() => completeTick != (completeTick = (int)(CumulativeRotation / 360)); private bool rotationTransferred; @@ -149,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } currentRotation += angle; - RotationAbsolute += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime); + CumulativeRotation += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index c11e20c9e7..1e54b576f1 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects @@ -24,6 +25,13 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderRepeat.SliderRepeatJudgement(); + public override Judgement CreateJudgement() => new SliderTailJudgement(); + + public class SliderTailJudgement : OsuJudgement + { + protected override int NumericResultFor(HitResult result) => 0; + + public override bool AffectsCombo => false; + } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs new file mode 100644 index 0000000000..0446cadac9 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs @@ -0,0 +1,52 @@ +// 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.Overlays.News; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Allocation; +using osu.Game.Overlays; +using osuTK; +using System; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsCard : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Purple); + + public TestSceneNewsCard() + { + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Width = 500, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new[] + { + new NewsCard(new APINewsPost + { + Title = "This post has an image which starts with \"/\" and has many authors!", + Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + Author = "someone, someone1, someone2, someone3, someone4", + FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", + PublishedAt = DateTimeOffset.Now + }), + new NewsCard(new APINewsPost + { + Title = "This post has a full-url image! (HTML entity: &)", + Preview = "boom (HTML entity: &)", + Author = "user (HTML entity: &)", + FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", + PublishedAt = DateTimeOffset.Now + }) + } + }); + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index d47d37806e..3106d1143e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -183,6 +183,7 @@ namespace osu.Game.Beatmaps public void Dispose() { cacheDownloadRequest?.Dispose(); + updateScheduler?.Dispose(); } [Serializable] diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs new file mode 100644 index 0000000000..67fcab43f7 --- /dev/null +++ b/osu.Game/Graphics/DateTooltip.cs @@ -0,0 +1,78 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics +{ + public class DateTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText dateText, timeText; + private readonly Box background; + + public DateTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + dateText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + timeText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; + timeText.Colour = colours.BlueLighter; + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + public bool SetContent(object content) + { + if (!(content is DateTimeOffset date)) + return false; + + dateText.Text = $"{date:d MMMM yyyy} "; + timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; + return true; + } + + public void Move(Vector2 pos) => Position = pos; + } +} diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 8b6df4a834..259d9c8d6e 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -4,12 +4,9 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using osu.Game.Utils; -using osuTK; namespace osu.Game.Graphics { @@ -81,69 +78,5 @@ namespace osu.Game.Graphics public ITooltip GetCustomTooltip() => new DateTooltip(); public object TooltipContent => Date; - - private class DateTooltip : VisibilityContainer, ITooltip - { - private readonly OsuSpriteText dateText, timeText; - private readonly Box background; - - public DateTooltip() - { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding(10), - Children = new Drawable[] - { - dateText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }, - timeText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - } - } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreySeafoamDarker; - timeText.Colour = colours.BlueLighter; - } - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - - public bool SetContent(object content) - { - if (!(content is DateTimeOffset date)) - return false; - - dateText.Text = $"{date:d MMMM yyyy} "; - timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; - return true; - } - - public void Move(Vector2 pos) => Position = pos; - } } } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index c2feca171b..61501b0cd8 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -23,6 +23,8 @@ namespace osu.Game.Graphics.UserInterface { private Color4 accentColour; + public const float HORIZONTAL_SPACING = 10; + public virtual Color4 AccentColour { get => accentColour; @@ -54,7 +56,7 @@ namespace osu.Game.Graphics.UserInterface public OsuTabControl() { - TabContainer.Spacing = new Vector2(10f, 0f); + TabContainer.Spacing = new Vector2(HORIZONTAL_SPACING, 0f); AddInternal(strip = new Box { diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 74b3134964..198ab6883d 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -55,6 +55,9 @@ namespace osu.Game.Input RulesetID = rulesetId, Variant = variant }); + + // required to ensure stable insert order (https://github.com/dotnet/efcore/issues/11686) + usage.Context.SaveChanges(); } } } diff --git a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs new file mode 100644 index 0000000000..ced08f0bf2 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System; +using System.Net; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APINewsPost + { + [JsonProperty("id")] + public long Id { get; set; } + + private string author; + + [JsonProperty("author")] + public string Author + { + get => author; + set => author = WebUtility.HtmlDecode(value); + } + + [JsonProperty("edit_url")] + public string EditUrl { get; set; } + + [JsonProperty("first_image")] + public string FirstImage { get; set; } + + [JsonProperty("published_at")] + public DateTimeOffset PublishedAt { get; set; } + + [JsonProperty("updated_at")] + public DateTimeOffset UpdatedAt { get; set; } + + [JsonProperty("slug")] + public string Slug { get; set; } + + private string title; + + [JsonProperty("title")] + public string Title + { + get => title; + set => title = WebUtility.HtmlDecode(value); + } + + private string preview; + + [JsonProperty("preview")] + public string Preview + { + get => preview; + set => preview = WebUtility.HtmlDecode(value); + } + } +} diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs new file mode 100644 index 0000000000..9c478a7c1d --- /dev/null +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -0,0 +1,198 @@ +// 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.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.News +{ + public class NewsCard : CompositeDrawable + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly APINewsPost post; + + private Box background; + private TextFlowContainer main; + + public NewsCard(APINewsPost post) + { + this.post = post; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 6; + + NewsBackground bg; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 160, + Masking = true, + CornerRadius = 6, + Children = new Drawable[] + { + new DelayedLoadWrapper(bg = new NewsBackground(post.FirstImage) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0 + }) + { + RelativeSizeAxes = Axes.Both + }, + new DateContainer(post.PublishedAt) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding + { + Top = 10, + Right = 15 + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 15, + Vertical = 10 + }, + Child = main = new TextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } + }, + new HoverClickSounds() + }; + + bg.OnLoadComplete += d => d.FadeIn(250, Easing.In); + + main.AddParagraph(post.Title, t => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold)); + main.AddParagraph(post.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font + main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12)); + main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)); + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeColour(colourProvider.Background3, 200, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeColour(colourProvider.Background4, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + + [LongRunningLoad] + private class NewsBackground : Sprite + { + private readonly string sourceUrl; + + public NewsBackground(string sourceUrl) + { + this.sourceUrl = sourceUrl; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore store) + { + Texture = store.Get(createUrl(sourceUrl)); + } + + private string createUrl(string source) + { + if (string.IsNullOrEmpty(source)) + return "Headers/news"; + + if (source.StartsWith('/')) + return "https://osu.ppy.sh" + source; + + return source; + } + } + + private class DateContainer : CircularContainer, IHasCustomTooltip + { + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => date; + + private readonly DateTimeOffset date; + + public DateContainer(DateTimeOffset date) + { + this.date = date; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + Masking = true; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6.Opacity(0.5f) + }, + new OsuSpriteText + { + Text = date.ToString("d MMM yyyy").ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Margin = new MarginPadding + { + Horizontal = 20, + Vertical = 5 + } + } + }; + } + } + } +} diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 1b748cb672..de08b79f57 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -28,9 +28,6 @@ namespace osu.Game.Overlays.Toolbar private const double transition_time = 500; - private const float alpha_hovering = 0.8f; - private const float alpha_normal = 0.6f; - private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. @@ -103,7 +100,6 @@ namespace osu.Game.Overlays.Toolbar public class ToolbarBackground : Container { - private readonly Box solidBackground; private readonly Box gradientBackground; public ToolbarBackground() @@ -111,11 +107,10 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.Both; Children = new Drawable[] { - solidBackground = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.1f), - Alpha = alpha_normal, }, gradientBackground = new Box { @@ -131,14 +126,12 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { - solidBackground.FadeTo(alpha_hovering, transition_time, Easing.OutQuint); gradientBackground.FadeIn(transition_time, Easing.OutQuint); return true; } protected override void OnHoverLost(HoverLostEvent e) { - solidBackground.FadeTo(alpha_normal, transition_time, Easing.OutQuint); gradientBackground.FadeOut(transition_time, Easing.OutQuint); } } @@ -146,7 +139,7 @@ namespace osu.Game.Overlays.Toolbar protected override void PopIn() { this.MoveToY(0, transition_time, Easing.OutQuint); - this.FadeIn(transition_time / 2, Easing.OutQuint); + this.FadeIn(transition_time / 4, Easing.OutQuint); } protected override void PopOut() @@ -154,7 +147,7 @@ namespace osu.Game.Overlays.Toolbar userButton.StateContainer?.Hide(); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); - this.FadeOut(transition_time); + this.FadeOut(transition_time, Easing.InQuint); } } } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 35091028ae..986de1edf0 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Poo, + Icon = FontAwesome.Solid.Flask, Size = new Vector2(icon_size), Y = icon_y, }, diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index e27fa154af..653cb3791a 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -95,22 +95,22 @@ namespace osu.Game.Screens.Multi { new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 24), Text = "Multiplayer" }, dot = new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 24), Text = "·" }, pageTitle = new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 24), Text = "Lounge" } diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index d4b6a3b79f..9c2ed26b52 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Multi.Lounge public LoungeSubScreen() { - SearchContainer searchContainer; + RoomsContainer roomsContainer; InternalChildren = new Drawable[] { @@ -55,14 +55,9 @@ namespace osu.Game.Screens.Multi.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = searchContainer = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = new RoomsContainer { JoinRequested = joinRequested } - }, + Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested } }, - loadingLayer = new LoadingLayer(searchContainer), + loadingLayer = new LoadingLayer(roomsContainer), } }, new RoomInspector diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index d613ce649a..e111ec4b15 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,21 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Select.Filter; -using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; -using osu.Game.Configuration; -using osu.Game.Rulesets; using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Screens.Select.Filter; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select { @@ -26,9 +25,7 @@ namespace osu.Game.Screens.Select public Action FilterChanged; - private readonly OsuTabControl sortTabs; - - private readonly TabControl groupTabs; + private OsuTabControl sortTabs; private Bindable sortMode; @@ -56,19 +53,39 @@ namespace osu.Game.Screens.Select return criteria; } - private readonly SeekLimitedSearchTextBox searchTextBox; + private SeekLimitedSearchTextBox searchTextBox; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - base.ReceivePositionalInputAt(screenSpacePos) || groupTabs.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); + base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); - public FilterControl() + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) { + config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); + showConverted.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); + minimumStars.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); + maximumStars.ValueChanged += _ => updateCriteria(); + + ruleset.BindTo(parentRuleset); + ruleset.BindValueChanged(_ => updateCriteria()); + + sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); + groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); + + groupMode.BindValueChanged(_ => updateCriteria()); + sortMode.BindValueChanged(_ => updateCriteria()); + Children = new Drawable[] { - Background = new Box + new Box { Colour = Color4.Black, Alpha = 0.8f, + Width = 2, RelativeSizeAxes = Axes.Both, }, new Container @@ -96,33 +113,35 @@ namespace osu.Game.Screens.Select Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), Children = new Drawable[] { - groupTabs = new OsuTabControl + new OsuTabControlCheckbox { - RelativeSizeAxes = Axes.X, - Height = 24, - Width = 0.5f, - AutoSort = true, + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, }, - //spriteText = new OsuSpriteText - //{ - // Font = @"Exo2.0-Bold", - // Text = "Sort results by", - // Size = 14, - // Margin = new MarginPadding - // { - // Top = 5, - // Bottom = 5 - // }, - //}, sortTabs = new OsuTabControl { RelativeSizeAxes = Axes.X, Width = 0.5f, Height = 24, AutoSort = true, - } + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + new OsuSpriteText + { + Text = "Sort by", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, } }, } @@ -131,8 +150,7 @@ namespace osu.Game.Screens.Select searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria()); - groupTabs.PinItem(GroupMode.All); - groupTabs.PinItem(GroupMode.RecentlyPlayed); + updateCriteria(); } public void Deactivate() @@ -156,37 +174,6 @@ namespace osu.Game.Screens.Select private readonly Bindable minimumStars = new BindableDouble(); private readonly Bindable maximumStars = new BindableDouble(); - public readonly Box Background; - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) - { - sortTabs.AccentColour = colours.GreenLight; - - config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); - showConverted.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); - minimumStars.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); - maximumStars.ValueChanged += _ => updateCriteria(); - - ruleset.BindTo(parentRuleset); - ruleset.BindValueChanged(_ => updateCriteria()); - - sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); - groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); - - sortTabs.Current.BindTo(sortMode); - groupTabs.Current.BindTo(groupMode); - - groupMode.BindValueChanged(_ => updateCriteria()); - sortMode.BindValueChanged(_ => updateCriteria()); - - updateCriteria(); - } - private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); protected override bool OnClick(ClickEvent e) => true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d613b0ae8d..e3705b15fa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -173,7 +173,6 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, Height = FilterControl.HEIGHT, FilterChanged = ApplyFilterToCarousel, - Background = { Width = 2 }, }, new GridContainer // used for max width implementation { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4e6de77e86..e4753e7ee9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c31e28638f..91fa003604 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - +