2019-01-24 08:43:03 +00:00
|
|
|
// 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.
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
using osu.Framework.Allocation;
|
2019-02-21 10:04:31 +00:00
|
|
|
using osu.Framework.Bindables;
|
2019-01-04 04:29:37 +00:00
|
|
|
using osu.Framework.Graphics;
|
|
|
|
using osu.Framework.Graphics.Containers;
|
2022-03-02 11:04:53 +00:00
|
|
|
using osu.Framework.Graphics.Primitives;
|
2019-03-25 04:28:51 +00:00
|
|
|
using osu.Framework.Screens;
|
2019-01-04 04:29:37 +00:00
|
|
|
using osu.Game.Configuration;
|
2019-03-24 21:49:57 +00:00
|
|
|
using osu.Game.Screens;
|
|
|
|
using osu.Game.Screens.Backgrounds;
|
2019-01-04 04:29:37 +00:00
|
|
|
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;
|
|
|
|
|
2022-02-04 10:19:44 +00:00
|
|
|
private Bindable<MarginPadding> safeAreaPadding;
|
|
|
|
|
2019-01-04 06:28:35 +00:00
|
|
|
private readonly ScalingMode? targetMode;
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
private Bindable<ScalingMode> scalingMode;
|
|
|
|
|
|
|
|
private readonly Container content;
|
|
|
|
protected override Container<Drawable> Content => content;
|
|
|
|
|
2019-01-16 08:21:26 +00:00
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
|
|
|
|
2019-01-04 04:29:37 +00:00
|
|
|
private readonly Container sizableContainer;
|
|
|
|
|
2019-03-25 04:28:51 +00:00
|
|
|
private BackgroundScreenStack backgroundStack;
|
2019-01-04 04:29:37 +00:00
|
|
|
|
2022-03-02 11:33:28 +00:00
|
|
|
private RectangleF? customRect;
|
|
|
|
private bool customRectIsRelativePosition;
|
2021-04-29 08:19:47 +00:00
|
|
|
|
|
|
|
/// <summary>
|
2022-03-02 11:04:53 +00:00
|
|
|
/// Set a custom position and scale which overrides any user specification.
|
2021-04-29 08:19:47 +00:00
|
|
|
/// </summary>
|
2022-03-02 11:33:28 +00:00
|
|
|
public void SetCustomRect(RectangleF? rect, bool relativePosition = false)
|
2021-04-29 08:19:47 +00:00
|
|
|
{
|
2022-03-02 11:33:28 +00:00
|
|
|
customRect = rect;
|
|
|
|
customRectIsRelativePosition = relativePosition;
|
2021-04-29 08:19:47 +00:00
|
|
|
|
2022-03-02 11:04:53 +00:00
|
|
|
if (IsLoaded) Scheduler.AddOnce(updateSize);
|
2021-04-29 08:19:47 +00:00
|
|
|
}
|
|
|
|
|
2022-03-02 10:41:47 +00:00
|
|
|
private const float corner_radius = 10;
|
|
|
|
|
2019-01-04 04:29:37 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Create a new instance.
|
|
|
|
/// </summary>
|
2019-01-04 06:28:35 +00:00
|
|
|
/// <param name="targetMode">The mode which this container should be handling. Handles all modes if null.</param>
|
|
|
|
public ScalingContainer(ScalingMode? targetMode = null)
|
2019-01-04 04:29:37 +00:00
|
|
|
{
|
|
|
|
this.targetMode = targetMode;
|
|
|
|
RelativeSizeAxes = Axes.Both;
|
|
|
|
|
2019-01-16 08:21:26 +00:00
|
|
|
InternalChild = sizableContainer = new AlwaysInputContainer
|
2019-01-04 04:29:37 +00:00
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.Both,
|
|
|
|
RelativePositionAxes = Axes.Both,
|
2022-03-02 10:41:47 +00:00
|
|
|
CornerRadius = corner_radius,
|
2019-01-09 10:01:33 +00:00
|
|
|
Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay)
|
2019-01-04 04:29:37 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-01-09 10:01:33 +00:00
|
|
|
private class ScalingDrawSizePreservingFillContainer : DrawSizePreservingFillContainer
|
|
|
|
{
|
|
|
|
private readonly bool applyUIScale;
|
|
|
|
private Bindable<float> uiScale;
|
|
|
|
|
2019-01-16 08:21:26 +00:00
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
|
|
|
|
2019-01-09 10:01:33 +00:00
|
|
|
public ScalingDrawSizePreservingFillContainer(bool applyUIScale)
|
|
|
|
{
|
|
|
|
this.applyUIScale = applyUIScale;
|
|
|
|
}
|
|
|
|
|
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
private void load(OsuConfigManager osuConfig)
|
|
|
|
{
|
|
|
|
if (applyUIScale)
|
|
|
|
{
|
|
|
|
uiScale = osuConfig.GetBindable<float>(OsuSetting.UIScale);
|
|
|
|
uiScale.BindValueChanged(scaleChanged, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-21 09:56:34 +00:00
|
|
|
private void scaleChanged(ValueChangedEvent<float> args)
|
2019-01-09 10:01:33 +00:00
|
|
|
{
|
2019-02-21 09:56:34 +00:00
|
|
|
this.ScaleTo(new Vector2(args.NewValue), 500, Easing.Out);
|
|
|
|
this.ResizeTo(new Vector2(1 / args.NewValue), 500, Easing.Out);
|
2019-01-09 10:01:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:29:37 +00:00
|
|
|
[BackgroundDependencyLoader]
|
2022-02-04 10:19:44 +00:00
|
|
|
private void load(OsuConfigManager config, ISafeArea safeArea)
|
2019-01-04 04:29:37 +00:00
|
|
|
{
|
|
|
|
scalingMode = config.GetBindable<ScalingMode>(OsuSetting.Scaling);
|
2022-02-04 11:33:15 +00:00
|
|
|
scalingMode.ValueChanged += _ => Scheduler.AddOnce(updateSize);
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
sizeX = config.GetBindable<float>(OsuSetting.ScalingSizeX);
|
2022-02-04 11:33:15 +00:00
|
|
|
sizeX.ValueChanged += _ => Scheduler.AddOnce(updateSize);
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
sizeY = config.GetBindable<float>(OsuSetting.ScalingSizeY);
|
2022-02-04 11:33:15 +00:00
|
|
|
sizeY.ValueChanged += _ => Scheduler.AddOnce(updateSize);
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
posX = config.GetBindable<float>(OsuSetting.ScalingPositionX);
|
2022-02-04 11:33:15 +00:00
|
|
|
posX.ValueChanged += _ => Scheduler.AddOnce(updateSize);
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
posY = config.GetBindable<float>(OsuSetting.ScalingPositionY);
|
2022-02-04 11:33:15 +00:00
|
|
|
posY.ValueChanged += _ => Scheduler.AddOnce(updateSize);
|
2022-02-04 07:07:05 +00:00
|
|
|
|
2022-02-04 10:19:44 +00:00
|
|
|
safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy();
|
2022-02-04 11:33:15 +00:00
|
|
|
safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize));
|
2019-01-04 04:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
2022-02-04 13:31:41 +00:00
|
|
|
updateSize();
|
2019-01-04 06:28:35 +00:00
|
|
|
sizableContainer.FinishTransforms();
|
2019-01-04 04:29:37 +00:00
|
|
|
}
|
|
|
|
|
2019-02-21 09:56:34 +00:00
|
|
|
private bool requiresBackgroundVisible => (scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1);
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
private void updateSize()
|
|
|
|
{
|
2022-03-02 11:25:34 +00:00
|
|
|
const float duration = 500;
|
2019-03-25 04:28:51 +00:00
|
|
|
|
2019-01-04 04:29:37 +00:00
|
|
|
if (targetMode == ScalingMode.Everything)
|
|
|
|
{
|
|
|
|
// the top level scaling container manages the background to be displayed while scaling.
|
|
|
|
if (requiresBackgroundVisible)
|
|
|
|
{
|
2019-03-25 04:28:51 +00:00
|
|
|
if (backgroundStack == null)
|
|
|
|
{
|
|
|
|
AddInternal(backgroundStack = new BackgroundScreenStack
|
2019-01-04 04:29:37 +00:00
|
|
|
{
|
|
|
|
Colour = OsuColour.Gray(0.1f),
|
|
|
|
Alpha = 0,
|
|
|
|
Depth = float.MaxValue
|
|
|
|
});
|
2019-03-25 04:28:51 +00:00
|
|
|
|
2019-03-25 04:38:50 +00:00
|
|
|
backgroundStack.Push(new ScalingBackgroundScreen());
|
2019-03-25 04:28:51 +00:00
|
|
|
}
|
|
|
|
|
2022-03-02 11:25:34 +00:00
|
|
|
backgroundStack.FadeIn(duration);
|
2019-01-04 04:29:37 +00:00
|
|
|
}
|
|
|
|
else
|
2022-03-02 11:25:34 +00:00
|
|
|
backgroundStack?.FadeOut(duration);
|
2019-01-04 04:29:37 +00:00
|
|
|
}
|
|
|
|
|
2022-03-02 11:33:28 +00:00
|
|
|
RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One);
|
2022-03-02 11:04:53 +00:00
|
|
|
|
2022-03-02 11:33:28 +00:00
|
|
|
if (customRect != null)
|
2022-03-02 11:04:53 +00:00
|
|
|
{
|
2022-03-02 11:33:28 +00:00
|
|
|
sizableContainer.RelativePositionAxes = customRectIsRelativePosition ? Axes.Both : Axes.None;
|
2022-03-02 11:04:53 +00:00
|
|
|
|
2022-03-02 11:33:28 +00:00
|
|
|
targetRect = customRect.Value;
|
2022-03-02 11:04:53 +00:00
|
|
|
}
|
|
|
|
else if (targetMode == null || scalingMode.Value == targetMode)
|
|
|
|
{
|
|
|
|
sizableContainer.RelativePositionAxes = Axes.Both;
|
|
|
|
|
|
|
|
Vector2 scale = new Vector2(sizeX.Value, sizeY.Value);
|
|
|
|
Vector2 pos = new Vector2(posX.Value, posY.Value) * (Vector2.One - scale);
|
|
|
|
|
2022-03-02 11:33:28 +00:00
|
|
|
targetRect = new RectangleF(pos, scale);
|
2022-03-02 11:04:53 +00:00
|
|
|
}
|
2019-01-04 04:29:37 +00:00
|
|
|
|
2022-03-02 11:33:28 +00:00
|
|
|
bool requiresMasking = targetRect.Size != Vector2.One
|
2022-02-04 07:07:05 +00:00
|
|
|
// For the top level scaling container, for now we apply masking if safe areas are in use.
|
|
|
|
// In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas.
|
2022-02-04 10:19:44 +00:00
|
|
|
|| (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero);
|
2019-01-04 04:29:37 +00:00
|
|
|
|
|
|
|
if (requiresMasking)
|
|
|
|
sizableContainer.Masking = true;
|
|
|
|
|
2022-03-02 11:33:28 +00:00
|
|
|
sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart);
|
|
|
|
sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart);
|
2022-03-02 11:25:34 +00:00
|
|
|
|
|
|
|
// Of note, this will not working great in the case of nested ScalingContainers where multiple are applying corner radius.
|
|
|
|
// There should likely only be masking and corner radius applied at one point in the full game stack to fix this.
|
|
|
|
// An example of how this can occur is it the skin editor is visible and the game screen scaling is set to "Everything".
|
|
|
|
sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None)
|
|
|
|
.OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
|
2019-01-04 04:29:37 +00:00
|
|
|
}
|
2019-01-16 08:21:26 +00:00
|
|
|
|
2019-03-25 04:38:50 +00:00
|
|
|
private class ScalingBackgroundScreen : BackgroundScreenDefault
|
2019-03-25 04:28:51 +00:00
|
|
|
{
|
2021-06-03 05:24:21 +00:00
|
|
|
protected override bool AllowStoryboardBackground => false;
|
|
|
|
|
2019-03-25 04:28:51 +00:00
|
|
|
public override void OnEntering(IScreen last)
|
|
|
|
{
|
|
|
|
this.FadeInFromZero(4000, Easing.OutQuint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-16 08:21:26 +00:00
|
|
|
private class AlwaysInputContainer : Container
|
|
|
|
{
|
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
|
|
|
|
|
|
|
public AlwaysInputContainer()
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.Both;
|
|
|
|
}
|
|
|
|
}
|
2019-01-04 04:29:37 +00:00
|
|
|
}
|
|
|
|
}
|