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

Merge branch 'master' into mod-overlay/incompatibility-panels-clickable

This commit is contained in:
Dean Herbert 2022-04-22 16:26:46 +09:00 committed by GitHub
commit f735d381a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1032 additions and 28 deletions

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.417.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.421.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">

View File

@ -21,8 +21,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
State = { Value = Visibility.Visible }
});
AddUntilStep("all column content loaded",
() => freeModSelectScreen.ChildrenOfType<ModColumn>().Any()
&& freeModSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
AddAssert("all visible mods are playable",
AddUntilStep("all visible mods are playable",
() => this.ChildrenOfType<ModPanel>()
.Where(panel => panel.IsPresent)
.All(panel => panel.Mod.HasImplementation && panel.Mod.UserPlayable));

View File

@ -0,0 +1,19 @@
// 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.Screens;
using osu.Game.Overlays.FirstRunSetup;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneFirstRunScreenUIScale : OsuManualInputManagerTestScene
{
public TestSceneFirstRunScreenUIScale()
{
AddStep("load screen", () =>
{
Child = new ScreenStack(new ScreenUIScale());
});
}
}
}

View File

@ -0,0 +1,193 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene
{
private FirstRunSetupOverlay overlay;
private readonly Mock<IPerformFromScreenRunner> performer = new Mock<IPerformFromScreenRunner>();
private readonly Mock<INotificationOverlay> notificationOverlay = new Mock<INotificationOverlay>();
private Notification lastNotification;
protected OsuConfigManager LocalConfig;
[BackgroundDependencyLoader]
private void load()
{
Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage));
Dependencies.CacheAs(performer.Object);
Dependencies.CacheAs(notificationOverlay.Object);
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup dependencies", () =>
{
performer.Reset();
notificationOverlay.Reset();
performer.Setup(g => g.PerformFromScreen(It.IsAny<Action<IScreen>>(), It.IsAny<IEnumerable<Type>>()))
.Callback((Action<IScreen> action, IEnumerable<Type> types) => action(null));
notificationOverlay.Setup(n => n.Post(It.IsAny<Notification>()))
.Callback((Notification n) => lastNotification = n);
});
AddStep("add overlay", () =>
{
Child = overlay = new FirstRunSetupOverlay
{
State = { Value = Visibility.Visible }
};
});
}
[Test]
public void TestDoesntOpenOnSecondRun()
{
AddStep("set first run", () => LocalConfig.SetValue(OsuSetting.ShowFirstRunSetup, true));
AddUntilStep("step through", () =>
{
if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.TriggerClick();
return overlay.State.Value == Visibility.Hidden;
});
AddAssert("first run false", () => !LocalConfig.Get<bool>(OsuSetting.ShowFirstRunSetup));
AddStep("add overlay", () =>
{
Child = overlay = new FirstRunSetupOverlay();
});
AddWaitStep("wait some", 5);
AddAssert("overlay didn't show", () => overlay.State.Value == Visibility.Hidden);
}
[TestCase(false)]
[TestCase(true)]
public void TestOverlayRunsToFinish(bool keyboard)
{
AddUntilStep("step through", () =>
{
if (overlay.CurrentScreen?.IsLoaded != false)
{
if (keyboard)
InputManager.Key(Key.Enter);
else
overlay.NextButton.TriggerClick();
}
return overlay.State.Value == Visibility.Hidden;
});
AddUntilStep("wait for screens removed", () => !overlay.ChildrenOfType<Screen>().Any());
AddStep("no notifications", () => notificationOverlay.VerifyNoOtherCalls());
AddStep("display again on demand", () => overlay.Show());
AddUntilStep("back at start", () => overlay.CurrentScreen is ScreenWelcome);
}
[TestCase(false)]
[TestCase(true)]
public void TestBackButton(bool keyboard)
{
AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value);
AddUntilStep("step to last", () =>
{
var nextButton = overlay.NextButton;
if (overlay.CurrentScreen?.IsLoaded != false)
nextButton.TriggerClick();
return nextButton.Text == CommonStrings.Finish;
});
AddUntilStep("step back to start", () =>
{
if (overlay.CurrentScreen?.IsLoaded != false)
{
if (keyboard)
InputManager.Key(Key.Escape);
else
overlay.BackButton.TriggerClick();
}
return overlay.CurrentScreen is ScreenWelcome;
});
AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value);
if (keyboard)
{
AddStep("exit via keyboard", () => InputManager.Key(Key.Escape));
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
}
}
[Test]
public void TestClickAwayToExit()
{
AddStep("click inside content", () =>
{
InputManager.MoveMouseTo(overlay.ScreenSpaceDrawQuad.Centre);
InputManager.Click(MouseButton.Left);
});
AddAssert("overlay not dismissed", () => overlay.State.Value == Visibility.Visible);
AddStep("click outside content", () =>
{
InputManager.MoveMouseTo(overlay.ScreenSpaceDrawQuad.TopLeft - new Vector2(1));
InputManager.Click(MouseButton.Left);
});
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
}
[Test]
public void TestResumeViaNotification()
{
AddStep("step to next", () => overlay.NextButton.TriggerClick());
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale);
AddStep("hide", () => overlay.Hide());
AddAssert("overlay hidden", () => overlay.State.Value == Visibility.Hidden);
AddStep("notification arrived", () => notificationOverlay.Verify(n => n.Post(It.IsAny<Notification>()), Times.Once));
AddStep("run notification action", () => lastNotification.Activated());
AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible);
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
}
}
}

