1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 16:07:24 +08:00

Merge branch 'master' into universal-offset-from-session-plays

This commit is contained in:
Bartłomiej Dach 2023-12-28 14:07:46 +01:00
commit 24a80da83f
No known key found for this signature in database
70 changed files with 1279 additions and 638 deletions

View File

@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit
.Concat(DistanceSnapProvider.CreateTernaryButtons())
.Concat(new[]
{
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap })
});
private BindableList<HitObject> selectedHitObjects;

View File

@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private const float spinning_sample_initial_frequency = 1.0f;
private const float spinning_sample_modulated_base_frequency = 0.5f;
private SkinnableSound maxBonusSample;
private PausableSkinnableSound maxBonusSample;
/// <summary>
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Looping = true,
Frequency = { Value = spinning_sample_initial_frequency }
},
maxBonusSample = new SkinnableSound
maxBonusSample = new PausableSkinnableSound
{
MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME,
}

View File

@ -70,10 +70,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
};
userCursorScale = config.GetBindable<float>(OsuSetting.GameplayCursorSize);
userCursorScale.ValueChanged += _ => calculateCursorScale();
userCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale();
autoCursorScale = config.GetBindable<bool>(OsuSetting.AutoCursorSize);
autoCursorScale.ValueChanged += _ => calculateCursorScale();
autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale();
cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true);
}
@ -81,10 +81,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override void LoadComplete()
{
base.LoadComplete();
calculateCursorScale();
cursorScale.Value = CalculateCursorScale();
}
private void calculateCursorScale()
protected virtual float CalculateCursorScale()
{
float scale = userCursorScale.Value;
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize);
}
cursorScale.Value = scale;
return scale;
}
protected override void SkinChanged(ISkinSource skin)

View File

