// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Drawing; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; 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.Framework.Platform; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; namespace osu.Game.Overlays.Settings.Sections.Graphics { public class LayoutSettings : SettingsSubsection { protected override string Header => "Layout"; private FillFlowContainer> scalingSettings; private Bindable scalingMode; private Bindable sizeFullscreen; private readonly IBindableList windowModes = new BindableList(); [Resolved] private OsuGameBase game { get; set; } private SettingsDropdown resolutionDropdown; private SettingsDropdown windowModeDropdown; private Bindable scalingPositionX; private Bindable scalingPositionY; private Bindable scalingSizeX; private Bindable scalingSizeY; private const int transition_duration = 400; [BackgroundDependencyLoader] private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, GameHost host) { scalingMode = osuConfig.GetBindable(OsuSetting.Scaling); sizeFullscreen = config.GetBindable(FrameworkSetting.SizeFullscreen); scalingSizeX = osuConfig.GetBindable(OsuSetting.ScalingSizeX); scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); if (host.Window != null) windowModes.BindTo(host.Window.SupportedWindowModes); Container resolutionSettingsContainer; Children = new Drawable[] { windowModeDropdown = new SettingsDropdown { LabelText = "Screen mode", Current = config.GetBindable(FrameworkSetting.WindowMode), ItemSource = windowModes, }, resolutionSettingsContainer = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, new SettingsSlider { LabelText = "UI Scaling", TransferValueOnCommit = true, Current = osuConfig.GetBindable(OsuSetting.UIScale), KeyboardStep = 0.01f, Keywords = new[] { "scale", "letterbox" }, }, new SettingsEnumDropdown { LabelText = "Screen Scaling", Current = osuConfig.GetBindable(OsuSetting.Scaling), Keywords = new[] { "scale", "letterbox" }, }, scalingSettings = new FillFlowContainer> { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, AutoSizeDuration = transition_duration, AutoSizeEasing = Easing.OutQuint, Masking = true, Children = new[] { new SettingsSlider { LabelText = "Horizontal position", Current = scalingPositionX, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Vertical position", Current = scalingPositionY, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Horizontal scale", Current = scalingSizeX, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Vertical scale", Current = scalingSizeY, KeyboardStep = 0.01f, DisplayAsPercentage = true }, } }, }; scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); var resolutions = getResolutions(); if (resolutions.Count > 1) { resolutionSettingsContainer.Child = resolutionDropdown = new ResolutionSettingsDropdown { LabelText = "Resolution", ShowsDefaultIndicator = false, Items = resolutions, Current = sizeFullscreen }; windowModeDropdown.Current.BindValueChanged(mode => { if (mode.NewValue == WindowMode.Fullscreen) { resolutionDropdown.Show(); sizeFullscreen.TriggerChange(); } else resolutionDropdown.Hide(); }, true); } scalingMode.BindValueChanged(mode => { scalingSettings.ClearTransforms(); scalingSettings.AutoSizeAxes = mode.NewValue != ScalingMode.Off ? Axes.Y : Axes.None; if (mode.NewValue == ScalingMode.Off) scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything); }, true); windowModes.CollectionChanged += (sender, args) => windowModesChanged(); windowModesChanged(); } private void windowModesChanged() { if (windowModes.Count > 1) windowModeDropdown.Show(); else windowModeDropdown.Hide(); } /// /// Create a delayed bindable which only updates when a condition is met. /// /// The config bindable. /// A bindable which will propagate updates with a delay. private void bindPreviewEvent(Bindable bindable) { bindable.ValueChanged += _ => { 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 getResolutions() { var resolutions = new List { new Size(9999, 9999) }; var currentDisplay = game.Window?.CurrentDisplayBindable.Value; if (currentDisplay != null) { resolutions.AddRange(currentDisplay.DisplayModes .Where(m => m.Size.Width >= 800 && m.Size.Height >= 600) .OrderByDescending(m => m.Size.Width) .ThenByDescending(m => m.Size.Height) .Select(m => m.Size) .Distinct()); } return resolutions; } private class ScalingPreview : ScalingContainer { public ScalingPreview() { Child = new Box { Colour = Color4.White, RelativeSizeAxes = Axes.Both, Alpha = 0.5f, }; } } private class UIScaleSlider : OsuSliderBar { public override string TooltipText => base.TooltipText + "x"; } private class ResolutionSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl(); private class ResolutionDropdownControl : DropdownControl { protected override string GenerateItemText(Size item) { if (item == new Size(9999, 9999)) return "Default"; return $"{item.Width}x{item.Height}"; } } } } }