View File

@ -134,6 +134,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Version, string.Empty);
SetDefault(OsuSetting.ShowFirstRunSetup, true);
SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false);
@ -308,6 +310,7 @@ namespace osu.Game.Configuration
BeatmapListingCardSize,
ToolbarClockDisplayMode,
Version,
ShowFirstRunSetup,
ShowConvertedBeatmaps,
Skin,
ScreenshotFormat,

View File

@ -79,12 +79,12 @@ namespace osu.Game.Graphics.Containers
};
}
private class ScalingDrawSizePreservingFillContainer : DrawSizePreservingFillContainer
public class ScalingDrawSizePreservingFillContainer : DrawSizePreservingFillContainer
{
private readonly bool applyUIScale;
private Bindable<float> uiScale;
private float currentScale = 1;
protected float CurrentScale { get; private set; } = 1;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
@ -99,14 +99,14 @@ namespace osu.Game.Graphics.Containers
if (applyUIScale)
{
uiScale = osuConfig.GetBindable<float>(OsuSetting.UIScale);
uiScale.BindValueChanged(args => this.TransformTo(nameof(currentScale), args.NewValue, duration, Easing.OutQuart), true);
uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, duration, Easing.OutQuart), true);
}
}
protected override void Update()
{
Scale = new Vector2(currentScale);
Size = new Vector2(1 / currentScale);
Scale = new Vector2(CurrentScale);
Size = new Vector2(1 / CurrentScale);
base.Update();
}

View File

@ -9,6 +9,16 @@ namespace osu.Game.Localisation
{
private const string prefix = @"osu.Game.Resources.Localisation.Common";
/// <summary>
/// "Back"
/// </summary>
public static LocalisableString Back => new TranslatableString(getKey(@"back"), @"Back");
/// <summary>
/// "Finish"
/// </summary>
public static LocalisableString Finish => new TranslatableString(getKey(@"finish"), @"Finish");
/// <summary>
/// "Enabled"
/// </summary>

View File

@ -0,0 +1,58 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class FirstRunSetupOverlayStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.FirstRunSetupOverlay";
/// <summary>
/// "Get started"
/// </summary>
public static LocalisableString GetStarted => new TranslatableString(getKey(@"get_started"), @"Get started");
/// <summary>
/// "Click to resume first-run setup at any point"
/// </summary>
public static LocalisableString ClickToResumeFirstRunSetupAtAnyPoint => new TranslatableString(getKey(@"click_to_resume_first_run_setup_at_any_point"), @"Click to resume first-run setup at any point");
/// <summary>
/// "First-run setup"
/// </summary>
public static LocalisableString FirstRunSetupTitle => new TranslatableString(getKey(@"first_run_setup_title"), @"First-run setup");
/// <summary>
/// "Set up osu! to suit you"
/// </summary>
public static LocalisableString FirstRunSetupDescription => new TranslatableString(getKey(@"first_run_setup_description"), @"Set up osu! to suit you");
/// <summary>
/// "Welcome"
/// </summary>
public static LocalisableString WelcomeTitle => new TranslatableString(getKey(@"welcome_title"), @"Welcome");
/// <summary>
/// "Welcome to the first-run setup guide!
///
/// osu! is a very configurable game, and diving straight into the settings can sometimes be overwhelming. This guide will help you get the important choices out of the way to ensure a great first experience!"
/// </summary>
public static LocalisableString WelcomeDescription => new TranslatableString(getKey(@"welcome_description"), @"Welcome to the first-run setup guide!
osu! is a very configurable game, and diving straight into the settings can sometimes be overwhelming. This guide will help you get the important choices out of the way to ensure a great first experience!");
/// <summary>
/// "The size of the osu! user interface can be adjusted to your liking."
/// </summary>
public static LocalisableString UIScaleDescription => new TranslatableString(getKey(@"ui_scale_description"), @"The size of the osu! user interface can be adjusted to your liking.");
/// <summary>
/// "Next ({0})"
/// </summary>
public static LocalisableString Next(LocalisableString nextStepDescription) => new TranslatableString(getKey(@"next"), @"Next ({0})", nextStepDescription);
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -59,6 +59,11 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString ChangeFolderLocation => new TranslatableString(getKey(@"change_folder_location"), @"Change folder location...");
/// <summary>
/// "Run setup wizard"
/// </summary>
public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -29,6 +29,7 @@ namespace osu.Game.Online.API.Requests
Ranked,
Loved,
Pending,
Guest,
Graveyard
}
}