@ -71,6 +71,12 @@ namespace osu.Game.Rulesets.Osu.UI
RelativePositionAxes = Axes.Both;
}
protected override float CalculateCursorScale()
{
// Force minimum cursor size so it's easily clickable
return Math.Max(1f, base.CalculateCursorScale());
}
protected override bool OnHover(HoverEvent e)
{
updateColour();

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

@ -1,19 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// 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 osu.Framework.Allocation;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
public partial class TestSceneDisclaimer : ScreenTestScene
public partial class TestSceneSupporterDisplay : OsuTestScene
{
[BackgroundDependencyLoader]
private void load()
[Test]
public void TestBasic()
{
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("create display", () =>
{
Child = new SupporterDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
});
AddStep("toggle support", () =>
{

View File

@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public TestTitle()
{
Title = "title";
Icon = HexaconsIcons.Devtools;
Icon = OsuIcon.ChangelogB;
}
}
}

View File

@ -96,6 +96,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MenuVoice, true);
SetDefault(OsuSetting.MenuMusic, true);
SetDefault(OsuSetting.MenuTips, true);
SetDefault(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1);
@ -350,6 +351,7 @@ namespace osu.Game.Configuration
VolumeInactive,
MenuMusic,
MenuVoice,
MenuTips,
CursorRotation,
MenuParallax,
Prefer24HourTime,

View File

@ -1,131 +0,0 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Text;
namespace osu.Game.Graphics
{
public static class HexaconsIcons
{
public const string FONT_NAME = "Icons/Hexacons";
public static IconUsage BeatmapPacks => get(HexaconsMapping.beatmap_packs);
public static IconUsage Beatmap => get(HexaconsMapping.beatmap);
public static IconUsage Calendar => get(HexaconsMapping.calendar);
public static IconUsage Chart => get(HexaconsMapping.chart);
public static IconUsage Community => get(HexaconsMapping.community);
public static IconUsage Contests => get(HexaconsMapping.contests);
public static IconUsage Devtools => get(HexaconsMapping.devtools);
public static IconUsage Download => get(HexaconsMapping.download);
public static IconUsage Editor => get(HexaconsMapping.editor);
public static IconUsage FeaturedArtist => get(HexaconsMapping.featured_artist);
public static IconUsage Home => get(HexaconsMapping.home);
public static IconUsage Messaging => get(HexaconsMapping.messaging);
public static IconUsage Music => get(HexaconsMapping.music);
public static IconUsage News => get(HexaconsMapping.news);
public static IconUsage Notification => get(HexaconsMapping.notification);
public static IconUsage Profile => get(HexaconsMapping.profile);
public static IconUsage Rankings => get(HexaconsMapping.rankings);
public static IconUsage Search => get(HexaconsMapping.search);
public static IconUsage Settings => get(HexaconsMapping.settings);
public static IconUsage Social => get(HexaconsMapping.social);
public static IconUsage Store => get(HexaconsMapping.store);
public static IconUsage Tournament => get(HexaconsMapping.tournament);
public static IconUsage Wiki => get(HexaconsMapping.wiki);
private static IconUsage get(HexaconsMapping icon) => new IconUsage((char)icon, FONT_NAME);
// Basically just converting to something we can use in a `char` lookup for FontStore/GlyphStore compatibility.
// Names should match filenames in resources.
private enum HexaconsMapping
{
beatmap_packs,
beatmap,
calendar,
chart,
community,
contests,
devtools,
download,
editor,
featured_artist,
home,
messaging,
music,
news,
notification,
profile,
rankings,
search,
settings,
social,
store,
tournament,
wiki,
}
public class HexaconsStore : ITextureStore, ITexturedGlyphLookupStore
{
private readonly TextureStore textures;
public HexaconsStore(TextureStore textures)
{
this.textures = textures;
}
public void Dispose()
{
textures.Dispose();
}
public ITexturedCharacterGlyph? Get(string? fontName, char character)
{
if (fontName == FONT_NAME)
return new Glyph(textures.Get($"{fontName}/{((HexaconsMapping)character).ToString().Replace("_", "-")}"));
return null;
}
public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public Texture Get(string name) => throw new NotImplementedException();
public Task<Texture> GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public Stream GetStream(string name) => throw new NotImplementedException();
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
public Task<Texture?> GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public class Glyph : ITexturedCharacterGlyph
{
public float XOffset => default;
public float YOffset => default;
public float XAdvance => default;
public float Baseline => default;
public char Character => default;
public float GetKerning<T>(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException();
public Texture Texture { get; }
public float Width => Texture.Width;
public float Height => Texture.Height;
public Glyph(Texture texture)
{
Texture = texture;
}
}
}
}
}

View File

@ -1,96 +1,444 @@
// 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.ComponentModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Text;
namespace osu.Game.Graphics
{
public static class OsuIcon
{
public static IconUsage Get(int icon) => new IconUsage((char)icon, "osuFont");
#region Legacy spritesheet-based icons
private static IconUsage get(int icon) => new IconUsage((char)icon, @"osuFont");
// ruleset icons in circles
public static IconUsage RulesetOsu => Get(0xe000);
public static IconUsage RulesetMania => Get(0xe001);
public static IconUsage RulesetCatch => Get(0xe002);
public static IconUsage RulesetTaiko => Get(0xe003);
public static IconUsage RulesetOsu => get(0xe000);
public static IconUsage RulesetMania => get(0xe001);
public static IconUsage RulesetCatch => get(0xe002);
public static IconUsage RulesetTaiko => get(0xe003);
// ruleset icons without circles
public static IconUsage FilledCircle => Get(0xe004);
public static IconUsage CrossCircle => Get(0xe005);
public static IconUsage Logo => Get(0xe006);
public static IconUsage ChevronDownCircle => Get(0xe007);
public static IconUsage EditCircle => Get(0xe033);
public static IconUsage LeftCircle => Get(0xe034);
public static IconUsage RightCircle => Get(0xe035);
public static IconUsage Charts => Get(0xe036);
public static IconUsage Solo => Get(0xe037);
public static IconUsage Multi => Get(0xe038);
public static IconUsage Gear => Get(0xe039);
public static IconUsage FilledCircle => get(0xe004);
public static IconUsage Logo => get(0xe006);
public static IconUsage ChevronDownCircle => get(0xe007);
public static IconUsage EditCircle => get(0xe033);
public static IconUsage LeftCircle => get(0xe034);
public static IconUsage RightCircle => get(0xe035);
public static IconUsage Charts => get(0xe036);
public static IconUsage Solo => get(0xe037);
public static IconUsage Multi => get(0xe038);
public static IconUsage Gear => get(0xe039);
// misc icons
public static IconUsage Bat => Get(0xe008);
public static IconUsage Bubble => Get(0xe009);
public static IconUsage BubblePop => Get(0xe02e);
public static IconUsage Dice => Get(0xe011);
public static IconUsage Heart => Get(0xe02f);
public static IconUsage HeartBreak => Get(0xe030);
public static IconUsage Hot => Get(0xe031);
public static IconUsage ListSearch => Get(0xe032);
public static IconUsage Bat => get(0xe008);
public static IconUsage Bubble => get(0xe009);
public static IconUsage BubblePop => get(0xe02e);
public static IconUsage Dice => get(0xe011);
public static IconUsage HeartBreak => get(0xe030);
public static IconUsage Hot => get(0xe031);
public static IconUsage ListSearch => get(0xe032);
//osu! playstyles
public static IconUsage PlayStyleTablet => Get(0xe02a);
public static IconUsage PlayStyleMouse => Get(0xe029);
public static IconUsage PlayStyleKeyboard => Get(0xe02b);
public static IconUsage PlayStyleTouch => Get(0xe02c);
public static IconUsage PlayStyleTablet => get(0xe02a);
public static IconUsage PlayStyleMouse => get(0xe029);
public static IconUsage PlayStyleKeyboard => get(0xe02b);
public static IconUsage PlayStyleTouch => get(0xe02c);
// osu! difficulties
public static IconUsage EasyOsu => Get(0xe015);
public static IconUsage NormalOsu => Get(0xe016);
public static IconUsage HardOsu => Get(0xe017);
public static IconUsage InsaneOsu => Get(0xe018);
public static IconUsage ExpertOsu => Get(0xe019);
public static IconUsage EasyOsu => get(0xe015);
public static IconUsage NormalOsu => get(0xe016);
public static IconUsage HardOsu => get(0xe017);
public static IconUsage InsaneOsu => get(0xe018);
public static IconUsage ExpertOsu => get(0xe019);
// taiko difficulties
public static IconUsage EasyTaiko => Get(0xe01a);
public static IconUsage NormalTaiko => Get(0xe01b);
public static IconUsage HardTaiko => Get(0xe01c);
public static IconUsage InsaneTaiko => Get(0xe01d);
public static IconUsage ExpertTaiko => Get(0xe01e);
public static IconUsage EasyTaiko => get(0xe01a);
public static IconUsage NormalTaiko => get(0xe01b);
public static IconUsage HardTaiko => get(0xe01c);
public static IconUsage InsaneTaiko => get(0xe01d);
public static IconUsage ExpertTaiko => get(0xe01e);
// fruits difficulties
public static IconUsage EasyFruits => Get(0xe01f);
public static IconUsage NormalFruits => Get(0xe020);
public static IconUsage HardFruits => Get(0xe021);
public static IconUsage InsaneFruits => Get(0xe022);
public static IconUsage ExpertFruits => Get(0xe023);
public static IconUsage EasyFruits => get(0xe01f);
public static IconUsage NormalFruits => get(0xe020);
public static IconUsage HardFruits => get(0xe021);
public static IconUsage InsaneFruits => get(0xe022);
public static IconUsage ExpertFruits => get(0xe023);
// mania difficulties
public static IconUsage EasyMania => Get(0xe024);
public static IconUsage NormalMania => Get(0xe025);
public static IconUsage HardMania => Get(0xe026);
public static IconUsage InsaneMania => Get(0xe027);
public static IconUsage ExpertMania => Get(0xe028);
public static IconUsage EasyMania => get(0xe024);
public static IconUsage NormalMania => get(0xe025);
public static IconUsage HardMania => get(0xe026);
public static IconUsage InsaneMania => get(0xe027);
public static IconUsage ExpertMania => get(0xe028);
// mod icons
public static IconUsage ModPerfect => Get(0xe049);
public static IconUsage ModAutopilot => Get(0xe03a);
public static IconUsage ModAuto => Get(0xe03b);
public static IconUsage ModCinema => Get(0xe03c);
public static IconUsage ModDoubleTime => Get(0xe03d);
public static IconUsage ModEasy => Get(0xe03e);
public static IconUsage ModFlashlight => Get(0xe03f);
public static IconUsage ModHalftime => Get(0xe040);
public static IconUsage ModHardRock => Get(0xe041);
public static IconUsage ModHidden => Get(0xe042);
public static IconUsage ModNightcore => Get(0xe043);
public static IconUsage ModNoFail => Get(0xe044);
public static IconUsage ModRelax => Get(0xe045);
public static IconUsage ModSpunOut => Get(0xe046);
public static IconUsage ModSuddenDeath => Get(0xe047);
public static IconUsage ModTarget => Get(0xe048);
public static IconUsage ModPerfect => get(0xe049);
public static IconUsage ModAutopilot => get(0xe03a);
public static IconUsage ModAuto => get(0xe03b);
public static IconUsage ModCinema => get(0xe03c);
public static IconUsage ModDoubleTime => get(0xe03d);
public static IconUsage ModEasy => get(0xe03e);
public static IconUsage ModFlashlight => get(0xe03f);
public static IconUsage ModHalftime => get(0xe040);
public static IconUsage ModHardRock => get(0xe041);
public static IconUsage ModHidden => get(0xe042);
public static IconUsage ModNightcore => get(0xe043);
public static IconUsage ModNoFail => get(0xe044);
public static IconUsage ModRelax => get(0xe045);
public static IconUsage ModSpunOut => get(0xe046);
public static IconUsage ModSuddenDeath => get(0xe047);
public static IconUsage ModTarget => get(0xe048);
// Use "Icons/BeatmapDetails/mod-icon" instead
// public static IconUsage ModBg => Get(0xe04a);
#endregion
#region New single-file-based icons
public const string FONT_NAME = @"Icons";
public static IconUsage Audio => get(OsuIconMapping.Audio);
public static IconUsage Beatmap => get(OsuIconMapping.Beatmap);
public static IconUsage Calendar => get(OsuIconMapping.Calendar);
public static IconUsage ChangelogA => get(OsuIconMapping.ChangelogA);
public static IconUsage ChangelogB => get(OsuIconMapping.ChangelogB);
public static IconUsage Chat => get(OsuIconMapping.Chat);
public static IconUsage CheckCircle => get(OsuIconMapping.CheckCircle);
public static IconUsage CollapseA => get(OsuIconMapping.CollapseA);
public static IconUsage Collections => get(OsuIconMapping.Collections);
public static IconUsage Cross => get(OsuIconMapping.Cross);
public static IconUsage CrossCircle => get(OsuIconMapping.CrossCircle);
public static IconUsage Crown => get(OsuIconMapping.Crown);
public static IconUsage Debug => get(OsuIconMapping.Debug);
public static IconUsage Delete => get(OsuIconMapping.Delete);
public static IconUsage Details => get(OsuIconMapping.Details);
public static IconUsage Discord => get(OsuIconMapping.Discord);
public static IconUsage EllipsisHorizontal => get(OsuIconMapping.EllipsisHorizontal);
public static IconUsage EllipsisVertical => get(OsuIconMapping.EllipsisVertical);
public static IconUsage ExpandA => get(OsuIconMapping.ExpandA);
public static IconUsage ExpandB => get(OsuIconMapping.ExpandB);
public static IconUsage FeaturedArtist => get(OsuIconMapping.FeaturedArtist);
public static IconUsage FeaturedArtistCircle => get(OsuIconMapping.FeaturedArtistCircle);
public static IconUsage GameplayA => get(OsuIconMapping.GameplayA);
public static IconUsage GameplayB => get(OsuIconMapping.GameplayB);
public static IconUsage GameplayC => get(OsuIconMapping.GameplayC);
public static IconUsage Global => get(OsuIconMapping.Global);
public static IconUsage Graphics => get(OsuIconMapping.Graphics);
public static IconUsage Heart => get(OsuIconMapping.Heart);
public static IconUsage Home => get(OsuIconMapping.Home);
public static IconUsage Input => get(OsuIconMapping.Input);
public static IconUsage Maintenance => get(OsuIconMapping.Maintenance);
public static IconUsage Megaphone => get(OsuIconMapping.Megaphone);
public static IconUsage Music => get(OsuIconMapping.Music);
public static IconUsage News => get(OsuIconMapping.News);
public static IconUsage Next => get(OsuIconMapping.Next);
public static IconUsage NextCircle => get(OsuIconMapping.NextCircle);
public static IconUsage Notification => get(OsuIconMapping.Notification);
public static IconUsage Online => get(OsuIconMapping.Online);
public static IconUsage Play => get(OsuIconMapping.Play);
public static IconUsage Player => get(OsuIconMapping.Player);
public static IconUsage PlayerFollow => get(OsuIconMapping.PlayerFollow);
public static IconUsage Prev => get(OsuIconMapping.Prev);
public static IconUsage PrevCircle => get(OsuIconMapping.PrevCircle);
public static IconUsage Ranking => get(OsuIconMapping.Ranking);
public static IconUsage Rulesets => get(OsuIconMapping.Rulesets);
public static IconUsage Search => get(OsuIconMapping.Search);
public static IconUsage Settings => get(OsuIconMapping.Settings);
public static IconUsage SkinA => get(OsuIconMapping.SkinA);
public static IconUsage SkinB => get(OsuIconMapping.SkinB);
public static IconUsage Star => get(OsuIconMapping.Star);
public static IconUsage Storyboard => get(OsuIconMapping.Storyboard);
public static IconUsage Team => get(OsuIconMapping.Team);
public static IconUsage ThumbsUp => get(OsuIconMapping.ThumbsUp);
public static IconUsage Tournament => get(OsuIconMapping.Tournament);
public static IconUsage Twitter => get(OsuIconMapping.Twitter);
public static IconUsage UserInterface => get(OsuIconMapping.UserInterface);
public static IconUsage Wiki => get(OsuIconMapping.Wiki);
public static IconUsage EditorAddControlPoint => get(OsuIconMapping.EditorAddControlPoint);
public static IconUsage EditorConvertToStream => get(OsuIconMapping.EditorConvertToStream);
public static IconUsage EditorDistanceSnap => get(OsuIconMapping.EditorDistanceSnap);
public static IconUsage EditorFinish => get(OsuIconMapping.EditorFinish);
public static IconUsage EditorGridSnap => get(OsuIconMapping.EditorGridSnap);
public static IconUsage EditorNewComboA => get(OsuIconMapping.EditorNewComboA);
public static IconUsage EditorNewComboB => get(OsuIconMapping.EditorNewComboB);
public static IconUsage EditorSelect => get(OsuIconMapping.EditorSelect);
public static IconUsage EditorSound => get(OsuIconMapping.EditorSound);
public static IconUsage EditorWhistle => get(OsuIconMapping.EditorWhistle);
private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME);
private enum OsuIconMapping
{
[Description(@"audio")]
Audio,
[Description(@"beatmap")]
Beatmap,
[Description(@"calendar")]
Calendar,
[Description(@"changelog-a")]
ChangelogA,
[Description(@"changelog-b")]
ChangelogB,
[Description(@"chat")]
Chat,
[Description(@"check-circle")]
CheckCircle,
[Description(@"collapse-a")]
CollapseA,
[Description(@"collections")]
Collections,
[Description(@"cross")]
Cross,
[Description(@"cross-circle")]
CrossCircle,
[Description(@"crown")]
Crown,
[Description(@"debug")]
Debug,
[Description(@"delete")]
Delete,
[Description(@"details")]
Details,
[Description(@"discord")]
Discord,
[Description(@"ellipsis-horizontal")]
EllipsisHorizontal,
[Description(@"ellipsis-vertical")]
EllipsisVertical,
[Description(@"expand-a")]
ExpandA,
[Description(@"expand-b")]
ExpandB,
[Description(@"featured-artist")]
FeaturedArtist,
[Description(@"featured-artist-circle")]
FeaturedArtistCircle,
[Description(@"gameplay-a")]
GameplayA,
[Description(@"gameplay-b")]
GameplayB,
[Description(@"gameplay-c")]
GameplayC,
[Description(@"global")]
Global,
[Description(@"graphics")]
Graphics,
[Description(@"heart")]
Heart,
[Description(@"home")]
Home,
[Description(@"input")]
Input,
[Description(@"maintenance")]
Maintenance,
[Description(@"megaphone")]
Megaphone,
[Description(@"music")]
Music,
[Description(@"news")]
News,
[Description(@"next")]
Next,
[Description(@"next-circle")]
NextCircle,
[Description(@"notification")]
Notification,
[Description(@"online")]
Online,
[Description(@"play")]
Play,
[Description(@"player")]
Player,
[Description(@"player-follow")]
PlayerFollow,
[Description(@"prev")]
Prev,
[Description(@"prev-circle")]
PrevCircle,
[Description(@"ranking")]
Ranking,
[Description(@"rulesets")]
Rulesets,
[Description(@"search")]
Search,
[Description(@"settings")]
Settings,
[Description(@"skin-a")]
SkinA,
[Description(@"skin-b")]
SkinB,
[Description(@"star")]
Star,
[Description(@"storyboard")]
Storyboard,
[Description(@"team")]
Team,
[Description(@"thumbs-up")]
ThumbsUp,
[Description(@"tournament")]
Tournament,
[Description(@"twitter")]
Twitter,
[Description(@"user-interface")]
UserInterface,
[Description(@"wiki")]
Wiki,
[Description(@"Editor/add-control-point")]
EditorAddControlPoint = 1000,
[Description(@"Editor/convert-to-stream")]
EditorConvertToStream,
[Description(@"Editor/distance-snap")]
EditorDistanceSnap,
[Description(@"Editor/finish")]
EditorFinish,
[Description(@"Editor/grid-snap")]
EditorGridSnap,
[Description(@"Editor/new-combo-a")]
EditorNewComboA,
[Description(@"Editor/new-combo-b")]
EditorNewComboB,
[Description(@"Editor/select")]
EditorSelect,
[Description(@"Editor/sound")]
EditorSound,
[Description(@"Editor/whistle")]
EditorWhistle,
}
public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore
{
private readonly TextureStore textures;
public OsuIconStore(TextureStore textures)
{
this.textures = textures;
}
public ITexturedCharacterGlyph? Get(string? fontName, char character)
{
if (fontName == FONT_NAME)
return new Glyph(textures.Get($@"{fontName}/{((OsuIconMapping)character).GetDescription()}"));
return null;
}
public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public Texture Get(string name) => throw new NotImplementedException();
public Task<Texture> GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public Stream GetStream(string name) => throw new NotImplementedException();
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
public Task<Texture?> GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public class Glyph : ITexturedCharacterGlyph
{
public float XOffset => default;
public float YOffset => default;
public float XAdvance => default;
public float Baseline => default;
public char Character => default;
public float GetKerning<T>(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException();
public Texture Texture { get; }
public float Width => Texture.Width;
public float Height => Texture.Height;
public Glyph(Texture texture)
{
Texture = texture;
}
}
public void Dispose()
{
textures.Dispose();
}
}
#endregion
}
}

View File

@ -20,9 +20,14 @@ namespace osu.Game.Localisation
public static LocalisableString ViewBeatmap => new TranslatableString(getKey(@"view_beatmap"), @"View beatmap");
/// <summary>
/// "Invite player"
/// "Invite to room"
/// </summary>
public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite player");
public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite to room");
/// <summary>
/// "Spectate"
/// </summary>
public static LocalisableString SpectatePlayer => new TranslatableString(getKey(@"spectate_player"), @"Spectate");
private static string getKey(string key) => $@"{prefix}:{key}";
}

View File

@ -20,12 +20,12 @@ namespace osu.Game.Localisation
public static LocalisableString ChangelogDescription => new TranslatableString(getKey(@"changelog_description"), @"track recent dev updates in the osu! ecosystem");
/// <summary>
/// "view your friends and other information"
/// "view your friends and spectate other players"
/// </summary>
public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and other information");
public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and spectate other players");
/// <summary>
/// "find out who's the best right now"
/// "find out who&#39;s the best right now"
/// </summary>
public static LocalisableString RankingsDescription => new TranslatableString(getKey(@"rankings_description"), @"find out who's the best right now");
@ -39,6 +39,6 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString WikiDescription => new TranslatableString(getKey(@"wiki_description"), @"knowledge base");
private static string getKey(string key) => $"{prefix}:{key}";
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -24,6 +24,11 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString MenuCursorSize => new TranslatableString(getKey(@"menu_cursor_size"), @"Menu cursor size");
/// <summary>
/// "Menu tips"
/// </summary>
public static LocalisableString ShowMenuTips => new TranslatableString(getKey(@"show_menu_tips"), @"Menu tips");
/// <summary>
/// "Parallax"
/// </summary>
@ -154,6 +159,6 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString TrueRandom => new TranslatableString(getKey(@"true_random"), @"True Random");
private static string getKey(string key) => $"{prefix}:{key}";
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

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

@ -483,7 +483,7 @@ namespace osu.Game
AddFont(Resources, @"Fonts/Venera/Venera-Bold");
AddFont(Resources, @"Fonts/Venera/Venera-Black");
Fonts.AddStore(new HexaconsIcons.HexaconsStore(Textures));
Fonts.AddStore(new OsuIcon.OsuIconStore(Textures));
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>

View File

@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing
{
Title = PageTitleStrings.MainBeatmapsetsControllerIndex;
Description = NamedOverlayComponentStrings.BeatmapListingDescription;
Icon = HexaconsIcons.Beatmap;
Icon = OsuIcon.Beatmap;
}
}
}

