1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 05:32:54 +08:00

Merge pull request #3997 from peppy/letterboxing

Add scaling support (aka letterboxing)
This commit is contained in:
Dan Balasescu 2019-01-08 16:52:58 +09:00 committed by GitHub
commit 2ecbb23b63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 336 additions and 34 deletions

View File

@ -3,6 +3,7 @@
using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring;
@ -96,15 +97,25 @@ 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)
public OsuConfigManager(Storage storage)
: base(storage)
{
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled"))
new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")),
new TrackedSetting<ScalingMode>(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
};
}
@ -151,6 +162,11 @@ namespace osu.Game.Configuration
BeatmapHitsounds,
IncreaseFirstObjectVisibility,
ScoreDisplayMode,
ExternalLinkWarning
ExternalLinkWarning,
Scaling,
ScalingPositionX,
ScalingPositionY,
ScalingSizeX,
ScalingSizeY
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
namespace osu.Game.Configuration
{
public enum ScalingMode
{
Off,
Everything,
[Description("Excluding overlays")]
ExcludeOverlays,
Gameplay,
}
}

View File

@ -0,0 +1,120 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// Handles user-defined scaling, allowing application at multiple levels defined by <see cref="ScalingMode"/>.
/// </summary>
public class ScalingContainer : Container
{
private Bindable<float> sizeX;
private Bindable<float> sizeY;
private Bindable<float> posX;
private Bindable<float> posY;
private readonly ScalingMode? targetMode;
private Bindable<ScalingMode> scalingMode;
private readonly Container content;
protected override Container<Drawable> Content => content;
private readonly Container sizableContainer;
private Drawable backgroundLayer;
/// <summary>
/// Create a new instance.
/// </summary>
/// <param name="targetMode">The mode which this container should be handling. Handles all modes if null.</param>
public ScalingContainer(ScalingMode? targetMode = null)
{
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<ScalingMode>(OsuSetting.Scaling);
scalingMode.ValueChanged += _ => updateSize();
sizeX = config.GetBindable<float>(OsuSetting.ScalingSizeX);
sizeX.ValueChanged += _ => updateSize();
sizeY = config.GetBindable<float>(OsuSetting.ScalingSizeY);
sizeY.ValueChanged += _ => updateSize();
posX = config.GetBindable<float>(OsuSetting.ScalingPositionX);
posX.ValueChanged += _ => updateSize();
posY = config.GetBindable<float>(OsuSetting.ScalingPositionY);
posY.ValueChanged += _ => updateSize();
}
protected override void LoadComplete()
{
base.LoadComplete();
updateSize();
sizableContainer.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 scaling = targetMode == null || scalingMode.Value == targetMode;
var targetSize = scaling ? new Vector2(sizeX, sizeY) : Vector2.One;
var targetPosition = scaling ? new Vector2(posX, posY) * (Vector2.One - targetSize) : Vector2.Zero;
bool requiresMasking = scaling && targetSize != Vector2.One;
if (requiresMasking)
sizableContainer.Masking = true;
sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart);
sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
}
}
}

View File

@ -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);

View File

@ -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()
{
@ -685,7 +692,7 @@ namespace osu.Game
ruleset.Disabled = applyBeatmapRulesetRestrictions;
Beatmap.Disabled = applyBeatmapRulesetRestrictions;
mainContent.Padding = new MarginPadding { Top = ToolbarOffset };
screenContainer.Padding = new MarginPadding { Top = ToolbarOffset };
MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
}

View File

@ -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();

View File

@ -6,9 +6,14 @@ using System.Drawing;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osuTK.Graphics;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
@ -16,20 +21,33 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
protected override string Header => "Layout";
private FillFlowContainer<SettingsSlider<float>> scalingSettings;
private Bindable<ScalingMode> scalingMode;
private Bindable<Size> sizeFullscreen;
private OsuGameBase game;
private SettingsDropdown<Size> resolutionDropdown;
private SettingsEnumDropdown<WindowMode> windowModeDropdown;
private Bindable<float> scalingPositionX;
private Bindable<float> scalingPositionY;
private Bindable<float> scalingSizeX;
private Bindable<float> scalingSizeY;
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;
scalingMode = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling);
sizeFullscreen = config.GetBindable<Size>(FrameworkSetting.SizeFullscreen);
scalingSizeX = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeX);
scalingSizeY = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeY);
scalingPositionX = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX);
scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY);
Container resolutionSettingsContainer;
@ -45,8 +63,51 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
new SettingsEnumDropdown<ScalingMode>
{
LabelText = "Scaling",
Bindable = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
},
scalingSettings = new FillFlowContainer<SettingsSlider<float>>
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
AutoSizeDuration = transition_duration,
AutoSizeEasing = Easing.OutQuint,
Masking = true,
Children = new []
{
new SettingsSlider<float>
{
LabelText = "Horizontal position",
Bindable = scalingPositionX,
KeyboardStep = 0.01f
},
new SettingsSlider<float>
{
LabelText = "Vertical position",
Bindable = scalingPositionY,
KeyboardStep = 0.01f
},
new SettingsSlider<float>
{
LabelText = "Horizontal scale",
Bindable = scalingSizeX,
KeyboardStep = 0.01f
},
new SettingsSlider<float>
{
LabelText = "Vertical scale",
Bindable = scalingSizeY,
KeyboardStep = 0.01f
},
}
},
};
scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable));
var resolutions = getResolutions();
if (resolutions.Count > 1)
@ -70,6 +131,45 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
resolutionDropdown.Hide();
}, true);
}
scalingMode.BindValueChanged(mode =>
{
scalingSettings.ClearTransforms();
scalingSettings.AutoSizeAxes = mode != ScalingMode.Off ? Axes.Y : Axes.None;
if (mode == ScalingMode.Off)
scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
scalingSettings.ForEach(s => s.TransferValueOnCommit = mode == ScalingMode.Everything);
}, true);
}
/// <summary>
/// Create a delayed bindable which only updates when a condition is met.
/// </summary>
/// <param name="bindable">The config bindable.</param>
/// <returns>A bindable which will propagate updates with a delay.</returns>
private void bindPreviewEvent(Bindable<float> bindable)
{
bindable.ValueChanged += v =>
{
switch (scalingMode.Value)
{
case ScalingMode.Gameplay:
showPreview();
break;
}
};
}
private Drawable preview;
private void showPreview()
{
if (preview?.IsAlive != true)
game.Add(preview = new ScalingPreview());
preview.FadeOutFromOne(1500);
preview.Expire();
}
private IReadOnlyList<Size> getResolutions()
@ -89,6 +189,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
return resolutions;
}
private class ScalingPreview : ScalingContainer
{
public ScalingPreview()
{
Child = new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
};
}
}
private class ResolutionSettingsDropdown : SettingsDropdown<Size>
{
protected override OsuDropdown<Size> CreateDropdown() => new ResolutionDropdownControl { Items = Items };

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
@ -23,14 +22,16 @@ namespace osu.Game.Overlays.Settings
RelativeSizeAxes = Axes.X
};
public float KeyboardStep;
[BackgroundDependencyLoader]
private void load()
public bool TransferValueOnCommit
{
var slider = Control as U;
if (slider != null)
slider.KeyboardStep = KeyboardStep;
get => ((U)Control).TransferValueOnCommit;
set => ((U)Control).TransferValueOnCommit = value;
}
public float KeyboardStep
{
get => ((U)Control).KeyboardStep;
set => ((U)Control).KeyboardStep = value;
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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 BackgroundScreenBlack : BackgroundScreen
{
public BackgroundScreenBlack()
{
Child = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
};
}
protected override void OnEntering(Screen last)
{
Show();
}
}
}

View File

@ -1,9 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Screens.Backgrounds
{
public class BackgroundScreenEmpty : BackgroundScreen
{
}
}

View File

@ -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<bool> menuVoice;
private Bindable<bool> menuMusic;

View File

@ -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,

View File

@ -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,13 @@ 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 +195,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