// 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Overlays.Settings { public abstract class SettingsItem : Container, IFilterable, ISettingsItem, IHasCurrentValue, IHasTooltip { protected abstract Drawable CreateControl(); protected Drawable Control { get; } private IHasCurrentValue controlWithCurrent => Control as IHasCurrentValue; protected override Container Content => FlowContent; protected readonly FillFlowContainer FlowContent; private SpriteText labelText; private readonly GridContainer gridContainer; [CanBeNull] private RestoreDefaultValueButton defaultValueButton; private OsuTextFlowContainer warningText; public bool ShowsDefaultIndicator = true; public LocalisableString TooltipText { get; set; } [Resolved] private OsuColour colours { get; set; } public virtual LocalisableString LabelText { get => labelText?.Text ?? string.Empty; set { if (labelText == null) { // construct lazily for cases where the label is not needed (may be provided by the Control). gridContainer.Content[0][1] = labelText = new OsuSpriteText(); updateDisabled(); } labelText.Text = value; updateLayout(); } } /// /// Text to be displayed at the bottom of this . /// Generally used to recommend the user change their setting as the current one is considered sub-optimal. /// public LocalisableString? WarningText { set { bool hasValue = !string.IsNullOrWhiteSpace(value.ToString()); if (warningText == null) { if (!hasValue) return; // construct lazily for cases where the label is not needed (may be provided by the Control). FlowContent.Add(warningText = new SettingsNoticeText(colours) { Margin = new MarginPadding { Bottom = 5 } }); } warningText.Alpha = hasValue ? 1 : 0; warningText.Text = value.ToString(); // TODO: Remove ToString() call after TextFlowContainer supports localisation (see https://github.com/ppy/osu-framework/issues/4636). } } public virtual Bindable Current { get => controlWithCurrent.Current; set => controlWithCurrent.Current = value; } public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List(Keywords) { LabelText.ToString() }.ToArray(); public IEnumerable Keywords { get; set; } public bool MatchingFilter { set => Alpha = value ? 1 : 0; } public bool FilteringActive { get; set; } public event Action SettingChanged; protected SettingsItem() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; FlowContent = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(0, 10), Child = Control = CreateControl(), }; InternalChild = gridContainer = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize) }, ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, SettingsPanel.CONTENT_MARGINS), new Dimension() }, Content = new[] { new Drawable[2], new Drawable[] { null, FlowContent } } }; // IMPORTANT: all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is // never loaded, but requires bindable storage. if (controlWithCurrent == null) throw new ArgumentException(@$"Control created via {nameof(CreateControl)} must implement {nameof(IHasCurrentValue)}"); controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke(); controlWithCurrent.Current.DisabledChanged += _ => updateDisabled(); } [BackgroundDependencyLoader] private void load() { // intentionally done before LoadComplete to avoid overhead. if (ShowsDefaultIndicator) { defaultValueButton = new RestoreDefaultValueButton { Current = controlWithCurrent.Current, Anchor = Anchor.Centre, Origin = Anchor.Centre }; updateLayout(); } } private void updateLayout() { bool hasLabel = !string.IsNullOrEmpty(labelText?.Text.ToString()); gridContainer.Content[0][0] = null; gridContainer.Content[1][0] = null; gridContainer.Content[hasLabel ? 0 : 1][0] = defaultValueButton; FlowContent.Margin = new MarginPadding { Top = hasLabel ? 10 : 0 }; } private void updateDisabled() { if (labelText != null) labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } } }