// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Collections.Generic; using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit.Compose.Components { public partial class EditorSelectionHandler : SelectionHandler { [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } [BackgroundDependencyLoader] private void load() { createStateBindables(); // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); SelectedItems.CollectionChanged += (_, _) => Scheduler.AddOnce(UpdateTernaryStates); } protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); #region Selection State /// /// The state of "new combo" for all selected hitobjects. /// public readonly Bindable SelectionNewComboState = new Bindable(); /// /// The state of each sample type for all selected hitobjects. Keys match with constant specifications. /// public readonly Dictionary> SelectionSampleStates = new Dictionary>(); /// /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) /// private void createStateBindables() { foreach (string sampleName in HitSampleInfo.AllAdditions) { var bindable = new Bindable { Description = sampleName.Replace("hit", string.Empty).Titleize() }; bindable.ValueChanged += state => { switch (state.NewValue) { case TernaryState.False: RemoveHitSample(sampleName); break; case TernaryState.True: AddHitSample(sampleName); break; } }; SelectionSampleStates[sampleName] = bindable; } // new combo SelectionNewComboState.ValueChanged += state => { switch (state.NewValue) { case TernaryState.False: SetNewCombo(false); break; case TernaryState.True: SetNewCombo(true); break; } }; } /// /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). /// protected virtual void UpdateTernaryStates() { SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); foreach ((string sampleName, var bindable) in SelectionSampleStates) { bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.Any(s => s.Name == sampleName)); } } #endregion #region Ternary state changes /// /// Adds a hit sample to all selected s. /// /// The name of the hit sample. public void AddHitSample(string sampleName) { EditorBeatmap.PerformOnSelection(h => { // Make sure there isn't already an existing sample if (h.Samples.Any(s => s.Name == sampleName)) return; h.Samples.Add(h.GetSampleInfo(sampleName)); EditorBeatmap.Update(h); }); } /// /// Removes a hit sample from all selected s. /// /// The name of the hit sample. public void RemoveHitSample(string sampleName) { EditorBeatmap.PerformOnSelection(h => { h.SamplesBindable.RemoveAll(s => s.Name == sampleName); EditorBeatmap.Update(h); }); } /// /// Set the new combo state of all selected s. /// /// Whether to set or unset. /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { EditorBeatmap.PerformOnSelection(h => { var comboInfo = h as IHasComboInformation; if (comboInfo == null || comboInfo.NewCombo == state) return; comboInfo.NewCombo = state; EditorBeatmap.Update(h); }); } #endregion #region Context Menu /// /// Provide context menu items relevant to current selection. Calling base is not required. /// /// The current selection. /// The relevant menu items. protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) { yield return new TernaryStateToggleMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; } yield return new OsuMenuItem("Sound") { Items = SelectionSampleStates.Select(kvp => new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }; } #endregion } }