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 @@
-
+