diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index e24fe1a1ec..fe3e08537e 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -169,7 +169,7 @@ namespace osu.Desktop case UserActivity.InGame game: return game.BeatmapInfo; - case UserActivity.Editing edit: + case UserActivity.EditingBeatmap edit: return edit.BeatmapInfo; } @@ -183,10 +183,10 @@ namespace osu.Desktop case UserActivity.InGame game: return game.BeatmapInfo.ToString() ?? string.Empty; - case UserActivity.Editing edit: + case UserActivity.EditingBeatmap edit: return edit.BeatmapInfo.ToString() ?? string.Empty; - case UserActivity.Watching watching: + case UserActivity.WatchingReplay watching: return watching.BeatmapInfo.ToString(); case UserActivity.InLobby lobby: diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs new file mode 100644 index 0000000000..ce93837925 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Screens.Play.Break; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneLetterboxOverlay : OsuTestScene + { + public TestSceneLetterboxOverlay() + { + AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both + }, + new LetterboxOverlay() + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 4675410164..10c2b2b9e1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEditActivity() { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.Editing(new BeatmapInfo())); + AddStep("Set activity", () => api.Activity.Value = new UserActivity.EditingBeatmap(new BeatmapInfo())); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 64a42ee6ce..a047e2f0c5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -109,15 +109,16 @@ namespace osu.Game.Tests.Visual.Online AddStep("set online status", () => status.Value = new UserStatusOnline()); AddStep("idle", () => activity.Value = null); - AddStep("watching", () => activity.Value = new UserActivity.Watching(createScore(@"nats"))); - AddStep("spectating", () => activity.Value = new UserActivity.Spectating(createScore(@"mrekk"))); + AddStep("watching replay", () => activity.Value = new UserActivity.WatchingReplay(createScore(@"nats"))); + AddStep("spectating user", () => activity.Value = new UserActivity.SpectatingUser(createScore(@"mrekk"))); AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0)); AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1)); AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2)); AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3)); AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap()); - AddStep("editing", () => activity.Value = new UserActivity.Editing(null)); - AddStep("modding", () => activity.Value = new UserActivity.Modding()); + AddStep("editing beatmap", () => activity.Value = new UserActivity.EditingBeatmap(null)); + AddStep("modding beatmap", () => activity.Value = new UserActivity.ModdingBeatmap(null)); + AddStep("testing beatmap", () => activity.Value = new UserActivity.TestingBeatmap(null, null)); } [Test] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs new file mode 100644 index 0000000000..72adbfc104 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs @@ -0,0 +1,146 @@ +// Copyright (c) ppy Pty Ltd . 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Screens.Select.FooterV2; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public partial class TestSceneSongSelectFooterV2 : OsuManualInputManagerTestScene + { + private FooterButtonRandomV2 randomButton = null!; + private FooterButtonModsV2 modsButton = null!; + + private bool nextRandomCalled; + private bool previousRandomCalled; + + private DummyOverlay overlay = null!; + + [Cached] + private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + [SetUp] + public void SetUp() => Schedule(() => + { + nextRandomCalled = false; + previousRandomCalled = false; + + FooterV2 footer; + + Children = new Drawable[] + { + footer = new FooterV2 + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + overlay = new DummyOverlay() + }; + + footer.AddButton(modsButton = new FooterButtonModsV2(), overlay); + footer.AddButton(randomButton = new FooterButtonRandomV2 + { + NextRandom = () => nextRandomCalled = true, + PreviousRandom = () => previousRandomCalled = true + }); + footer.AddButton(new FooterButtonOptionsV2()); + + overlay.Hide(); + }); + + [Test] + public void TestState() + { + AddToggleStep("set options enabled state", state => this.ChildrenOfType().Last().Enabled.Value = state); + } + + [Test] + public void TestFooterRandom() + { + AddStep("press F2", () => InputManager.Key(Key.F2)); + AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled); + } + + [Test] + public void TestFooterRandomViaMouse() + { + AddStep("click button", () => + { + InputManager.MoveMouseTo(randomButton); + InputManager.Click(MouseButton.Left); + }); + AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled); + } + + [Test] + public void TestFooterRewind() + { + AddStep("press Shift+F2", () => + { + InputManager.PressKey(Key.LShift); + InputManager.PressKey(Key.F2); + InputManager.ReleaseKey(Key.F2); + InputManager.ReleaseKey(Key.LShift); + }); + AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled); + } + + [Test] + public void TestFooterRewindViaShiftMouseLeft() + { + AddStep("shift + click button", () => + { + InputManager.PressKey(Key.LShift); + InputManager.MoveMouseTo(randomButton); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.LShift); + }); + AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled); + } + + [Test] + public void TestFooterRewindViaMouseRight() + { + AddStep("right click button", () => + { + InputManager.MoveMouseTo(randomButton); + InputManager.Click(MouseButton.Right); + }); + AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled); + } + + [Test] + public void TestOverlayPresent() + { + AddStep("Press F1", () => + { + InputManager.MoveMouseTo(modsButton); + InputManager.Click(MouseButton.Left); + }); + AddAssert("Overlay visible", () => overlay.State.Value == Visibility.Visible); + AddStep("Hide", () => overlay.Hide()); + } + + private partial class DummyOverlay : ShearedOverlayContainer + { + public DummyOverlay() + : base(OverlayColourScheme.Green) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Header.Title = "An overlay"; + } + } + } +} diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index cbe327bac7..9f21512825 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -100,9 +100,9 @@ namespace osu.Game.Graphics.Containers /// /// Abort any ongoing confirmation. Should be called when the container's interaction is no longer valid (ie. the user releases a key). /// - protected void AbortConfirm() + protected virtual void AbortConfirm() { - if (!AllowMultipleFires && Fired) return; + if (!confirming || (!AllowMultipleFires && Fired)) return; confirming = false; Fired = false; diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 6fe1de2216..fceee90d06 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -46,8 +46,8 @@ namespace osu.Game.Graphics.Containers AddRangeInternal(new Drawable[] { + CreateHoverSounds(sampleSet), content, - CreateHoverSounds(sampleSet) }); } diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 9902704883..e7018d6993 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Chat beatmapInfo = game.BeatmapInfo; break; - case UserActivity.Editing edit: + case UserActivity.EditingBeatmap edit: verb = "editing"; beatmapInfo = edit.BeatmapInfo; break; diff --git a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs index 6b3716ac8d..19d7ea7a87 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs @@ -57,6 +57,7 @@ namespace osu.Game.Overlays.Dialog private Sample confirmSample; private double lastTickPlaybackTime; private AudioFilter lowPassFilter = null!; + private bool mouseDown; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -73,6 +74,12 @@ namespace osu.Game.Overlays.Dialog Progress.BindValueChanged(progressChanged); } + protected override void AbortConfirm() + { + lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); + base.AbortConfirm(); + } + protected override void Confirm() { lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); @@ -83,6 +90,7 @@ namespace osu.Game.Overlays.Dialog protected override bool OnMouseDown(MouseDownEvent e) { BeginConfirm(); + mouseDown = true; return true; } @@ -90,11 +98,28 @@ namespace osu.Game.Overlays.Dialog { if (!e.HasAnyButtonPressed) { - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); AbortConfirm(); + mouseDown = false; } } + protected override bool OnHover(HoverEvent e) + { + if (mouseDown) + BeginConfirm(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (!mouseDown) return; + + AbortConfirm(); + } + private void progressChanged(ValueChangedEvent progress) { if (progress.NewValue < progress.OldValue) return; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0622cbebae..bd133383d1 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -157,7 +157,16 @@ namespace osu.Game.Screens.Edit private bool isNewBeatmap; - protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); + protected override UserActivity InitialActivity + { + get + { + if (Beatmap.Value.Metadata.Author.OnlineID == api.LocalUser.Value.OnlineID) + return new UserActivity.EditingBeatmap(Beatmap.Value.BeatmapInfo); + + return new UserActivity.ModdingBeatmap(Beatmap.Value.BeatmapInfo); + } + } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index e7db1c105b..7dff05667d 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -7,6 +7,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Screens.Play; +using osu.Game.Users; namespace osu.Game.Screens.Edit.GameplayTest { @@ -15,6 +16,8 @@ namespace osu.Game.Screens.Edit.GameplayTest private readonly Editor editor; private readonly EditorState editorState; + protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo, Ruleset.Value); + [Resolved] private MusicController musicController { get; set; } = null!; diff --git a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs index 92b432831d..c4e2dbf403 100644 --- a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -22,29 +20,21 @@ namespace osu.Game.Screens.Play.Break RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { - new Container + new Box { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, RelativeSizeAxes = Axes.X, Height = height, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black, transparent_black), - } + Colour = ColourInfo.GradientVertical(Color4.Black, transparent_black), }, - new Container + new Box { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, Height = height, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(transparent_black, Color4.Black), - } + Colour = ColourInfo.GradientVertical(transparent_black, Color4.Black), } }; } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 9e87969687..8a4e63d21c 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play private readonly bool replayIsFailedScore; - protected override UserActivity InitialActivity => new UserActivity.Watching(Score.ScoreInfo); + protected override UserActivity InitialActivity => new UserActivity.WatchingReplay(Score.ScoreInfo); // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) protected override bool CheckModsAllowFailure() diff --git a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs index 8a9cda2af7..c9d1f4acaa 100644 --- a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play { private readonly Score score; - protected override UserActivity InitialActivity => new UserActivity.Spectating(Score.ScoreInfo); + protected override UserActivity InitialActivity => new UserActivity.SpectatingUser(Score.ScoreInfo); public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null) : base(score, configuration) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs new file mode 100644 index 0000000000..b8c9f0b34b --- /dev/null +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Select.FooterV2 +{ + public partial class FooterButtonModsV2 : FooterButtonV2 + { + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + Text = "Mods"; + Icon = FontAwesome.Solid.ExchangeAlt; + AccentColour = colour.Lime1; + } + } +} diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs new file mode 100644 index 0000000000..87cca0042a --- /dev/null +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Input.Bindings; + +namespace osu.Game.Screens.Select.FooterV2 +{ + public partial class FooterButtonOptionsV2 : FooterButtonV2 + { + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + Text = "Options"; + Icon = FontAwesome.Solid.Cog; + AccentColour = colour.Purple1; + Hotkey = GlobalAction.ToggleBeatmapOptions; + } + } +} diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs new file mode 100644 index 0000000000..70d1c0c19e --- /dev/null +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Select.FooterV2 +{ + public partial class FooterButtonRandomV2 : FooterButtonV2 + { + public Action? NextRandom { get; set; } + public Action? PreviousRandom { get; set; } + + private Container persistentText = null!; + private OsuSpriteText randomSpriteText = null!; + private OsuSpriteText rewindSpriteText = null!; + private bool rewindSearch; + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + //TODO: use https://fontawesome.com/icons/shuffle?s=solid&f=classic when local Fontawesome is updated + Icon = FontAwesome.Solid.Random; + AccentColour = colour.Blue1; + TextContainer.Add(persistentText = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AlwaysPresent = true, + AutoSizeAxes = Axes.Both, + Children = new[] + { + randomSpriteText = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 19), + AlwaysPresent = true, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Random", + }, + rewindSpriteText = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 19), + AlwaysPresent = true, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Rewind", + Alpha = 0f, + } + } + }); + + Action = () => + { + if (rewindSearch) + { + const double fade_time = 500; + + OsuSpriteText fallingRewind; + + TextContainer.Add(fallingRewind = new OsuSpriteText + { + Alpha = 0, + Text = rewindSpriteText.Text, + AlwaysPresent = true, // make sure the button is sized large enough to always show this + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.TorusAlternate.With(size: 19), + }); + + fallingRewind.FadeOutFromOne(fade_time, Easing.In); + fallingRewind.MoveTo(Vector2.Zero).MoveTo(new Vector2(0, 10), fade_time, Easing.In); + fallingRewind.Expire(); + + persistentText.FadeInFromZero(fade_time, Easing.In); + + PreviousRandom?.Invoke(); + } + else + { + NextRandom?.Invoke(); + } + }; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + updateText(e.ShiftPressed); + return base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyUpEvent e) + { + updateText(e.ShiftPressed); + base.OnKeyUp(e); + } + + protected override bool OnClick(ClickEvent e) + { + try + { + // this uses OR to handle rewinding when clicks are triggered by other sources (i.e. right button in OnMouseUp). + rewindSearch |= e.ShiftPressed; + return base.OnClick(e); + } + finally + { + rewindSearch = false; + } + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (e.Button == MouseButton.Right) + { + rewindSearch = true; + TriggerClick(); + return; + } + + base.OnMouseUp(e); + } + + public override bool OnPressed(KeyBindingPressEvent e) + { + rewindSearch = e.Action == GlobalAction.SelectPreviousRandom; + + if (e.Action != GlobalAction.SelectNextRandom && e.Action != GlobalAction.SelectPreviousRandom) + { + return false; + } + + if (!e.Repeat) + TriggerClick(); + return true; + } + + public override void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == GlobalAction.SelectPreviousRandom) + { + rewindSearch = false; + } + } + + private void updateText(bool rewind = false) + { + randomSpriteText.Alpha = rewind ? 0 : 1; + rewindSpriteText.Alpha = rewind ? 1 : 0; + } + } +} diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs new file mode 100644 index 0000000000..2f5046d2bb --- /dev/null +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs @@ -0,0 +1,211 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +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.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select.FooterV2 +{ + public partial class FooterButtonV2 : OsuClickableContainer, IKeyBindingHandler + { + private const int button_height = 90; + private const int button_width = 140; + private const int corner_radius = 10; + private const int transition_length = 500; + + // This should be 12 by design, but an extra allowance is added due to the corner radius specification. + public const float SHEAR_WIDTH = 13.5f; + + public Bindable OverlayState = new Bindable(); + + protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / button_height, 0); + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private Colour4 buttonAccentColour; + + protected Colour4 AccentColour + { + set + { + buttonAccentColour = value; + bar.Colour = buttonAccentColour; + icon.Colour = buttonAccentColour; + } + } + + protected IconUsage Icon + { + set => icon.Icon = value; + } + + protected LocalisableString Text + { + set => text.Text = value; + } + + private readonly SpriteText text; + private readonly SpriteIcon icon; + + protected Container TextContainer; + private readonly Box bar; + private readonly Box backgroundBox; + + public FooterButtonV2() + { + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 4, + // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. + Colour = Colour4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + }; + Shear = SHEAR; + Size = new Vector2(button_width, button_height); + Masking = true; + CornerRadius = corner_radius; + Children = new Drawable[] + { + backgroundBox = new Box + { + RelativeSizeAxes = Axes.Both + }, + + // For elements that should not be sheared. + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = -SHEAR, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + TextContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 42, + AutoSizeAxes = Axes.Both, + Child = text = new OsuSpriteText + { + // figma design says the size is 16, but due to the issues with font sizes 19 matches better + Font = OsuFont.TorusAlternate.With(size: 19), + AlwaysPresent = true + } + }, + icon = new SpriteIcon + { + Y = 12, + Size = new Vector2(20), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + } + }, + new Container + { + Shear = -SHEAR, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Y = -corner_radius, + Size = new Vector2(120, 6), + Masking = true, + CornerRadius = 3, + Child = bar = new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + OverlayState.BindValueChanged(_ => updateDisplay()); + Enabled.BindValueChanged(_ => updateDisplay(), true); + + FinishTransforms(true); + } + + public GlobalAction? Hotkey; + + private bool handlingMouse; + + protected override bool OnHover(HoverEvent e) + { + updateDisplay(); + return true; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + handlingMouse = true; + updateDisplay(); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + handlingMouse = false; + updateDisplay(); + base.OnMouseUp(e); + } + + protected override void OnHoverLost(HoverLostEvent e) => updateDisplay(); + + public virtual bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action != Hotkey || e.Repeat) return false; + + TriggerClick(); + return true; + } + + public virtual void OnReleased(KeyBindingReleaseEvent e) { } + + private void updateDisplay() + { + Color4 backgroundColour = colourProvider.Background3; + + if (!Enabled.Value) + { + backgroundColour = colourProvider.Background3.Darken(0.4f); + } + else + { + if (OverlayState.Value == Visibility.Visible) + backgroundColour = buttonAccentColour.Darken(0.5f); + + if (IsHovered) + { + backgroundColour = backgroundColour.Lighten(0.3f); + + if (handlingMouse) + backgroundColour = backgroundColour.Lighten(0.3f); + } + } + + backgroundBox.FadeColour(backgroundColour, transition_length, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/Select/FooterV2/FooterV2.cs b/osu.Game/Screens/Select/FooterV2/FooterV2.cs new file mode 100644 index 0000000000..cd95f3eb6c --- /dev/null +++ b/osu.Game/Screens/Select/FooterV2/FooterV2.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Select.FooterV2 +{ + public partial class FooterV2 : InputBlockingContainer + { + //Should be 60, setting to 50 for now for the sake of matching the current BackButton height. + private const int height = 50; + private const int padding = 80; + + private readonly List overlays = new List(); + + /// The button to be added. + /// The to be toggled by this button. + public void AddButton(FooterButtonV2 button, OverlayContainer? overlay = null) + { + if (overlay != null) + { + overlays.Add(overlay); + button.Action = () => showOverlay(overlay); + button.OverlayState.BindTo(overlay.State); + } + + buttons.Add(button); + } + + private void showOverlay(OverlayContainer overlay) + { + foreach (var o in overlays) + { + if (o == overlay) + o.ToggleVisibility(); + else + o.Hide(); + } + } + + private FillFlowContainer buttons = null!; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.X; + Height = height; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5 + }, + buttons = new FillFlowContainer + { + Position = new Vector2(TwoLayerButton.SIZE_EXTENDED.X + padding, 10), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(-FooterButtonV2.SHEAR_WIDTH + 7, 0), + AutoSizeAxes = Axes.Both + } + }; + } + } +} diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index b1c3fac382..0b11d12c46 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -18,10 +18,15 @@ namespace osu.Game.Users public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker; - public class Modding : UserActivity + public class ModdingBeatmap : EditingBeatmap { - public override string GetStatus(bool hideIdentifiableInformation = false) => "Modding a map"; + public override string GetStatus(bool hideIdentifiableInformation = false) => "Modding a beatmap"; public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark; + + public ModdingBeatmap(IBeatmapInfo info) + : base(info) + { + } } public class ChoosingBeatmap : UserActivity @@ -80,11 +85,21 @@ namespace osu.Game.Users } } - public class Editing : UserActivity + public class TestingBeatmap : InGame + { + public override string GetStatus(bool hideIdentifiableInformation = false) => "Testing a beatmap"; + + public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) + : base(beatmapInfo, ruleset) + { + } + } + + public class EditingBeatmap : UserActivity { public IBeatmapInfo BeatmapInfo { get; } - public Editing(IBeatmapInfo info) + public EditingBeatmap(IBeatmapInfo info) { BeatmapInfo = info; } @@ -92,7 +107,7 @@ namespace osu.Game.Users public override string GetStatus(bool hideIdentifiableInformation = false) => @"Editing a beatmap"; } - public class Watching : UserActivity + public class WatchingReplay : UserActivity { private readonly ScoreInfo score; @@ -100,7 +115,7 @@ namespace osu.Game.Users public BeatmapInfo BeatmapInfo => score.BeatmapInfo; - public Watching(ScoreInfo score) + public WatchingReplay(ScoreInfo score) { this.score = score; } @@ -108,11 +123,11 @@ namespace osu.Game.Users public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Watching a replay" : $@"Watching {Username}'s replay"; } - public class Spectating : Watching + public class SpectatingUser : WatchingReplay { public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Spectating a user" : $@"Spectating {Username}"; - public Spectating(ScoreInfo score) + public SpectatingUser(ScoreInfo score) : base(score) { }