1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 18:52:55 +08:00

Merge pull request #26172 from bdach/system-title-basic

Add support for displaying "system title" on main menu
This commit is contained in:
Dean Herbert 2023-12-28 16:05:49 +09:00 committed by GitHub
commit 3a3b4d445c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 273 additions and 1 deletions

View File

@ -0,0 +1,30 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
public partial class TestSceneMainMenu : OsuGameTestScene
{
[Test]
public void TestSystemTitle()
{
AddStep("set system title", () => Game.ChildrenOfType<SystemTitle>().Single().Current.Value = new APISystemTitle
{
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023",
});
AddStep("set another title", () => Game.ChildrenOfType<SystemTitle>().Single().Current.Value = new APISystemTitle
{
Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png",
Url = @"https://osu.ppy.sh/community/contests/189",
});
AddStep("unset system title", () => Game.ChildrenOfType<SystemTitle>().Single().Current.Value = null);
}
}
}

View File

@ -0,0 +1,16 @@
// 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 osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetSystemTitleRequest : OsuJsonWebRequest<APISystemTitle>
{
public GetSystemTitleRequest()
: base($@"https://assets.ppy.sh/lazer-status.json?{DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 1800}")
{
}
}
}

View File

@ -0,0 +1,30 @@
// 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 Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
public class APISystemTitle : IEquatable<APISystemTitle>
{
[JsonProperty(@"image")]
public string Image { get; set; } = string.Empty;
[JsonProperty(@"url")]
public string Url { get; set; } = string.Empty;
public bool Equals(APISystemTitle? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Image == other.Image && Url == other.Url;
}
public override bool Equals(object? obj) => obj is APISystemTitle other && Equals(other);
// ReSharper disable NonReadonlyMemberInGetHashCode
public override int GetHashCode() => HashCode.Combine(Image, Url);
}
}

View File

@ -995,7 +995,10 @@ namespace osu.Game
}, topMostOverlayContent.Add);
if (!args?.Any(a => a == @"--no-version-overlay") ?? true)
loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
{
dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue });
loadComponentSingleFile(versionManager, ScreenContainer.Add);
}
loadComponentSingleFile(osuLogo, _ =>
{

View File

@ -76,6 +76,9 @@ namespace osu.Game.Screens.Menu
[Resolved(canBeNull: true)]
private IDialogOverlay dialogOverlay { get; set; }
[Resolved(canBeNull: true)]
private VersionManager versionManager { get; set; }
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
protected override bool PlayExitSound => false;
@ -91,6 +94,7 @@ namespace osu.Game.Screens.Menu
private ParallaxContainer buttonsContainer;
private SongTicker songTicker;
private Container logoTarget;
private SystemTitle systemTitle;
private Sample reappearSampleSwoosh;
@ -153,6 +157,7 @@ namespace osu.Game.Screens.Menu
Margin = new MarginPadding { Right = 15, Top = 5 }
},
new KiaiMenuFountains(),
systemTitle = new SystemTitle(),
holdToExitGameOverlay?.CreateProxy() ?? Empty()
});
@ -263,6 +268,16 @@ namespace osu.Game.Screens.Menu
}
}
protected override void Update()
{
base.Update();
systemTitle.Margin = new MarginPadding
{
Bottom = (versionManager?.DrawHeight + 5) ?? 0
};
}
protected override void LogoSuspending(OsuLogo logo)
{
var seq = logo.FadeOut(300, Easing.InSine)

View File

@ -0,0 +1,178 @@
// 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.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Screens.Menu
{
public partial class SystemTitle : CompositeDrawable
{
internal Bindable<APISystemTitle?> Current { get; } = new Bindable<APISystemTitle?>();
private Container content = null!;
private CancellationTokenSource? cancellationTokenSource;
private SystemTitleImage? currentImage;
private ScheduledDelegate? openUrlAction;
[BackgroundDependencyLoader]
private void load(OsuGame? game)
{
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
AutoSizeAxes = Axes.Both;
InternalChild = content = new OsuClickableContainer
{
AutoSizeAxes = Axes.Both,
Action = () =>
{
currentImage?.Flash();
// Delay slightly to allow animation to play out.
openUrlAction?.Cancel();
openUrlAction = Scheduler.AddDelayed(() =>
{
if (!string.IsNullOrEmpty(Current.Value?.Url))
game?.HandleLink(Current.Value.Url);
}, 250);
}
};
}
protected override bool OnHover(HoverEvent e)
{
content.ScaleTo(1.05f, 2000, Easing.OutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
content.ScaleTo(1f, 500, Easing.OutQuint);
base.OnHoverLost(e);
}
protected override bool OnMouseDown(MouseDownEvent e)
{
content.ScaleTo(0.95f, 500, Easing.OutQuint);
return base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseUpEvent e)
{
content
.ScaleTo(0.95f)
.ScaleTo(1, 500, Easing.OutElastic);
base.OnMouseUp(e);
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => loadNewImage(), true);
checkForUpdates();
}
private void checkForUpdates()
{
var request = new GetSystemTitleRequest();
Task.Run(() => request.Perform())
.ContinueWith(r =>
{
if (r.IsCompletedSuccessfully)
Schedule(() => Current.Value = request.ResponseObject);
// if the request failed, "observe" the exception.
// it isn't very important why this failed, as it's only for display.
// the inner error will be logged by framework mechanisms anyway.
if (r.IsFaulted)
_ = r.Exception;
Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(5).TotalMilliseconds);
});
}
private void loadNewImage()
{
cancellationTokenSource?.Cancel();
cancellationTokenSource = null;
currentImage?.FadeOut(500, Easing.OutQuint).Expire();
if (string.IsNullOrEmpty(Current.Value?.Image))
return;
LoadComponentAsync(new SystemTitleImage(Current.Value), loaded =>
{
if (!loaded.SystemTitle.Equals(Current.Value))
loaded.Dispose();
content.Add(currentImage = loaded);
}, (cancellationTokenSource ??= new CancellationTokenSource()).Token);
}
[LongRunningLoad]
private partial class SystemTitleImage : CompositeDrawable
{
public readonly APISystemTitle SystemTitle;
private Sprite flash = null!;
public SystemTitleImage(APISystemTitle systemTitle)
{
SystemTitle = systemTitle;
}
[BackgroundDependencyLoader]
private void load(LargeTextureStore textureStore)
{
var texture = textureStore.Get(SystemTitle.Image);
if (SystemTitle.Image.Contains(@"@2x"))
texture.ScaleAdjust *= 2;
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Sprite { Texture = texture },
flash = new Sprite
{
Texture = texture,
Blending = BlendingParameters.Additive,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(500, Easing.OutQuint);
flash.FadeOutFromOne(4000, Easing.OutQuint);
}
public Drawable Flash()
{
flash.FadeInFromZero(50)
.Then()
.FadeOut(500, Easing.OutQuint);
return this;
}
}
}
}