// 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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; namespace osu.Game.Overlays.Settings.Sections { public class SkinSection : SettingsSection { private SkinSettingsDropdown skinDropdown; public override string Header => "Skin"; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.PaintBrush }; private readonly Bindable dropdownBindable = new Bindable { Default = SkinInfo.Default }; private readonly Bindable configBindable = new Bindable(); private static readonly SkinInfo random_skin_info = new SkinInfo { ID = SkinInfo.RANDOM_SKIN, Name = "", }; private List skinItems; private int firstNonDefaultSkinIndex { get { var index = skinItems.FindIndex(s => s.ID > 0); if (index < 0) index = skinItems.Count; return index; } } [Resolved] private SkinManager skins { get; set; } private IBindable> managerUpdated; private IBindable> managerRemoved; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { FlowContent.Spacing = new Vector2(0, 5); Children = new Drawable[] { skinDropdown = new SkinSettingsDropdown(), new ExportSkinButton(), new SettingsSlider { LabelText = "Gameplay cursor size", Current = config.GetBindable(OsuSetting.GameplayCursorSize), KeyboardStep = 0.01f }, new SettingsCheckbox { LabelText = "Adjust gameplay cursor size based on current beatmap", Current = config.GetBindable(OsuSetting.AutoCursorSize) }, new SettingsCheckbox { LabelText = "Beatmap skins", Current = config.GetBindable(OsuSetting.BeatmapSkins) }, new SettingsCheckbox { LabelText = "Beatmap colours", Current = config.GetBindable(OsuSetting.BeatmapColours) }, new SettingsCheckbox { LabelText = "Beatmap hitsounds", Current = config.GetBindable(OsuSetting.BeatmapHitsounds) }, }; managerUpdated = skins.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(itemUpdated); managerRemoved = skins.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); config.BindWith(OsuSetting.Skin, configBindable); skinDropdown.Current = dropdownBindable; updateItems(); // Todo: This should not be necessary when OsuConfigManager is databased if (skinDropdown.Items.All(s => s.ID != configBindable.Value)) configBindable.Value = 0; configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig), true); dropdownBindable.BindValueChanged(skin => { if (skin.NewValue == random_skin_info) { skins.SelectRandomSkin(); return; } configBindable.Value = skin.NewValue.ID; }); } private void updateSelectedSkinFromConfig() { int id = configBindable.Value; var skin = skinDropdown.Items.FirstOrDefault(s => s.ID == id); if (skin == null) { // there may be a thread race condition where an item is selected that hasn't yet been added to the dropdown. // to avoid adding complexity, let's just ensure the item is added so we can perform the selection. skin = skins.Query(s => s.ID == id); addItem(skin); } dropdownBindable.Value = skin; } private void updateItems() { skinItems = skins.GetAllUsableSkins(); skinItems.Insert(firstNonDefaultSkinIndex, random_skin_info); sortUserSkins(skinItems); skinDropdown.Items = skinItems; } private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) Schedule(() => addItem(item)); } private void addItem(SkinInfo item) { List newDropdownItems = skinDropdown.Items.Where(i => !i.Equals(item)).Append(item).ToList(); sortUserSkins(newDropdownItems); skinDropdown.Items = newDropdownItems; } private void itemRemoved(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != item.ID).ToArray()); } private void sortUserSkins(List skinsList) { // Sort user skins separately from built-in skins skinsList.Sort(firstNonDefaultSkinIndex, skinsList.Count - firstNonDefaultSkinIndex, Comparer.Create((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase))); } private class SkinSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new SkinDropdownControl(); private class SkinDropdownControl : DropdownControl { protected override LocalisableString GenerateItemText(SkinInfo item) => item.ToString(); } } private class ExportSkinButton : SettingsButton { [Resolved] private SkinManager skins { get; set; } private Bindable currentSkin; [BackgroundDependencyLoader] private void load() { Text = "Export selected skin"; Action = export; currentSkin = skins.CurrentSkin.GetBoundCopy(); currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true); } private void export() { try { skins.Export(currentSkin.Value.SkinInfo); } catch (Exception e) { Logger.Log($"Could not export current skin: {e.Message}", level: LogLevel.Error); } } } } }