1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-25 08:52:56 +08:00
osu-lazer/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

429 lines
18 KiB
C#
Raw Normal View History

// 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.
2018-04-13 17:19:50 +08:00
using System;
using System.Collections.Generic;
2018-06-10 21:17:57 +08:00
using System.Drawing;
using System.Linq;
using osu.Framework;
2016-11-11 06:40:42 +08:00
using osu.Framework.Allocation;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables;
using osu.Framework.Configuration;
2019-01-08 12:48:38 +08:00
using osu.Framework.Extensions.IEnumerableExtensions;
2016-11-09 11:38:40 +08:00
using osu.Framework.Graphics;
2017-05-23 17:09:32 +08:00
using osu.Framework.Graphics.Containers;
2019-01-04 14:28:35 +08:00
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Platform;
2022-05-26 17:37:04 +08:00
using osu.Framework.Platform.Windows;
2019-01-04 12:29:37 +08:00
using osu.Game.Configuration;
2019-01-04 14:28:35 +08:00
using osu.Game.Graphics.Containers;
2018-11-14 17:02:38 +08:00
using osu.Game.Graphics.UserInterface;
2021-08-11 16:48:37 +08:00
using osu.Game.Localisation;
using osuTK;
2019-01-04 14:28:35 +08:00
using osuTK.Graphics;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public partial class LayoutSettings : SettingsSubsection
{
2021-08-11 16:48:37 +08:00
protected override LocalisableString Header => GraphicsSettingsStrings.LayoutHeader;
2018-04-13 17:19:50 +08:00
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
private SettingsSlider<float> dimSlider = null!;
2018-04-13 17:19:50 +08:00
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
private Bindable<ScalingMode> scalingMode = null!;
private Bindable<Size> sizeFullscreen = null!;
private readonly BindableList<Size> resolutions = new BindableList<Size>(new[] { new Size(9999, 9999) });
2022-05-26 17:37:04 +08:00
private readonly IBindable<FullscreenCapability> fullscreenCapability = new Bindable<FullscreenCapability>(FullscreenCapability.Capable);
2018-04-13 17:19:50 +08:00
2020-02-14 21:14:00 +08:00
[Resolved]
private OsuGameBase game { get; set; } = null!;
2020-02-14 21:14:00 +08:00
2022-05-26 17:37:04 +08:00
[Resolved]
private GameHost host { get; set; } = null!;
2022-05-26 17:37:04 +08:00
private IWindow? window;
private SettingsDropdown<Size> resolutionDropdown = null!;
private SettingsDropdown<Display> displayDropdown = null!;
private SettingsDropdown<WindowMode> windowModeDropdown = null!;
private SettingsCheckbox minimiseOnFocusLossCheckbox = null!;
private SettingsCheckbox safeAreaConsiderationsCheckbox = null!;
private Bindable<float> scalingPositionX = null!;
private Bindable<float> scalingPositionY = null!;
private Bindable<float> scalingSizeX = null!;
private Bindable<float> scalingSizeY = null!;
private Bindable<float> scalingBackgroundDim = null!;
private const int transition_duration = 400;
2018-04-13 17:19:50 +08:00
[BackgroundDependencyLoader]
2020-02-14 21:14:00 +08:00
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, GameHost host)
2016-11-09 11:38:40 +08:00
{
window = host.Window;
2019-01-04 12:29:37 +08:00
scalingMode = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling);
2018-06-10 21:17:57 +08:00
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);
2023-03-29 21:55:25 +08:00
scalingBackgroundDim = osuConfig.GetBindable<float>(OsuSetting.ScalingBackgroundDim);
2018-06-10 21:17:57 +08:00
if (window != null)
{
currentDisplay.BindTo(window.CurrentDisplayBindable);
window.DisplaysChanged += onDisplaysChanged;
}
2018-09-19 16:52:35 +08:00
if (host.Renderer is IWindowsRenderer windowsRenderer)
fullscreenCapability.BindTo(windowsRenderer.FullscreenCapability);
2022-05-26 17:37:04 +08:00
2016-11-11 06:40:42 +08:00
Children = new Drawable[]
2016-11-09 11:38:40 +08:00
{
windowModeDropdown = new SettingsDropdown<WindowMode>
2016-11-11 06:40:42 +08:00
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.ScreenMode,
2023-04-02 19:16:14 +08:00
Items = window?.SupportedWindowModes,
2023-04-03 01:23:18 +08:00
CanBeShown = { Value = window?.SupportedWindowModes.Count() > 1 },
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
2016-11-11 06:40:42 +08:00
},
displayDropdown = new DisplaySettingsDropdown
{
LabelText = GraphicsSettingsStrings.Display,
Items = window?.Displays,
Current = currentDisplay,
},
resolutionDropdown = new ResolutionSettingsDropdown
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.Resolution,
ShowsDefaultIndicator = false,
ItemSource = resolutions,
Current = sizeFullscreen
},
minimiseOnFocusLossCheckbox = new SettingsCheckbox
{
LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss,
Current = config.GetBindable<bool>(FrameworkSetting.MinimiseOnFocusLossInFullscreen),
2024-01-15 13:12:39 +08:00
Keywords = new[] { "alt-tab", "minimize", "focus", "hide" },
},
safeAreaConsiderationsCheckbox = new SettingsCheckbox
{
2024-02-06 01:05:21 +08:00
LabelText = GraphicsSettingsStrings.ShrinkGameToSafeArea,
Current = osuConfig.GetBindable<bool>(OsuSetting.SafeAreaConsiderations),
},
new SettingsSlider<float, UIScaleSlider>
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.UIScaling,
TransferValueOnCommit = true,
Current = osuConfig.GetBindable<float>(OsuSetting.UIScale),
2019-11-21 00:27:34 +08:00
KeyboardStep = 0.01f,
2019-11-21 21:35:15 +08:00
Keywords = new[] { "scale", "letterbox" },
},
2019-01-04 12:29:37 +08:00
new SettingsEnumDropdown<ScalingMode>
2016-11-11 06:40:42 +08:00
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.ScreenScaling,
Current = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
2019-11-21 21:35:15 +08:00
Keywords = new[] { "scale", "letterbox" },
2016-11-11 06:40:42 +08:00
},
2019-01-08 12:48:38 +08:00
scalingSettings = new FillFlowContainer<SettingsSlider<float>>
{
2017-05-23 17:09:32 +08:00
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
2017-05-23 17:09:32 +08:00
Masking = true,
2019-02-28 12:31:40 +08:00
Children = new[]
2017-05-23 17:09:32 +08:00
{
2019-01-04 12:29:37 +08:00
new SettingsSlider<float>
2017-05-23 17:09:32 +08:00
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.HorizontalPosition,
Keywords = new[] { "screen", "scaling" },
Current = scalingPositionX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
2017-05-23 17:09:32 +08:00
},
2019-01-04 12:29:37 +08:00
new SettingsSlider<float>
2017-05-23 17:09:32 +08:00
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.VerticalPosition,
Keywords = new[] { "screen", "scaling" },
Current = scalingPositionY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
2019-01-04 12:29:37 +08:00
},
new SettingsSlider<float>
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.HorizontalScale,
Keywords = new[] { "screen", "scaling" },
Current = scalingSizeX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
2019-01-04 12:29:37 +08:00
},
new SettingsSlider<float>
{
2021-08-11 16:48:37 +08:00
LabelText = GraphicsSettingsStrings.VerticalScale,
Keywords = new[] { "screen", "scaling" },
Current = scalingSizeY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
2017-05-23 17:09:32 +08:00
},
dimSlider = new SettingsSlider<float>
{
LabelText = GameplaySettingsStrings.BackgroundDim,
Current = scalingBackgroundDim,
KeyboardStep = 0.01f,
2023-03-29 21:59:54 +08:00
DisplayAsPercentage = true,
},
2017-05-23 17:09:32 +08:00
}
},
2016-11-11 06:40:42 +08:00
};
2022-05-26 17:37:04 +08:00
fullscreenCapability.BindValueChanged(_ => Schedule(updateScreenModeWarning), true);
}
protected override void LoadComplete()
{
base.LoadComplete();
scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
2022-06-24 20:25:23 +08:00
windowModeDropdown.Current.BindValueChanged(_ =>
2021-05-04 15:59:48 +08:00
{
updateDisplaySettingsVisibility();
2022-05-26 17:37:04 +08:00
updateScreenModeWarning();
2021-05-04 15:59:48 +08:00
}, true);
2020-12-22 16:34:51 +08:00
currentDisplay.BindValueChanged(display => Schedule(() =>
{
if (display.NewValue == null)
{
resolutions.Clear();
return;
}
resolutions.ReplaceRange(1, resolutions.Count - 1, display.NewValue.DisplayModes
.Where(m => m.Size.Width >= 800 && m.Size.Height >= 600)
.OrderByDescending(m => Math.Max(m.Size.Height, m.Size.Width))
.Select(m => m.Size)
.Distinct());
updateDisplaySettingsVisibility();
}), true);
2022-06-24 20:25:23 +08:00
scalingMode.BindValueChanged(_ =>
{
2019-01-04 12:29:37 +08:00
scalingSettings.ClearTransforms();
scalingSettings.AutoSizeDuration = transition_duration;
scalingSettings.AutoSizeEasing = Easing.OutQuint;
2018-04-13 17:19:50 +08:00
updateScalingModeVisibility();
});
2019-01-08 12:48:38 +08:00
// initial update bypasses transforms
updateScalingModeVisibility();
void updateScalingModeVisibility()
{
if (scalingMode.Value == ScalingMode.Off)
scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
scalingSettings.AutoSizeAxes = scalingMode.Value != ScalingMode.Off ? Axes.Y : Axes.None;
scalingSettings.ForEach(s =>
{
if (s == dimSlider)
{
s.CanBeShown.Value = scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays;
}
2023-05-02 13:31:39 +08:00
else
{
s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything;
s.CanBeShown.Value = scalingMode.Value != ScalingMode.Off;
}
});
}
2017-03-21 23:24:19 +08:00
}
private void onDisplaysChanged(IEnumerable<Display> displays)
{
Scheduler.AddOnce(d =>
{
if (!displayDropdown.Items.SequenceEqual(d, DisplayListComparer.DEFAULT))
displayDropdown.Items = d;
updateDisplaySettingsVisibility();
}, displays);
}
private void updateDisplaySettingsVisibility()
{
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
minimiseOnFocusLossCheckbox.CanBeShown.Value = RuntimeInfo.IsDesktop && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
}
2022-05-26 17:37:04 +08:00
private void updateScreenModeWarning()
{
2022-06-03 15:51:20 +08:00
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS)
2022-05-26 17:37:04 +08:00
{
2022-06-03 15:51:20 +08:00
if (windowModeDropdown.Current.Value == WindowMode.Fullscreen)
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true);
else
windowModeDropdown.ClearNoticeText();
2022-05-26 17:37:04 +08:00
return;
}
2022-06-03 15:51:20 +08:00
if (windowModeDropdown.Current.Value != WindowMode.Fullscreen)
{
2022-06-03 15:51:20 +08:00
windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true);
return;
}
if (host.Renderer is IWindowsRenderer)
2022-05-26 17:37:04 +08:00
{
switch (fullscreenCapability.Value)
{
case FullscreenCapability.Unknown:
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.CheckingForFullscreenCapabilities, true);
break;
2022-05-26 17:37:04 +08:00
case FullscreenCapability.Capable:
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen);
break;
2022-05-26 17:37:04 +08:00
case FullscreenCapability.Incapable:
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen, true);
break;
}
}
else
{
// We can only detect exclusive fullscreen status on windows currently.
windowModeDropdown.ClearNoticeText();
2022-05-26 17:37:04 +08:00
}
}
2019-01-08 12:48:38 +08:00
private void bindPreviewEvent(Bindable<float> bindable)
{
bindable.ValueChanged += _ =>
{
2019-01-04 14:28:35 +08:00
switch (scalingMode.Value)
{
case ScalingMode.Gameplay:
showPreview();
break;
}
};
}
private Drawable? preview;
2019-02-28 12:31:40 +08:00
2019-01-04 14:28:35 +08:00
private void showPreview()
{
if (preview?.IsAlive != true)
game.Add(preview = new ScalingPreview());
preview.FadeOutFromOne(1500);
preview.Expire();
}
protected override void Dispose(bool isDisposing)
{
if (window != null)
window.DisplaysChanged -= onDisplaysChanged;
base.Dispose(isDisposing);
}
2019-01-04 14:28:35 +08:00
private partial class ScalingPreview : ScalingContainer
{
public ScalingPreview()
{
Child = new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
};
}
}
private partial class UIScaleSlider : RoundedSliderBar<float>
{
public override LocalisableString TooltipText => base.TooltipText + "x";
}
private partial class DisplaySettingsDropdown : SettingsDropdown<Display>
{
protected override OsuDropdown<Display> CreateDropdown() => new DisplaySettingsDropdownControl();
private partial class DisplaySettingsDropdownControl : DropdownControl
{
protected override LocalisableString GenerateItemText(Display item)
{
return $"{item.Index}: {item.Name} ({item.Bounds.Width}x{item.Bounds.Height})";
}
}
}
2018-11-14 17:02:38 +08:00
private partial class ResolutionSettingsDropdown : SettingsDropdown<Size>
{
protected override OsuDropdown<Size> CreateDropdown() => new ResolutionDropdownControl();
2018-11-14 17:02:38 +08:00
private partial class ResolutionDropdownControl : DropdownControl
{
protected override LocalisableString GenerateItemText(Size item)
2018-11-14 17:02:38 +08:00
{
if (item == new Size(9999, 9999))
2021-08-11 16:48:37 +08:00
return CommonStrings.Default;
2019-02-28 12:31:40 +08:00
2018-11-14 17:02:38 +08:00
return $"{item.Width}x{item.Height}";
}
}
}
/// <summary>
/// Contrary to <see cref="Display.Equals(osu.Framework.Platform.Display?)"/>, this comparer disregards the value of <see cref="Display.Bounds"/>.
/// We want to just show a list of displays, and for the purposes of settings we don't care about their bounds when it comes to the list.
/// However, <see cref="IWindow.DisplaysChanged"/> fires even if only the resolution of the current display was changed
/// (because it causes the bounds of all displays to also change).
/// We're not interested in those changes, so compare only the rest that we actually care about.
/// This helps to avoid a bindable/event feedback loop, in which a resolution change
/// would trigger a display "change", which would in turn reset resolution again.
/// </summary>
private class DisplayListComparer : IEqualityComparer<Display>
{
public static readonly DisplayListComparer DEFAULT = new DisplayListComparer();
public bool Equals(Display? x, Display? y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
return x.Index == y.Index
&& x.Name == y.Name
&& x.DisplayModes.SequenceEqual(y.DisplayModes);
}
public int GetHashCode(Display obj)
{
var hashCode = new HashCode();
hashCode.Add(obj.Index);
hashCode.Add(obj.Name);
hashCode.Add(obj.DisplayModes.Length);
foreach (var displayMode in obj.DisplayModes)
hashCode.Add(displayMode);
return hashCode.ToHashCode();
}
}
}
}