View File

@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet
public BeatmapHeaderTitle()
{
Title = PageTitleStrings.MainBeatmapsetsControllerShow;
Icon = HexaconsIcons.Beatmap;
Icon = OsuIcon.Beatmap;
}
}
}

View File

@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Changelog
{
Title = PageTitleStrings.MainChangelogControllerDefault;
Description = NamedOverlayComponentStrings.ChangelogDescription;
Icon = HexaconsIcons.Devtools;
Icon = OsuIcon.ChangelogB;
}
}
}

View File

@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = HexaconsIcons.Messaging,
Icon = OsuIcon.Chat,
Size = new Vector2(24),
},
// Placeholder text

View File

@ -31,7 +31,7 @@ namespace osu.Game.Overlays
{
public partial class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler<PlatformAction>
{
public IconUsage Icon => HexaconsIcons.Messaging;
public IconUsage Icon => OsuIcon.Chat;
public LocalisableString Title => ChatStrings.HeaderTitle;
public LocalisableString Description => ChatStrings.HeaderDescription;

View File

@ -131,9 +131,6 @@ namespace osu.Game.Overlays.Dashboard
{
int userId = kvp.Key;
if (userId == api.LocalUser.Value.Id)
continue;
users.GetUserAsync(userId).ContinueWith(task =>
{
APIUser user = task.GetResultSafely();

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Dashboard
{
Title = PageTitleStrings.MainHomeControllerIndex;
Description = NamedOverlayComponentStrings.DashboardDescription;
Icon = HexaconsIcons.Social;
Icon = OsuIcon.Global;
}
}
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Mods.Input
{
[Key.Q] = new[] { typeof(ModEasy) },
[Key.W] = new[] { typeof(ModNoFail) },
[Key.E] = new[] { typeof(ModHalfTime) },
[Key.E] = new[] { typeof(ModHalfTime), typeof(ModDaycore) },
[Key.A] = new[] { typeof(ModHardRock) },
[Key.S] = new[] { typeof(ModSuddenDeath), typeof(ModPerfect) },
[Key.D] = new[] { typeof(ModDoubleTime), typeof(ModNightcore) },

View File

@ -69,7 +69,7 @@ namespace osu.Game.Overlays.News
{
Title = PageTitleStrings.MainNewsControllerDefault;
Description = NamedOverlayComponentStrings.NewsDescription;
Icon = HexaconsIcons.News;
Icon = OsuIcon.News;
}
}
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Overlays
{
public partial class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, INotificationOverlay
{
public IconUsage Icon => HexaconsIcons.Notification;
public IconUsage Icon => OsuIcon.Notification;
public LocalisableString Title => NotificationsStrings.HeaderTitle;
public LocalisableString Description => NotificationsStrings.HeaderDescription;

View File

@ -29,7 +29,7 @@ namespace osu.Game.Overlays
{
public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
{
public IconUsage Icon => HexaconsIcons.Music;
public IconUsage Icon => OsuIcon.Music;
public LocalisableString Title => NowPlayingStrings.HeaderTitle;
public LocalisableString Description => NowPlayingStrings.HeaderDescription;

View File

@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Profile
public ProfileHeaderTitle()
{
Title = PageTitleStrings.MainUsersControllerDefault;
Icon = HexaconsIcons.Profile;
Icon = OsuIcon.Player;
}
}
}

View File

@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Rankings
{
Title = PageTitleStrings.MainRankingControllerDefault;
Description = NamedOverlayComponentStrings.RankingsDescription;
Icon = HexaconsIcons.Rankings;
Icon = OsuIcon.Ranking;
}
}
}

View File

@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Localisation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Audio;
@ -17,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.VolumeUp
Icon = OsuIcon.Audio
};
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "sound" });

View File

@ -5,6 +5,7 @@ using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.DebugSettings;
@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Bug
Icon = OsuIcon.Debug
};
public DebugSection()

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Gameplay;
@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Regular.DotCircle
Icon = OsuIcon.GameplayC
};
public GameplaySection()