View File

@ -148,6 +148,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"pending_beatmapset_count")]
public int PendingBeatmapsetCount;
[JsonProperty(@"guest_beatmapset_count")]
public int GuestBeatmapsetCount;
[JsonProperty(@"scores_best_count")]
public int ScoresBestCount;

View File

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
@ -144,6 +145,12 @@ namespace osu.Game.Online
var builder = new HubConnectionBuilder()
.WithUrl(endpoint, options =>
{
// Use HttpClient.DefaultProxy once on net6 everywhere.
// The credential setter can also be removed at this point.
options.Proxy = WebRequest.DefaultWebProxy;
if (options.Proxy != null)
options.Proxy.Credentials = CredentialCache.DefaultCredentials;
options.Headers.Add("Authorization", $"Bearer {api.AccessToken}");
options.Headers.Add("OsuVersionHash", versionHash);
});

View File

@ -149,6 +149,8 @@ namespace osu.Game
protected SettingsOverlay Settings;
private FirstRunSetupOverlay firstRunOverlay;
private VolumeOverlay volume;
private OsuLogo osuLogo;
@ -799,6 +801,7 @@ namespace osu.Game
loadComponentSingleFile(CreateUpdateManager(), Add, true);
// overlay elements
loadComponentSingleFile(firstRunOverlay = new FirstRunSetupOverlay(), overlayContent.Add, true);
loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true);
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
@ -849,7 +852,7 @@ namespace osu.Game
Add(new MusicKeyBindingHandler());
// side overlays which cancel each other.
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications };
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications, firstRunOverlay };
foreach (var overlay in singleDisplaySideOverlays)
{
@ -874,7 +877,7 @@ namespace osu.Game
}
// ensure only one of these overlays are open at once.
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay };
var singleDisplayOverlays = new OverlayContainer[] { firstRunOverlay, chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay };
foreach (var overlay in singleDisplayOverlays)
{

View File

@ -0,0 +1,71 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osuTK;
namespace osu.Game.Overlays.FirstRunSetup
{
public abstract class FirstRunSetupScreen : Screen
{
private const float offset = 100;
protected FillFlowContainer Content { get; private set; }
protected FirstRunSetupScreen()
{
InternalChildren = new Drawable[]
{
new OsuScrollContainer(Direction.Vertical)
{
RelativeSizeAxes = Axes.Both,
Child = Content = new FillFlowContainer
{
Spacing = new Vector2(20),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
},
}
};
}
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(e);
this
.FadeInFromZero(500)
.MoveToX(offset)
.MoveToX(0, 500, Easing.OutQuint);
}
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(e);
this
.FadeInFromZero(500)
.MoveToX(0, 500, Easing.OutQuint);
}
public override bool OnExiting(ScreenExitEvent e)
{
this
.FadeOut(100)
.MoveToX(offset, 500, Easing.OutQuint);
return base.OnExiting(e);
}
public override void OnSuspending(ScreenTransitionEvent e)
{
this
.FadeOut(100)
.MoveToX(-offset, 500, Easing.OutQuint);
base.OnSuspending(e);
}
}
}

