Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add first run setup dialog #17881

Merged
merged 44 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ae07b2b
Initial setup
peppy Apr 6, 2022
c2df346
Add ui scaling setup screen, kind of
peppy Apr 6, 2022
e064f2f
Improve general layout and allow overlay to be displayed without depe…
peppy Apr 18, 2022
3b94e01
Improve layout of ui scale step
peppy Apr 18, 2022
07da1cd
Move buttons to top level and add backwards navigation
peppy Apr 18, 2022
288f759
Add test coverage of navigation
peppy Apr 18, 2022
56c4283
Only exit dialog from click if outside the dialog content
peppy Apr 18, 2022
ea52fab
Tidy up dependencies and test naming
peppy Apr 18, 2022
11395c4
Add button to access first run setup on demand
peppy Apr 18, 2022
6716621
Re-fix clicking inside display also dismissing
peppy Apr 18, 2022
fb7dc89
Ensure wizard returns to initial screen after completion
peppy Apr 18, 2022
3ea4eab
Ensure button text is updated on going backwards
peppy Apr 18, 2022
8bfa59d
Ensure all other dialogs and overlays are dismissed when the first ru…
peppy Apr 18, 2022
9074eb2
Show a notification instead of blocking exit of wizard
peppy Apr 18, 2022
2682373
Use existing blocking / exit logic provided by `OsuFocusedOverlayCont…
peppy Apr 18, 2022
5fd64a4
Add test coverage to ensure we don't leave any screens in the first r…
peppy Apr 18, 2022
02f8367
Improve animation when showing/hiding first run overlay
peppy Apr 18, 2022
a8d32a2
Add test coverage of notification resume flow
peppy Apr 18, 2022
5dc3805
Fix `stack` nullability
peppy Apr 19, 2022
e67cc29
Add localisation support for all new strings
peppy Apr 19, 2022
6d53404
Add keyboard traversal support for first run dialog (and tidy up step…
peppy Apr 19, 2022
c4bade0
Expose `MainMenu` buttons
peppy Apr 19, 2022
c278311
Add scroll and flow at the `FirstRunSetupScreen` level
peppy Apr 19, 2022
1490502
Improve overall usability of scale adjust screen
peppy Apr 19, 2022
3378c91
Fix double applied padding
peppy Apr 19, 2022
e2da1d7
Only show first run setup once per install
peppy Apr 19, 2022
c562004
Add test coverage of only showing on first run
peppy Apr 19, 2022
17eaa44
Ensure notifications don't interrupt the first run setup process
peppy Apr 19, 2022
ce70c10
Tidy up unused dependencies
peppy Apr 19, 2022
e8adbb3
Skip first run setup in `OsuGameTestScene`s
peppy Apr 19, 2022
488fc9d
Reverse content colours to match design spec
peppy Apr 20, 2022
9797e2d
Rename `showLastStep` method to avoid ambiguity
peppy Apr 20, 2022
66373bf
Move back button enable handling to shared method
peppy Apr 20, 2022
7e7fa63
Use direct localised string reference in test
peppy Apr 20, 2022
8c2d70e
Disable forward button when already at end
peppy Apr 20, 2022
2906af3
Fix incorrectly written string equality logic
peppy Apr 20, 2022
56d8de2
Replace horse with "more suitable" icon
peppy Apr 21, 2022
7d8cf1b
Merge branch 'master' into first-run-setup
peppy Apr 21, 2022
5ae3d0e
Fix wording of ui scale screen description
peppy Apr 21, 2022
d17c16d
Merge branch 'master' into first-run-setup
peppy Apr 21, 2022
1f967ec
Update string keys and change "setup" to "set up" to fix grammar
peppy Apr 21, 2022
ed6481f
Also rename `Welcome` to `WelcomeTitle` to match other usages
peppy Apr 21, 2022
8c68647
Merge branch 'master' into first-run-setup
bdach Apr 21, 2022
8fe4443
Update screen method signatures to match framework API changes
bdach Apr 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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());
});
}
}
}
193 changes: 193 additions & 0 deletions osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
3 changes: 3 additions & 0 deletions osu.Game/Configuration/OsuConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ protected override void InitialiseDefaults()

SetDefault(OsuSetting.Version, string.Empty);

SetDefault(OsuSetting.ShowFirstRunSetup, true);

SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false);

Expand Down Expand Up @@ -308,6 +310,7 @@ public enum OsuSetting
BeatmapListingCardSize,
ToolbarClockDisplayMode,
Version,
ShowFirstRunSetup,
ShowConvertedBeatmaps,
Skin,
ScreenshotFormat,
Expand Down
10 changes: 5 additions & 5 deletions osu.Game/Graphics/Containers/ScalingContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ public ScalingContainer(ScalingMode? targetMode = null)
};
}

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;

Expand All @@ -99,14 +99,14 @@ private void load(OsuConfigManager osuConfig)
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();
}
Expand Down
10 changes: 10 additions & 0 deletions osu.Game/Localisation/CommonStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ public static class CommonStrings
{
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>
Expand Down
58 changes: 58 additions & 0 deletions osu.Game/Localisation/FirstRunSetupOverlayStrings.cs
Original file line number Diff line number Diff line change
@@ -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}";
}
}
5 changes: 5 additions & 0 deletions osu.Game/Localisation/GeneralSettingsStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public static class GeneralSettingsStrings
/// </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}";
}
}
7 changes: 5 additions & 2 deletions osu.Game/OsuGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUser

protected SettingsOverlay Settings;

private FirstRunSetupOverlay firstRunOverlay;

private VolumeOverlay volume;

private OsuLogo osuLogo;
Expand Down Expand Up @@ -799,6 +801,7 @@ protected override void LoadComplete()
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);
Expand Down Expand Up @@ -849,7 +852,7 @@ void updateChatPollRate()
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)
{
Expand All @@ -874,7 +877,7 @@ void updateChatPollRate()
}

// 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)
{
Expand Down
Loading