View File

@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Cog
Icon = OsuIcon.Settings
};
[BackgroundDependencyLoader]

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Graphics;
@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Laptop
Icon = OsuIcon.Graphics
};
public GraphicsSection()

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Handlers;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Input;
@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Keyboard
Icon = OsuIcon.Input
};
public InputSection(KeyBindingPanel keyConfig)

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Maintenance;
@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Wrench
Icon = OsuIcon.Maintenance
};
public MaintenanceSection()

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Online;
@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.GlobeAsia
Icon = OsuIcon.Online
};
public OnlineSection()

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Rulesets;
@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Chess
Icon = OsuIcon.Rulesets
};
[BackgroundDependencyLoader]

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays.SkinEditor;
@ -31,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.PaintBrush
Icon = OsuIcon.SkinB
};
private static readonly Live<SkinInfo> random_skin_info = new SkinInfo

View File

@ -29,6 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ShowMenuTips,
Current = config.GetBindable<bool>(OsuSetting.MenuTips)
},
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.InterfaceVoices,

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.UserInterface;
@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.LayerGroup
Icon = OsuIcon.UserInterface
};
public UserInterfaceSection()

View File

@ -23,7 +23,7 @@ namespace osu.Game.Overlays
{
public partial class SettingsOverlay : SettingsPanel, INamedOverlayComponent
{
public IconUsage Icon => HexaconsIcons.Settings;
public IconUsage Icon => OsuIcon.Settings;
public LocalisableString Title => SettingsStrings.HeaderTitle;
public LocalisableString Description => SettingsStrings.HeaderDescription;

View File

@ -164,11 +164,11 @@ namespace osu.Game.Overlays.Toolbar
{
new ToolbarNewsButton(),
new ToolbarChangelogButton(),
new ToolbarWikiButton(),
new ToolbarRankingsButton(),
new ToolbarBeatmapListingButton(),
new ToolbarChatButton(),
new ToolbarSocialButton(),
new ToolbarWikiButton(),
new ToolbarMusicButton(),
//new ToolbarButton
//{
@ -224,9 +224,9 @@ namespace osu.Game.Overlays.Toolbar
RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Alpha = 0,
Height = 100,
Height = 80,
Colour = ColourInfo.GradientVertical(
OsuColour.Gray(0).Opacity(0.9f), OsuColour.Gray(0).Opacity(0)),
OsuColour.Gray(0f).Opacity(0.7f), OsuColour.Gray(0).Opacity(0)),
},
};
}
@ -241,9 +241,9 @@ namespace osu.Game.Overlays.Toolbar
private void updateState()
{
if (ShowGradient.Value)
gradientBackground.FadeIn(transition_time, Easing.OutQuint);
gradientBackground.FadeIn(2500, Easing.OutQuint);
else
gradientBackground.FadeOut(transition_time, Easing.OutQuint);
gradientBackground.FadeOut(200, Easing.OutQuint);
}
}