View File

@ -0,0 +1,182 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Select;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Overlays.FirstRunSetup
{
public class ScreenUIScale : FirstRunSetupScreen
{
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Content.Children = new Drawable[]
{
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24))
{
Text = FirstRunSetupOverlayStrings.UIScaleDescription,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
new SettingsSlider<float, UIScaleSlider>
{
LabelText = GraphicsSettingsStrings.UIScaling,
Current = config.GetBindable<float>(OsuSetting.UIScale),
KeyboardStep = 0.01f,
},
new InverseScalingDrawSizePreservingFillContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.None,
Size = new Vector2(960, 960 / 16f * 9 / 2),
Children = new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new SampleScreenContainer(new PinnedMainMenu()),
new SampleScreenContainer(new PlaySongSelect()),
},
// TODO: add more screens here in the future (gameplay / results)
// requires a bit more consideration to isolate their behaviour from the "parent" game.
}
}
}
}
};
}
private class InverseScalingDrawSizePreservingFillContainer : ScalingContainer.ScalingDrawSizePreservingFillContainer
{
private Vector2 initialSize;
public InverseScalingDrawSizePreservingFillContainer()
: base(true)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
initialSize = Size;
}
protected override void Update()
{
Size = initialSize / CurrentScale;
}
}
private class PinnedMainMenu : MainMenu
{
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(e);
Buttons.ReturnToTopOnIdle = false;
Buttons.State = ButtonSystemState.TopLevel;
}
}
private class UIScaleSlider : OsuSliderBar<float>
{
public override LocalisableString TooltipText => base.TooltipText + "x";
}
private class SampleScreenContainer : CompositeDrawable
{
// Minimal isolation from main game.
[Cached]
[Cached(typeof(IBindable<RulesetInfo>))]
protected readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
[Cached]
[Cached(typeof(IBindable<WorkingBeatmap>))]
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } = new Bindable<WorkingBeatmap>();
public override bool HandlePositionalInput => false;
public override bool HandleNonPositionalInput => false;
public override bool PropagatePositionalInputSubTree => false;
public override bool PropagateNonPositionalInputSubTree => false;
[BackgroundDependencyLoader]
private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets)
{
Beatmap.Value = new DummyWorkingBeatmap(audio, textures);
Beatmap.Value.LoadTrack();
Ruleset.Value = rulesets.AvailableRulesets.First();
}
public SampleScreenContainer(Screen screen)
{
OsuScreenStack stack;
RelativeSizeAxes = Axes.Both;
OsuLogo logo;
Padding = new MarginPadding(5);
InternalChildren = new Drawable[]
{
new DependencyProvidingContainer
{
CachedDependencies = new (Type, object)[]
{
(typeof(OsuLogo), logo = new OsuLogo
{
RelativePositionAxes = Axes.Both,
Position = new Vector2(0.5f),
})
},
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new ScalingContainer.ScalingDrawSizePreservingFillContainer(true)
{
Masking = true,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
stack = new OsuScreenStack(),
logo
},
},
}
},
};
stack.Push(screen);
}
}
}
}

View File

