// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Tournament.IPC; using osuTK; using osuTK.Graphics; namespace osu.Game.Tournament.Screens { public class SetupScreen : TournamentScreen, IProvideVideo { private FillFlowContainer fillFlow; private LoginOverlay loginOverlay; private ResolutionSelector resolution; [Resolved] private MatchIPCInfo ipc { get; set; } [Resolved] private IAPIProvider api { get; set; } [Resolved] private RulesetStore rulesets { get; set; } private Bindable windowSize; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); InternalChild = fillFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Padding = new MarginPadding(10), Spacing = new Vector2(10), }; api.LocalUser.BindValueChanged(_ => Schedule(reload)); reload(); } [Resolved] private Framework.Game game { get; set; } private void reload() { var fileBasedIpc = ipc as FileBasedIPC; fillFlow.Children = new Drawable[] { new ActionableInfo { Label = "Current IPC source", ButtonText = "Refresh", Action = () => { fileBasedIpc?.LocateStableStorage(); reload(); }, Value = fileBasedIpc?.Storage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.Storage == null, Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install." }, new ActionableInfo { Label = "Current User", ButtonText = "Change Login", Action = () => { api.Logout(); if (loginOverlay == null) { AddInternal(loginOverlay = new LoginOverlay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }); } loginOverlay.State.Value = Visibility.Visible; }, Value = api?.LocalUser.Value.Username, Failing = api?.IsLoggedIn != true, Description = "In order to access the API and display metadata, a login is required." }, new LabelledDropdown { Label = "Ruleset", Description = "Decides what stats are displayed and which ranks are retrieved for players", Items = rulesets.AvailableRulesets, Current = LadderInfo.Ruleset, }, resolution = new ResolutionSelector { Label = "Stream area resolution", ButtonText = "Set height", Action = height => { windowSize.Value = new Size((int)(height * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), height); } }, }; } private const float aspect_ratio = 16f / 9f; protected override void Update() { base.Update(); resolution.Value = $"{ScreenSpaceDrawQuad.Width:N0}x{ScreenSpaceDrawQuad.Height:N0}"; } public class LabelledDropdown : LabelledComponent, T> { public LabelledDropdown() : base(true) { } public IEnumerable Items { get => Component.Items; set => Component.Items = value; } protected override OsuDropdown CreateComponent() => new OsuDropdown { RelativeSizeAxes = Axes.X, Width = 0.5f, }; } private class ActionableInfo : LabelledDrawable { private OsuButton button; public ActionableInfo() : base(true) { } public string ButtonText { set => button.Text = value; } public string Value { set => valueText.Text = value; } public bool Failing { set => valueText.Colour = value ? Color4.Red : Color4.White; } public Action Action; private TournamentSpriteText valueText; protected FillFlowContainer FlowContainer; protected override Drawable CreateComponent() => new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] { valueText = new TournamentSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, FlowContainer = new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, AutoSizeAxes = Axes.Both, Spacing = new Vector2(10, 0), Children = new Drawable[] { button = new TriangleButton { Size = new Vector2(100, 30), Action = () => Action?.Invoke() } } } } }; } private class ResolutionSelector : ActionableInfo { private const int minimum_window_height = 480; private const int maximum_window_height = 2160; public new Action Action; private OsuNumberBox numberBox; protected override Drawable CreateComponent() { var drawable = base.CreateComponent(); FlowContainer.Insert(-1, numberBox = new OsuNumberBox { Text = "1080", Width = 100 }); base.Action = () => { if (string.IsNullOrEmpty(numberBox.Text)) return; // box contains text if (!int.TryParse(numberBox.Text, out var number)) { // at this point, the only reason we can arrive here is if the input number was too big to parse into an int // so clamp to max allowed value number = maximum_window_height; } else { number = Math.Clamp(number, minimum_window_height, maximum_window_height); } // in case number got clamped, reset number in numberBox numberBox.Text = number.ToString(); Action?.Invoke(number); }; return drawable; } } } }