View File

@ -7,7 +7,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
@ -74,6 +73,8 @@ namespace osu.Game.Overlays.Toolbar
private readonly SpriteText keyBindingTooltip;
protected FillFlowContainer Flow;
protected readonly Container BackgroundContent;
[Resolved]
private RealmAccess realm { get; set; } = null!;
@ -82,21 +83,33 @@ namespace osu.Game.Overlays.Toolbar
Width = Toolbar.HEIGHT;
RelativeSizeAxes = Axes.Y;
Padding = new MarginPadding(3);
Children = new Drawable[]
{
HoverBackground = new Box
BackgroundContent = new Container
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(80).Opacity(180),
Blending = BlendingParameters.Additive,
Alpha = 0,
},
flashBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Colour = Color4.White.Opacity(100),
Blending = BlendingParameters.Additive,
Masking = true,
CornerRadius = 6,
CornerExponent = 3f,
Children = new Drawable[]
{
HoverBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(80).Opacity(180),
Blending = BlendingParameters.Additive,
Alpha = 0,
},
flashBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Colour = Color4.White.Opacity(100),
Blending = BlendingParameters.Additive,
},
}
},
Flow = new FillFlowContainer
{
@ -113,7 +126,7 @@ namespace osu.Game.Overlays.Toolbar
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(26),
Size = new Vector2(20),
Alpha = 0,
},
DrawableText = new OsuSpriteText
@ -170,7 +183,7 @@ namespace osu.Game.Overlays.Toolbar
protected override bool OnClick(ClickEvent e)
{
flashBackground.FadeOutFromOne(800, Easing.OutQuint);
flashBackground.FadeIn(50).Then().FadeOutFromOne(800, Easing.OutQuint);
tooltipContainer.FadeOut(100);
return base.OnClick(e);
}
@ -219,14 +232,6 @@ namespace osu.Game.Overlays.Toolbar
public OpaqueBackground()
{
RelativeSizeAxes = Axes.Both;
Masking = true;
MaskingSmoothness = 0;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
};
Children = new Drawable[]
{

View File

@ -42,21 +42,33 @@ namespace osu.Game.Overlays.Toolbar
clockDisplayMode = config.GetBindable<ToolbarClockDisplayMode>(OsuSetting.ToolbarClockDisplayMode);
prefer24HourTime = config.GetBindable<bool>(OsuSetting.Prefer24HourTime);
Padding = new MarginPadding(3);
Children = new Drawable[]
{
hoverBackground = new Box
new Container
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(80).Opacity(180),
Blending = BlendingParameters.Additive,
Alpha = 0,
},
flashBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Colour = Color4.White.Opacity(100),
Blending = BlendingParameters.Additive,
Masking = true,
CornerRadius = 6,
CornerExponent = 3f,
Children = new Drawable[]
{
hoverBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(80).Opacity(180),
Blending = BlendingParameters.Additive,
Alpha = 0,
},
flashBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Colour = Color4.White.Opacity(100),
Blending = BlendingParameters.Additive,
},
}
},
new FillFlowContainer
{

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar
{
TooltipMain = ToolbarStrings.HomeHeaderTitle;
TooltipSub = ToolbarStrings.HomeHeaderDescription;
SetIcon(HexaconsIcons.Home);
SetIcon(OsuIcon.Home);
}
}
}

View File

@ -3,6 +3,7 @@
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Toolbar
{
public partial class ToolbarOverlayToggleButton : ToolbarButton
{
private readonly Box stateBackground;
private Box stateBackground;
private OverlayContainer stateContainer;
@ -44,14 +45,15 @@ namespace osu.Game.Overlays.Toolbar
}
}
public ToolbarOverlayToggleButton()
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Add(stateBackground = new Box
BackgroundContent.Add(stateBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(150).Opacity(180),
Colour = colours.Carmine.Opacity(180),
Blending = BlendingParameters.Additive,
Depth = 2,
Depth = float.MaxValue,
Alpha = 0,
});
@ -63,11 +65,11 @@ namespace osu.Game.Overlays.Toolbar
switch (state.NewValue)
{
case Visibility.Hidden:
stateBackground.FadeOut(200);
stateBackground.FadeOut(200, Easing.OutQuint);
break;
case Visibility.Visible:
stateBackground.FadeIn(200);
stateBackground.FadeIn(200, Easing.OutQuint);
break;
}
}