@ -0,0 +1,26 @@
// 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.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
namespace osu.Game.Overlays.FirstRunSetup
{
public class ScreenWelcome : FirstRunSetupScreen
{
public ScreenWelcome()
{
Content.Children = new Drawable[]
{
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
{
Text = FirstRunSetupOverlayStrings.WelcomeDescription,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
};
}
}
}

View File

@ -0,0 +1,398 @@
// 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 enable
using System;
using System.Diagnostics;
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.Events;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays.FirstRunSetup;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays
{
[Cached]
public class FirstRunSetupOverlay : OsuFocusedOverlayContainer
{
protected override bool StartHidden => true;
[Resolved]
private IPerformFromScreenRunner performer { get; set; } = null!;
[Resolved]
private INotificationOverlay notificationOverlay { get; set; } = null!;
[Resolved]
private OsuConfigManager config { get; set; } = null!;
private ScreenStack? stack;
public PurpleTriangleButton NextButton = null!;
public DangerousTriangleButton BackButton = null!;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private readonly Bindable<bool> showFirstRunSetup = new Bindable<bool>();
private int? currentStepIndex;
private const float scale_when_hidden = 0.9f;
/// <summary>
/// The currently displayed screen, if any.
/// </summary>
public FirstRunSetupScreen? CurrentScreen => (FirstRunSetupScreen?)stack?.CurrentScreen;
private readonly FirstRunStep[] steps =
{
new FirstRunStep(typeof(ScreenWelcome), FirstRunSetupOverlayStrings.WelcomeTitle),
new FirstRunStep(typeof(ScreenUIScale), GraphicsSettingsStrings.UIScaling),
};
private Container stackContainer = null!;
private Bindable<OverlayActivation>? overlayActivationMode;
public FirstRunSetupOverlay()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(0.95f);
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Radius = 5,
Colour = Color4.Black.Opacity(0.2f),
};
Masking = true;
CornerRadius = 10;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6,
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
Colour = colourProvider.Background5,
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding(10),
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = FirstRunSetupOverlayStrings.FirstRunSetupTitle,
Font = OsuFont.Default.With(size: 32),
Colour = colourProvider.Content1,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
new OsuTextFlowContainer
{
Text = FirstRunSetupOverlayStrings.FirstRunSetupDescription,
Colour = colourProvider.Content2,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
},
}
},
}
},
},
new Drawable[]
{
stackContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(20),
},
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(20)
{
Top = 0 // provided by the stack container above.
},
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new[]
{
BackButton = new DangerousTriangleButton
{
Width = 200,
Text = CommonStrings.Back,
Action = showPreviousStep,
Enabled = { Value = false },
},
Empty(),
NextButton = new PurpleTriangleButton
{
RelativeSizeAxes = Axes.X,
Width = 1,
Text = FirstRunSetupOverlayStrings.GetStarted,
Action = showNextStep
}
},
}
},
}
}
}
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
config.BindWith(OsuSetting.ShowFirstRunSetup, showFirstRunSetup);
if (showFirstRunSetup.Value) Show();
}
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (!e.Repeat)
{
switch (e.Action)
{
case GlobalAction.Select:
NextButton.TriggerClick();
return true;
case GlobalAction.Back:
if (BackButton.Enabled.Value)
{
BackButton.TriggerClick();
return true;
}
// If back button is disabled, we are at the first step.
// The base call will handle dismissal of the overlay.
break;
}
}
return base.OnPressed(e);
}
public override void Show()
{
// if we are valid for display, only do so after reaching the main menu.
performer.PerformFromScreen(screen =>
{
MainMenu menu = (MainMenu)screen;
// Eventually I'd like to replace this with a better method that doesn't access the screen.
// Either this dialog would be converted to its own screen, or at very least be "hosted" by a screen pushed to the main menu.
// Alternatively, another method of disabling notifications could be added to `INotificationOverlay`.
if (menu != null)
{
overlayActivationMode = menu.OverlayActivationMode.GetBoundCopy();
overlayActivationMode.Value = OverlayActivation.UserTriggered;
}
base.Show();
}, new[] { typeof(MainMenu) });
}
protected override void PopIn()
{
base.PopIn();
this.ScaleTo(scale_when_hidden)
.ScaleTo(1, 400, Easing.OutElasticHalf);
this.FadeIn(400, Easing.OutQuint);
if (currentStepIndex == null)
showFirstStep();
}
protected override void PopOut()
{
if (overlayActivationMode != null)
{
// If this is non-null we are guaranteed to have come from the main menu.
overlayActivationMode.Value = OverlayActivation.All;
overlayActivationMode = null;
}
if (currentStepIndex != null)
{
notificationOverlay.Post(new SimpleNotification
{
Text = FirstRunSetupOverlayStrings.ClickToResumeFirstRunSetupAtAnyPoint,
Icon = FontAwesome.Solid.Redo,
Activated = () =>
{
Show();
return true;
},
});
}
else
{
stack?.FadeOut(100)
.Expire();
}
base.PopOut();
this.ScaleTo(0.96f, 400, Easing.OutQuint);
this.FadeOut(200, Easing.OutQuint);
}
private void showFirstStep()
{
Debug.Assert(currentStepIndex == null);
stackContainer.Child = stack = new ScreenStack
{
RelativeSizeAxes = Axes.Both,
};
currentStepIndex = -1;
showNextStep();
}
private void showPreviousStep()
{
if (currentStepIndex == 0)
return;
Debug.Assert(stack != null);
stack.CurrentScreen.Exit();
currentStepIndex--;
updateButtons();
}
private void showNextStep()
{
Debug.Assert(currentStepIndex != null);
Debug.Assert(stack != null);
currentStepIndex++;
if (currentStepIndex < steps.Length)
{
stack.Push((Screen)Activator.CreateInstance(steps[currentStepIndex.Value].ScreenType));
}
else
{
showFirstRunSetup.Value = false;
currentStepIndex = null;
Hide();
}
updateButtons();
}
private void updateButtons()
{
BackButton.Enabled.Value = currentStepIndex > 0;
NextButton.Enabled.Value = currentStepIndex != null;
if (currentStepIndex != null)
{
NextButton.Text = currentStepIndex + 1 < steps.Length
? FirstRunSetupOverlayStrings.Next(steps[currentStepIndex.Value + 1].Description)
: CommonStrings.Finish;
}
}
private class FirstRunStep
{
public readonly Type ScreenType;
public readonly LocalisableString Description;
public FirstRunStep(Type screenType, LocalisableString description)
{
ScreenType = screenType;
Description = description;
}
}
}
}

