// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osu.Game.Users.Drawables; using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeEventFeed : CompositeDrawable { private DailyChallengeEventFeedFlow flow = null!; public Action<long>? PresentScore { get; init; } private readonly Queue<NewScoreEvent> newScores = new Queue<NewScoreEvent>(); [BackgroundDependencyLoader] private void load() { InternalChildren = new Drawable[] { new SectionHeader("Events"), new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 35 }, Child = flow = new DailyChallengeEventFeedFlow { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, Spacing = new Vector2(5), Masking = true, } } }; } public void AddNewScore(NewScoreEvent newScoreEvent) { newScores.Enqueue(newScoreEvent); // ensure things don't get too out-of-hand. if (newScores.Count > 25) newScores.Dequeue(); } protected override void Update() { base.Update(); while (newScores.TryDequeue(out var newScore)) { flow.Add(new NewScoreEventRow(newScore) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, PresentScore = PresentScore, }); } for (int i = 0; i < flow.Count; ++i) { var row = flow[i]; row.Alpha = Interpolation.ValueAt(Math.Clamp(row.Y + flow.DrawHeight, 0, flow.DrawHeight), 0f, 1f, 0, flow.DrawHeight, Easing.Out); if (row.Y < -flow.DrawHeight) { row.RemoveAndDisposeImmediately(); i -= 1; } } } private partial class DailyChallengeEventFeedFlow : FillFlowContainer { public override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Reverse(); } private partial class NewScoreEventRow : CompositeDrawable { private readonly NewScoreEvent newScore; public Action<long>? PresentScore { get; init; } public NewScoreEventRow(NewScoreEvent newScore) { this.newScore = newScore; } [BackgroundDependencyLoader] private void load(OsuColour colours) { LinkFlowContainer text; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; AutoSizeDuration = 300; AutoSizeEasing = Easing.OutQuint; InternalChildren = new Drawable[] { new ClickableAvatar(newScore.User) { Size = new Vector2(16), Masking = true, CornerRadius = 8, }, text = new LinkFlowContainer(t => { FontWeight fontWeight = FontWeight.Medium; if (newScore.NewRank < 100) fontWeight = FontWeight.Bold; else if (newScore.NewRank < 1000) fontWeight = FontWeight.SemiBold; t.Font = OsuFont.Default.With(weight: fontWeight); t.Colour = newScore.NewRank < 10 ? colours.Orange1 : Colour4.White; }) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = 21 }, } }; text.AddUserLink(newScore.User); text.AddText(" scored "); text.AddLink($"{newScore.TotalScore:N0}", () => PresentScore?.Invoke(newScore.ScoreID)); if (newScore.NewRank != null) text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}"); text.AddText("!"); } } } }