View File

@ -4,20 +4,19 @@
#nullable disable
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osuTK.Input;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Overlays.Toolbar
{
@ -41,25 +40,24 @@ namespace osu.Game.Overlays.Toolbar
new OpaqueBackground
{
Depth = 1,
Masking = true,
},
ModeButtonLine = new Container
{
Size = new Vector2(Toolbar.HEIGHT, 3),
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
Masking = true,
EdgeEffect = new EdgeEffectParameters
Origin = Anchor.BottomLeft,
Y = -1,
Children = new Drawable[]
{
Type = EdgeEffectType.Glow,
Colour = new Color4(255, 194, 224, 100),
Radius = 15,
Roundness = 15,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(18, 3),
}
}
}
},
});
foreach (var ruleset in Rulesets.AvailableRulesets)
@ -89,7 +87,7 @@ namespace osu.Game.Overlays.Toolbar
{
if (SelectedTab != null)
{
ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint);
ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 500, Easing.OutElasticQuarter);
if (hasInitialPosition)
selectionSamples[SelectedTab.Value.ShortName]?.Play();

View File

@ -1,14 +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 osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Rulesets;
using osuTK.Graphics;
namespace osu.Game.Overlays.Toolbar
{
@ -41,27 +43,31 @@ namespace osu.Game.Overlays.Toolbar
{
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
[Resolved]
private OsuColour colours { get; set; } = null!;
public RulesetButton()
{
Padding = new MarginPadding(3)
{
Bottom = 5
};
}
public bool Active
{
set
set => Scheduler.AddOnce(() =>
{
if (value)
{
IconContainer.Colour = Color4.White;
IconContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = new Color4(255, 194, 224, 100),
Radius = 15,
Roundness = 15,
};
IconContainer.Colour = Color4Extensions.FromHex("#00FFAA");
}
else
{
IconContainer.Colour = new Color4(255, 194, 224, 255);
IconContainer.Colour = colours.GrayF;
IconContainer.EdgeEffect = new EdgeEffectParameters();
}
}
});
}
protected override bool OnClick(ClickEvent e)

View File

@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Toolbar
[BackgroundDependencyLoader]
private void load(OsuColour colours, IAPIProvider api, LoginOverlay? login)
{
Add(new OpaqueBackground { Depth = 1 });
BackgroundContent.Add(new OpaqueBackground { Depth = 1 });
Flow.Add(new Container
{

View File

@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Wiki
{
Title = PageTitleStrings.MainWikiControllerDefault;
Description = NamedOverlayComponentStrings.WikiDescription;
Icon = HexaconsIcons.Wiki;
Icon = OsuIcon.Wiki;
}
}
}

View File

@ -16,6 +16,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
@ -169,7 +170,7 @@ namespace osu.Game.Rulesets.Edit
public IEnumerable<TernaryButton> CreateTernaryButtons() => new[]
{
new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap })
};
protected override bool OnKeyDown(KeyDownEvent e)

View File

@ -5,6 +5,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Edit.Tools
{
@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Edit.Tools
{
}
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.MousePointer };
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect };
public override PlacementBlueprint CreatePlacementBlueprint() => null;
}

View File

@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Components.Menus
Size = new Vector2(26),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = HexaconsIcons.Editor,
Icon = OsuIcon.EditCircle,
},
text = new TextFlowContainer
{

View File

@ -225,7 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected virtual IEnumerable<TernaryButton> CreateTernaryButtons()
{
//TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects.
yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle });
yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = OsuIcon.EditorNewComboA });
foreach (var kvp in SelectionHandler.SelectionSampleStates)
yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => getIconForSample(kvp.Key));
@ -272,10 +272,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
return new SpriteIcon { Icon = FontAwesome.Solid.Hands };
case HitSampleInfo.HIT_WHISTLE:
return new SpriteIcon { Icon = FontAwesome.Solid.Bullhorn };
return new SpriteIcon { Icon = OsuIcon.EditorWhistle };
case HitSampleInfo.HIT_FINISH:
return new SpriteIcon { Icon = FontAwesome.Solid.DrumSteelpan };
return new SpriteIcon { Icon = OsuIcon.EditorFinish };
}
return null;

View File

@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Setup
{
Title = EditorSetupStrings.BeatmapSetup.ToLower();
Description = EditorSetupStrings.BeatmapSetupDescription;
Icon = HexaconsIcons.Social;
Icon = OsuIcon.Beatmap;
}
}

View File

@ -21,8 +21,6 @@ namespace osu.Game.Screens
{
public partial class Loader : StartupScreen
{
private bool showDisclaimer;
public Loader()
{
ValidForResume = false;
@ -35,13 +33,7 @@ namespace osu.Game.Screens
private LoadingSpinner spinner;
private ScheduledDelegate spinnerShow;
protected virtual OsuScreen CreateLoadableScreen()
{
if (showDisclaimer)
return new Disclaimer(getIntroSequence());
return getIntroSequence();
}
protected virtual OsuScreen CreateLoadableScreen() => getIntroSequence();
private IntroScreen getIntroSequence()
{
@ -107,9 +99,8 @@ namespace osu.Game.Screens
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game, OsuConfigManager config)
private void load(OsuConfigManager config)
{
showDisclaimer = game.IsDeployedBuild;
introSequence = config.Get<IntroSequence>(OsuSetting.IntroSequence);
}

View File

@ -13,7 +13,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
@ -103,8 +102,8 @@ namespace osu.Game.Screens.Menu
buttonArea.AddRange(new Drawable[]
{
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
-WEDGE_WIDTH)
{
VisibleStateMin = ButtonSystemState.Play,
@ -128,18 +127,18 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host)
{
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", HexaconsIcons.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B));
buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", HexaconsIcons.Editor, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S));
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B));
buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S));
buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit);
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
if (host.CanExit)
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));

View File

@ -1,257 +0,0 @@
// 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.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Menu
{
public partial class Disclaimer : StartupScreen
{
private SpriteIcon icon;
private Color4 iconColour;
private LinkFlowContainer textFlow;
private LinkFlowContainer supportFlow;
private Drawable heart;
private const float icon_y = -85;
private const float icon_size = 30;
private readonly OsuScreen nextScreen;
private readonly Bindable<APIUser> currentUser = new Bindable<APIUser>();
private FillFlowContainer fill;
private readonly List<ITextPart> expendableText = new List<ITextPart>();
public Disclaimer(OsuScreen nextScreen = null)
{
this.nextScreen = nextScreen;
ValidForResume = false;
}
[Resolved]
private IAPIProvider api { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChildren = new Drawable[]
{
icon = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = OsuIcon.Logo,
Size = new Vector2(icon_size),
Y = icon_y,
},
fill = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Y = icon_y,
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Children = new Drawable[]
{
textFlow = new LinkFlowContainer
{
Width = 680,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Spacing = new Vector2(0, 2),
},
}
},
supportFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Padding = new MarginPadding(20),
Alpha = 0,
Spacing = new Vector2(0, 2),
},
};
textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular));
expendableText.Add(textFlow.AddText("lazer", t =>
{
t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular);
t.Colour = colours.PinkLight;
}));
static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular);
static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold);
textFlow.NewParagraph();
textFlow.AddText("the next ", formatRegular);
textFlow.AddText("major update", t =>
{
t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold);
t.Colour = colours.Pink;
});
expendableText.Add(textFlow.AddText(" coming to osu!", formatRegular));
textFlow.AddText(".", formatRegular);
textFlow.NewParagraph();
textFlow.NewParagraph();
textFlow.AddParagraph("today's tip:", formatSemiBold);
textFlow.AddParagraph(getRandomTip(), formatRegular);
textFlow.NewParagraph();
textFlow.NewParagraph();
iconColour = colours.Yellow;
// manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading).
// the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters.
currentUser.Value = api.LocalUser.Value;
currentUser.BindValueChanged(e =>
{
supportFlow.Children.ForEach(d => d.FadeOut().Expire());
if (e.NewValue.IsSupporter)
{
supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold);
}
else
{
supportFlow.AddText("Consider becoming an ", formatSemiBold);
supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold);
supportFlow.AddText(" to help support osu!'s development", formatSemiBold);
}
supportFlow.AddIcon(FontAwesome.Solid.Heart, t =>
{
heart = t;
t.Padding = new MarginPadding { Left = 5, Top = 3 };
t.Font = t.Font.With(size: 20);
t.Origin = Anchor.Centre;
t.Colour = colours.Pink;
Schedule(() => heart?.FlashColour(Color4.White, 750, Easing.OutQuint).Loop());
});
if (supportFlow.IsPresent)
supportFlow.FadeInFromZero(500);
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
if (nextScreen != null)
LoadComponentAsync(nextScreen);
((IBindable<APIUser>)currentUser).BindTo(api.LocalUser);
}
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(e);
// Once this screen has finished being displayed, we don't want to unnecessarily handle user change events.
currentUser.UnbindAll();
}
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(e);
icon.RotateTo(10);
icon.FadeOut();
icon.ScaleTo(0.5f);
icon.Delay(500).FadeIn(500).ScaleTo(1, 500, Easing.OutQuint);
using (BeginDelayedSequence(3000))
{
icon.FadeColour(iconColour, 200, Easing.OutQuint);
icon.MoveToY(icon_y * 1.3f, 500, Easing.OutCirc)
.RotateTo(-360, 520, Easing.OutQuint)
.Then()
.MoveToY(icon_y, 160, Easing.InQuart)
.FadeColour(Color4.White, 160);
using (BeginDelayedSequence(520 + 160))
{
fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart);
Schedule(() => expendableText.SelectMany(t => t.Drawables).ForEach(t =>
{
t.FadeOut(100);
t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart);
}));
}
}
supportFlow.FadeOut().Delay(2000).FadeIn(500);
double delay = 500;
foreach (var c in textFlow.Children)
c.FadeTo(0.001f).Delay(delay += 20).FadeIn(500);
this
.FadeInFromZero(500)
.Then(5500)
.FadeOut(250)
.ScaleTo(0.9f, 250, Easing.InQuint)
.Finally(_ =>
{
if (nextScreen != null)
this.Push(nextScreen);
});
}
private string getRandomTip()
{
string[] tips =
{
"You can press Ctrl-T anywhere in the game to toggle the toolbar!",
"You can press Ctrl-O anywhere in the game to access options!",
"All settings are dynamic and take effect in real-time. Try pausing and changing the skin while playing!",
"New features are coming online every update. Make sure to stay up-to-date!",
"If you find the UI too large or small, try adjusting UI scale in settings!",
"Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",
"What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!",
"Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!",
"Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!",
"Try scrolling down in the mod select panel to find a bunch of new fun mods!",
"Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!",
"Get more details, hide or delete a beatmap by right-clicking on its panel at song select!",
"All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!",
"Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!",
"Toggle advanced frame / thread statistics with Ctrl-F11!",
"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!",
};
return tips[RNG.Next(0, tips.Length)];
}
}
}

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,9 @@ namespace osu.Game.Screens.Menu
private ParallaxContainer buttonsContainer;
private SongTicker songTicker;
private Container logoTarget;
private MenuTip menuTip;
private FillFlowContainer bottomElementsFlow;
private SupporterDisplay supporterDisplay;
private Sample reappearSampleSwoosh;
@ -153,6 +159,33 @@ namespace osu.Game.Screens.Menu
Margin = new MarginPadding { Right = 15, Top = 5 }
},
new KiaiMenuFountains(),
bottomElementsFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Spacing = new Vector2(5),
Children = new Drawable[]
{
menuTip = new MenuTip
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
new SystemTitle
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}
}
},
supporterDisplay = new SupporterDisplay
{
Margin = new MarginPadding(5),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
holdToExitGameOverlay?.CreateProxy() ?? Empty()
});
@ -263,6 +296,16 @@ namespace osu.Game.Screens.Menu
}
}
protected override void Update()
{
base.Update();
bottomElementsFlow.Margin = new MarginPadding
{
Bottom = (versionManager?.DrawHeight + 5) ?? 0
};
}
protected override void LogoSuspending(OsuLogo logo)
{
var seq = logo.FadeOut(300, Easing.InSine)
@ -299,6 +342,13 @@ namespace osu.Game.Screens.Menu
buttonsContainer.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine);
sideFlashes.FadeOut(64, Easing.OutQuint);
bottomElementsFlow
.ScaleTo(0.9f, 1000, Easing.OutQuint)
.FadeOut(500, Easing.OutQuint);
supporterDisplay
.FadeOut(500, Easing.OutQuint);
}
public override void OnResuming(ScreenTransitionEvent e)
@ -315,6 +365,13 @@ namespace osu.Game.Screens.Menu
preloadSongSelect();
musicController.EnsurePlayingSomething();
// Cycle tip on resuming
menuTip.ShowNextTip();
bottomElementsFlow
.ScaleTo(1, 1000, Easing.OutQuint)
.FadeIn(1000, Easing.OutQuint);
}
public override bool OnExiting(ScreenExitEvent e)
@ -352,6 +409,13 @@ namespace osu.Game.Screens.Menu
songTicker.Hide();
this.FadeOut(3000);
bottomElementsFlow
.FadeOut(500, Easing.OutQuint);
supporterDisplay
.FadeOut(500, Easing.OutQuint);
return base.OnExiting(e);
}

View File

@ -0,0 +1,118 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Menu
{
public partial class MenuTip : CompositeDrawable
{
[Resolved]
private OsuConfigManager config { get; set; } = null!;
private LinkFlowContainer textFlow = null!;
private Bindable<bool> showMenuTips = null!;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerExponent = 2.5f,
CornerRadius = 15,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
Alpha = 0.4f,
},
}
},
textFlow = new LinkFlowContainer
{
Width = 600,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Spacing = new Vector2(0, 2),
Margin = new MarginPadding(10)
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
showMenuTips = config.GetBindable<bool>(OsuSetting.MenuTips);
showMenuTips.BindValueChanged(_ => ShowNextTip(), true);
}
public void ShowNextTip()
{
if (!showMenuTips.Value)
{
this.FadeOut(100, Easing.OutQuint);
return;
}
static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular);
static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold);
string tip = getRandomTip();
textFlow.Clear();
textFlow.AddParagraph("a tip for you:", formatSemiBold);
textFlow.AddParagraph(tip, formatRegular);
this.FadeInFromZero(200, Easing.OutQuint)
.Delay(1000 + 80 * tip.Length)
.Then()
.FadeOutFromOne(2000, Easing.OutQuint);
}
private string getRandomTip()
{
string[] tips =
{
"You can press Ctrl-T anywhere in the game to toggle the toolbar!",
"You can press Ctrl-O anywhere in the game to access options!",
"All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!",
"New features are coming online every update. Make sure to stay up-to-date!",
"If you find the UI too large or small, try adjusting UI scale in settings!",
"Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",
"What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!",
"Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!",
"Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!",
"Try scrolling down in the mod select panel to find a bunch of new fun mods!",
"Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!",
"Get more details, hide or delete a beatmap by right-clicking on its panel at song select!",
"All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!",
"Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!",
"Toggle advanced frame / thread statistics with Ctrl-F11!",
"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!",
};
return tips[RNG.Next(0, tips.Length)];
}
}
}