View File

@ -53,6 +53,9 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
case BeatmapSetType.Pending:
return user.PendingBeatmapsetCount;
case BeatmapSetType.Guest:
return user.GuestBeatmapsetCount;
default:
return 0;
}

View File

@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Profile.Sections
new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, UsersStrings.ShowExtraBeatmapsFavouriteTitle),
new PaginatedBeatmapContainer(BeatmapSetType.Ranked, User, UsersStrings.ShowExtraBeatmapsRankedTitle),
new PaginatedBeatmapContainer(BeatmapSetType.Loved, User, UsersStrings.ShowExtraBeatmapsLovedTitle),
new PaginatedBeatmapContainer(BeatmapSetType.Guest, User, UsersStrings.ShowExtraBeatmapsGuestTitle),
new PaginatedBeatmapContainer(BeatmapSetType.Pending, User, UsersStrings.ShowExtraBeatmapsPendingTitle),
new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, UsersStrings.ShowExtraBeatmapsGraveyardTitle)
};

View File

@ -1,6 +1,7 @@
// 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.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
@ -11,6 +12,9 @@ namespace osu.Game.Overlays.Settings.Sections
{
public class GeneralSection : SettingsSection
{
[Resolved(CanBeNull = true)]
private FirstRunSetupOverlay firstRunSetupOverlay { get; set; }
public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
@ -22,6 +26,11 @@ namespace osu.Game.Overlays.Settings.Sections
{
Children = new Drawable[]
{
new SettingsButton
{
Text = GeneralSettingsStrings.RunSetupWizard,
Action = () => firstRunSetupOverlay?.Show(),
},
new LanguageSettings(),
new UpdateSettings(),
};

View File

@ -87,6 +87,8 @@ namespace osu.Game.Screens.Menu
private readonly LogoTrackingContainer logoTrackingContainer;
public bool ReturnToTopOnIdle { get; set; } = true;
public ButtonSystem()
{
RelativeSizeAxes = Axes.Both;
@ -100,7 +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, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
-WEDGE_WIDTH)
{
VisibleState = ButtonSystemState.Play,
},
@ -127,9 +130,11 @@ namespace osu.Game.Screens.Menu
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
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.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-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0,
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));
@ -177,6 +182,9 @@ namespace osu.Game.Screens.Menu
private void updateIdleState(bool isIdle)
{
if (!ReturnToTopOnIdle)
return;
if (isIdle && State != ButtonSystemState.Exit && State != ButtonSystemState.EnteringMode)
State = ButtonSystemState.Initial;
}

View File

@ -35,7 +35,7 @@ namespace osu.Game.Screens.Menu
public const float FADE_OUT_DURATION = 400;
public override bool HideOverlaysOnEnter => buttons == null || buttons.State == ButtonSystemState.Initial;
public override bool HideOverlaysOnEnter => Buttons == null || Buttons.State == ButtonSystemState.Initial;
public override bool AllowBackButton => false;
@ -45,7 +45,7 @@ namespace osu.Game.Screens.Menu
private MenuSideFlashes sideFlashes;
private ButtonSystem buttons;
protected ButtonSystem Buttons;
[Resolved]
private GameHost host { get; set; }
@ -101,7 +101,7 @@ namespace osu.Game.Screens.Menu
ParallaxAmount = 0.01f,
Children = new Drawable[]
{
buttons = new ButtonSystem
Buttons = new ButtonSystem
{
OnEdit = delegate
{
@ -125,7 +125,7 @@ namespace osu.Game.Screens.Menu
exitConfirmOverlay?.CreateProxy() ?? Empty()
});
buttons.StateChanged += state =>
Buttons.StateChanged += state =>
{
switch (state)
{
@ -140,8 +140,8 @@ namespace osu.Game.Screens.Menu
}
};
buttons.OnSettings = () => settings?.ToggleVisibility();
buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
Buttons.OnSettings = () => settings?.ToggleVisibility();
Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
LoadComponentAsync(background = new BackgroundScreenDefault());
preloadSongSelect();
@ -179,7 +179,7 @@ namespace osu.Game.Screens.Menu
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(e);
buttons.FadeInFromZero(500);
Buttons.FadeInFromZero(500);
if (e.Last is IntroScreen && musicController.TrackLoaded)
{
@ -203,14 +203,14 @@ namespace osu.Game.Screens.Menu
{
base.LogoArriving(logo, resuming);
buttons.SetOsuLogo(logo);
Buttons.SetOsuLogo(logo);
logo.FadeColour(Color4.White, 100, Easing.OutQuint);
logo.FadeIn(100, Easing.OutQuint);
if (resuming)
{
buttons.State = ButtonSystemState.TopLevel;
Buttons.State = ButtonSystemState.TopLevel;
this.FadeIn(FADE_IN_DURATION, Easing.OutQuint);
buttonsContainer.MoveTo(new Vector2(0, 0), FADE_IN_DURATION, Easing.OutQuint);
@ -245,15 +245,15 @@ namespace osu.Game.Screens.Menu
var seq = logo.FadeOut(300, Easing.InSine)
.ScaleTo(0.2f, 300, Easing.InSine);
seq.OnComplete(_ => buttons.SetOsuLogo(null));
seq.OnAbort(_ => buttons.SetOsuLogo(null));
seq.OnComplete(_ => Buttons.SetOsuLogo(null));
seq.OnAbort(_ => Buttons.SetOsuLogo(null));
}
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(e);
buttons.State = ButtonSystemState.EnteringMode;
Buttons.State = ButtonSystemState.EnteringMode;
this.FadeOut(FADE_OUT_DURATION, Easing.InSine);
buttonsContainer.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine);
@ -285,7 +285,7 @@ namespace osu.Game.Screens.Menu
return true;
}
buttons.State = ButtonSystemState.Exit;
Buttons.State = ButtonSystemState.Exit;
OverlayActivationMode.Value = OverlayActivation.Disabled;
songTicker.Hide();

View File

@ -48,7 +48,7 @@ namespace osu.Game.Screens
/// </summary>
protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All;
protected readonly Bindable<OverlayActivation> OverlayActivationMode;
public readonly Bindable<OverlayActivation> OverlayActivationMode;
IBindable<OverlayActivation> IOsuScreen.OverlayActivationMode => OverlayActivationMode;

View File

@ -156,6 +156,7 @@ namespace osu.Game.Tests.Visual
base.LoadComplete();
LocalConfig.SetValue(OsuSetting.IntroSequence, IntroSequence.Circles);
LocalConfig.SetValue(OsuSetting.ShowFirstRunSetup, false);
API.Login("Rhythm Champion", "osu!");

View File

@ -36,7 +36,7 @@
</PackageReference>
<PackageReference Include="Realm" Version="10.10.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.421.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.417.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
<PackageReference Include="Sentry" Version="3.14.1" />
<PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" />

View File

@ -62,7 +62,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.421.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.417.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
<PropertyGroup>