From 3953f829c8e9b0b0247b6a43a6b0cbbfeda2bab3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jan 2019 13:29:37 +0900 Subject: [PATCH] Add letterbox/screen scaling support --- osu.Game/Configuration/OsuConfigManager.cs | 15 ++- osu.Game/Configuration/ScalingMode.cs | 12 ++ .../Graphics/Containers/ScalingContainer.cs | 122 ++++++++++++++++++ .../Graphics/UserInterface/OsuSliderBar.cs | 2 +- osu.Game/OsuGame.cs | 15 ++- osu.Game/OsuGameBase.cs | 6 +- .../Sections/Graphics/LayoutSettings.cs | 47 ++++--- .../Backgrounds/BackgroundScreenEmpty.cs | 20 ++- osu.Game/Screens/Menu/Intro.cs | 2 +- osu.Game/Screens/Menu/MenuSideFlashes.cs | 2 + osu.Game/Screens/Play/Player.cs | 16 ++- 11 files changed, 228 insertions(+), 31 deletions(-) create mode 100644 osu.Game/Configuration/ScalingMode.cs create mode 100644 osu.Game/Graphics/Containers/ScalingContainer.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8975ab8a0e..be293d02f6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -96,6 +96,14 @@ namespace osu.Game.Configuration Set(OsuSetting.ScreenshotCaptureMenuCursor, false); Set(OsuSetting.SongSelectRightMouseScroll, false); + + Set(OsuSetting.Scaling, ScalingMode.Off); + + Set(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); + Set(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); + + Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); + Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); } public OsuConfigManager(Storage storage) : base(storage) @@ -151,6 +159,11 @@ namespace osu.Game.Configuration BeatmapHitsounds, IncreaseFirstObjectVisibility, ScoreDisplayMode, - ExternalLinkWarning + ExternalLinkWarning, + Scaling, + ScalingPositionX, + ScalingPositionY, + ScalingSizeX, + ScalingSizeY } } diff --git a/osu.Game/Configuration/ScalingMode.cs b/osu.Game/Configuration/ScalingMode.cs new file mode 100644 index 0000000000..063e967fa3 --- /dev/null +++ b/osu.Game/Configuration/ScalingMode.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2007-2019 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +namespace osu.Game.Configuration +{ + public enum ScalingMode + { + Off, + Everything, + ExcludeOverlays, + Gameplay, + } +} \ No newline at end of file diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs new file mode 100644 index 0000000000..6686e6057e --- /dev/null +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2007-2019 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// Handles user-defined scaling, allowing application at multiple levels defined by . + /// + public class ScalingContainer : Container + { + private readonly bool isTopLevel; + + private Bindable sizeX; + private Bindable sizeY; + private Bindable posX; + private Bindable posY; + + private readonly ScalingMode targetMode; + + private Bindable scalingMode; + + private readonly Container content; + protected override Container Content => content; + + private readonly Container sizableContainer; + + private Drawable backgroundLayer; + + /// + /// Create a new instance. + /// + /// The mode which this container should be handling. + public ScalingContainer(ScalingMode targetMode) + { + this.targetMode = targetMode; + RelativeSizeAxes = Axes.Both; + + InternalChild = sizableContainer = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + CornerRadius = 10, + Child = content = new DrawSizePreservingFillContainer() + }; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + scalingMode = config.GetBindable(OsuSetting.Scaling); + scalingMode.ValueChanged += _ => updateSize(); + + sizeX = config.GetBindable(OsuSetting.ScalingSizeX); + sizeX.ValueChanged += _ => updateSize(); + + sizeY = config.GetBindable(OsuSetting.ScalingSizeY); + sizeY.ValueChanged += _ => updateSize(); + + posX = config.GetBindable(OsuSetting.ScalingPositionX); + posX.ValueChanged += _ => updateSize(); + + posY = config.GetBindable(OsuSetting.ScalingPositionY); + posY.ValueChanged += _ => updateSize(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateSize(); + content.FinishTransforms(); + } + + private bool requiresBackgroundVisible => (scalingMode == ScalingMode.Everything || scalingMode == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1); + + private void updateSize() + { + if (targetMode == ScalingMode.Everything) + { + // the top level scaling container manages the background to be displayed while scaling. + if (requiresBackgroundVisible) + { + if (backgroundLayer == null) + LoadComponentAsync(backgroundLayer = new Background("Menu/menu-background-1") + { + Colour = OsuColour.Gray(0.1f), + Alpha = 0, + Depth = float.MaxValue + }, d => + { + AddInternal(d); + d.FadeTo(requiresBackgroundVisible ? 1 : 0, 4000, Easing.OutQuint); + }); + else + backgroundLayer.FadeIn(500); + } + else + backgroundLayer?.FadeOut(500); + } + + bool letterbox = scalingMode.Value == targetMode; + + var targetSize = letterbox ? new Vector2(sizeX, sizeY) : Vector2.One; + var targetPosition = letterbox ? new Vector2(posX, posY) * (Vector2.One - targetSize) : Vector2.Zero; + bool requiresMasking = targetSize != Vector2.One; + + if (requiresMasking) + sizableContainer.Masking = true; + + sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); + sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { content.Masking = requiresMasking; }); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index a59abcbcee..10b83c2610 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -47,7 +47,7 @@ namespace osu.Game.Graphics.UserInterface var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue; var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue; - if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1)) + if (floatMaxValue == 1 && floatMinValue >= -1) return floatValue.Value.ToString("P0"); var decimalPrecision = normalise((decimal)floatPrecision, max_decimal_digits); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2a4c812401..c9385359bc 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -26,6 +26,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; @@ -187,6 +188,7 @@ namespace osu.Game } private ExternalLinkOpener externalLinkOpener; + public void OpenUrlExternally(string url) { if (url.StartsWith("/")) @@ -353,7 +355,11 @@ namespace osu.Game ActionRequested = action => volume.Adjust(action), ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), }, - mainContent = new Container { RelativeSizeAxes = Axes.Both }, + screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) + { + RelativeSizeAxes = Axes.Both, + }, + mainContent = new DrawSizePreservingFillContainer(), overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, idleTracker = new IdleTracker(6000) }); @@ -362,7 +368,7 @@ namespace osu.Game { screenStack.ModePushed += screenAdded; screenStack.Exited += screenRemoved; - mainContent.Add(screenStack); + screenContainer.Add(screenStack); }); loadComponentSingleFile(Toolbar = new Toolbar @@ -497,7 +503,7 @@ namespace osu.Game if (notifications.State == Visibility.Visible) offset -= ToolbarButton.WIDTH / 2; - screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); + screenContainer.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); } settings.StateChanged += _ => updateScreenOffset(); @@ -555,7 +561,7 @@ namespace osu.Game focused.StateChanged += s => { visibleOverlayCount += s == Visibility.Visible ? 1 : -1; - screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); + screenContainer.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); }; } @@ -646,6 +652,7 @@ namespace osu.Game private OsuScreen currentScreen; private FrameworkConfigManager frameworkConfig; + private ScalingContainer screenContainer; protected override bool OnExiting() { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 683fa30818..b6c642c9dc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -24,6 +24,7 @@ using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; @@ -189,7 +190,7 @@ namespace osu.Game Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } }; - base.Content.Add(new DrawSizePreservingFillContainer { Child = MenuCursorContainer }); + base.Content.Add(new ScalingContainer(ScalingMode.Everything) { Child = MenuCursorContainer }); KeyBindingStore.Register(globalBinding); dependencies.Cache(globalBinding); @@ -247,7 +248,8 @@ namespace osu.Game var extension = Path.GetExtension(paths.First())?.ToLowerInvariant(); foreach (var importer in fileImporters) - if (importer.HandledExtensions.Contains(extension)) importer.Import(paths); + if (importer.HandledExtensions.Contains(extension)) + importer.Import(paths); } public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 685244e06b..0386065a82 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.Graphics @@ -16,9 +17,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { protected override string Header => "Layout"; - private FillFlowContainer letterboxSettings; + private FillFlowContainer scalingSettings; - private Bindable letterboxing; + private Bindable scalingMode; private Bindable sizeFullscreen; private OsuGameBase game; @@ -28,11 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private const int transition_duration = 400; [BackgroundDependencyLoader] - private void load(FrameworkConfigManager config, OsuGameBase game) + private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, OsuGameBase game) { this.game = game; - letterboxing = config.GetBindable(FrameworkSetting.Letterboxing); + scalingMode = osuConfig.GetBindable(OsuSetting.Scaling); sizeFullscreen = config.GetBindable(FrameworkSetting.SizeFullscreen); Container resolutionSettingsContainer; @@ -49,12 +50,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, - new SettingsCheckbox + new SettingsEnumDropdown { - LabelText = "Letterboxing", - Bindable = letterboxing, + LabelText = "Scaling", + Bindable = osuConfig.GetBindable(OsuSetting.Scaling), }, - letterboxSettings = new FillFlowContainer + scalingSettings = new FillFlowContainer { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, @@ -65,16 +66,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Children = new Drawable[] { - new SettingsSlider + new SettingsSlider { LabelText = "Horizontal position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX), + Bindable = osuConfig.GetBindable(OsuSetting.ScalingPositionX), KeyboardStep = 0.01f }, - new SettingsSlider + new SettingsSlider { LabelText = "Vertical position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY), + Bindable = osuConfig.GetBindable(OsuSetting.ScalingPositionY), + KeyboardStep = 0.01f + }, + new SettingsSlider + { + LabelText = "Horizontal size", + Bindable = osuConfig.GetBindable(OsuSetting.ScalingSizeX), + KeyboardStep = 0.01f + }, + new SettingsSlider + { + LabelText = "Vertical size", + Bindable = osuConfig.GetBindable(OsuSetting.ScalingSizeY), KeyboardStep = 0.01f }, } @@ -105,13 +118,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, true); } - letterboxing.BindValueChanged(isVisible => + scalingMode.BindValueChanged(mode => { - letterboxSettings.ClearTransforms(); - letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + scalingSettings.ClearTransforms(); + scalingSettings.AutoSizeAxes = mode != ScalingMode.Off ? Axes.Y : Axes.None; - if (!isVisible) - letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + if (mode == ScalingMode.Off) + scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); }, true); } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs index 5e08db8907..c097d25178 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs @@ -1,9 +1,27 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osuTK.Graphics; + namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenEmpty : BackgroundScreen + public class BackgroundScreenBlack : BackgroundScreen { + public BackgroundScreenBlack() + { + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override void OnEntering(Screen last) + { + Show(); + } } } diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index fa01411a0f..8d9cd8dbe9 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu public override bool CursorVisible => false; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty(); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); private Bindable menuVoice; private Bindable menuMusic; diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index ec5528b13f..188e95ced5 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -58,6 +58,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, Width = box_width * 2, + Height = 1.5f, // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, Alpha = 0, @@ -70,6 +71,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, Width = box_width * 2, + Height = 1.5f, X = box_width, Alpha = 0, Blending = BlendingMode.Additive, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c102fb0223..93102228a4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -20,6 +20,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; using osu.Game.Overlays; @@ -179,10 +180,14 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Alpha = 0, }, - new LocalSkinOverrideContainer(working.Skin) + new ScalingContainer(ScalingMode.Gameplay) { - RelativeSizeAxes = Axes.Both, - Child = RulesetContainer + Child = + new LocalSkinOverrideContainer(working.Skin) + { + RelativeSizeAxes = Axes.Both, + Child = RulesetContainer + } }, new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { @@ -191,7 +196,10 @@ namespace osu.Game.Screens.Play ProcessCustomClock = false, Breaks = beatmap.Breaks }, - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), + new ScalingContainer(ScalingMode.Gameplay) + { + Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(), + }, hudOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { Clock = Clock, // hud overlay doesn't want to use the audio clock directly