View File

@ -0,0 +1,167 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Menu
{
public partial class SupporterDisplay : CompositeDrawable
{
private LinkFlowContainer supportFlow = null!;
private Drawable heart = null!;
private readonly IBindable<APIUser> currentUser = new Bindable<APIUser>();
private Box backgroundBox = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
Height = 40;
AutoSizeAxes = Axes.X;
AutoSizeDuration = 1000;
AutoSizeEasing = Easing.OutQuint;
Masking = true;
CornerExponent = 2.5f;
CornerRadius = 15;
InternalChildren = new Drawable[]
{
backgroundBox = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.4f,
},
supportFlow = new LinkFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
Spacing = new Vector2(0, 2),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
const float font_size = 14;
static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: font_size, weight: FontWeight.SemiBold);
currentUser.BindTo(api.LocalUser);
currentUser.BindValueChanged(e =>
{
supportFlow.Children.ForEach(d => d.FadeOut().Expire());
if (e.NewValue.IsSupporter)
{
supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold);
backgroundBox.FadeColour(colours.Pink, 250);
}
else
{
supportFlow.AddText("Consider becoming an ", formatSemiBold);
supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold);
supportFlow.AddText(" to help support osu!'s development", formatSemiBold);
backgroundBox.FadeColour(colours.Pink4, 250);
}
supportFlow.AddIcon(FontAwesome.Solid.Heart, t =>
{
heart = t;
t.Padding = new MarginPadding { Left = 5, Top = 1 };
t.Font = t.Font.With(size: font_size);
t.Origin = Anchor.Centre;
t.Colour = colours.Pink;
Schedule(() =>
{
heart?.FlashColour(Color4.White, 750, Easing.OutQuint).Loop();
});
});
}, true);
this
.FadeOut()
.Delay(1000)
.FadeInFromZero(800, Easing.OutQuint);
scheduleDismissal();
}
protected override bool OnClick(ClickEvent e)
{
dismissalDelegate?.Cancel();
supportFlow.BypassAutoSizeAxes = Axes.X;
this.FadeOut(500, Easing.OutQuint);
return base.OnClick(e);
}
protected override bool OnHover(HoverEvent e)
{
backgroundBox.FadeTo(0.6f, 500, Easing.OutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
backgroundBox.FadeTo(0.4f, 500, Easing.OutQuint);
base.OnHoverLost(e);
}
private ScheduledDelegate? dismissalDelegate;
private void scheduleDismissal()
{
dismissalDelegate?.Cancel();
dismissalDelegate = Scheduler.AddDelayed(() =>
{
// If the user is hovering they may want to interact with the link.
// Give them more time.
if (IsHovered)
{
scheduleDismissal();
return;
}
dismissalDelegate?.Cancel();
AutoSizeEasing = Easing.In;
supportFlow.BypassAutoSizeAxes = Axes.X;
this
.Delay(200)
.FadeOut(750, Easing.Out);
}, 6000);
}
}
}

View File

@ -0,0 +1,176 @@
// 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)
{
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;
}
}
}
}

View File

@ -44,7 +44,15 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Action that is invoked when <see cref="GlobalAction.Back"/> is triggered.
/// </summary>
protected virtual Action BackAction => () => InternalButtons.LastOrDefault()?.TriggerClick();
protected virtual Action BackAction => () =>
{
// We prefer triggering the button click as it will animate...
// but sometimes buttons aren't present (see FailOverlay's constructor as an example).
if (Buttons.Any())
Buttons.Last().TriggerClick();
else
OnQuit?.Invoke();
};
/// <summary>
/// Action that is invoked when <see cref="GlobalAction.Select"/> is triggered.

View File

@ -29,7 +29,13 @@ namespace osu.Game.Screens.Play
private SkinnableSound pauseLoop;
protected override Action BackAction => () => InternalButtons.First().TriggerClick();
protected override Action BackAction => () =>
{
if (Buttons.Any())
Buttons.First().TriggerClick();
else
OnResume?.Invoke();
};
[BackgroundDependencyLoader]
private void load(OsuColour colours)

View File

@ -12,12 +12,12 @@ namespace osu.Game.Screens.Select.Leaderboards
[Description("Local Ranking")]
Local,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardCountry))]
Country,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardGlobal))]
Global,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardCountry))]
Country,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardFriend))]
Friend,
}

View File

@ -80,8 +80,8 @@ namespace osu.Game.Screens.Select
protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Concat(new BeatmapDetailAreaTabItem[]
{
new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local),
new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Country),
new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Global),
new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Country),
new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Friend),
}).ToArray();
@ -95,12 +95,12 @@ namespace osu.Game.Screens.Select
case TabType.Local:
return new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local);
case TabType.Country:
return new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Country);
case TabType.Global:
return new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Global);
case TabType.Country:
return new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Country);
case TabType.Friends:
return new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Friend);

View File

@ -13,6 +13,7 @@ using osu.Game.Overlays;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@ -20,6 +21,8 @@ using osu.Game.Online.Chat;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Localisation;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens;
using osu.Game.Screens.Play;
namespace osu.Game.Users
{
@ -60,6 +63,9 @@ namespace osu.Game.Users
[Resolved]
protected OverlayColourProvider? ColourProvider { get; private set; }
[Resolved]
private IPerformFromScreenRunner? performer { get; set; }
[Resolved]
protected OsuColour Colours { get; private set; } = null!;
@ -113,23 +119,26 @@ namespace osu.Game.Users
new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, ViewProfile)
};
if (!User.Equals(api.LocalUser.Value))
{
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () =>
{
channelManager?.OpenPrivateChannel(User);
chatOverlay?.Show();
}));
}
if (User.Equals(api.LocalUser.Value))
return items.ToArray();
if (
// TODO: uncomment this once lazer / osu-web is updating online states
// User.IsOnline &&
multiplayerClient?.Room != null &&
multiplayerClient.Room.Users.All(u => u.UserID != User.Id)
)
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () =>
{
items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id)));
channelManager?.OpenPrivateChannel(User);
chatOverlay?.Show();
}));
if (User.IsOnline)
{
items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () =>
{
performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User)));
}));
if (multiplayerClient?.Room?.Users.All(u => u.UserID != User.Id) == true)
{
items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id)));
}
}
return items.ToArray();

View File

@ -37,7 +37,7 @@
</PackageReference>
<PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2023.1227.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1225.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1228.0" />
<PackageReference Include="Sentry" Version="3.40.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.33.0" />