// 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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// /// A blueprint container generally displayed as an overlay to a ruleset's playfield. /// public class ComposeBlueprintContainer : BlueprintContainer { [Resolved] private HitObjectComposer composer { get; set; } private PlacementBlueprint currentPlacement; private readonly Container placementBlueprintContainer; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private InputManager inputManager; private readonly IEnumerable drawableHitObjects; public ComposeBlueprintContainer(IEnumerable drawableHitObjects) { this.drawableHitObjects = drawableHitObjects; placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }; } [BackgroundDependencyLoader] private void load() { AddInternal(placementBlueprintContainer); } protected override void LoadComplete() { base.LoadComplete(); inputManager = GetContainingInputManager(); Beatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateTogglesFromSelection(); // the updated object may be in the selection Beatmap.HitObjectUpdated += _ => updateTogglesFromSelection(); NewCombo.ValueChanged += combo => { if (Beatmap.SelectedHitObjects.Count > 0) { foreach (var h in Beatmap.SelectedHitObjects) { if (h is IHasComboInformation c) { c.NewCombo = combo.NewValue; Beatmap.UpdateHitObject(h); } } } else if (currentPlacement != null) { // update placement object from toggle if (currentPlacement.HitObject is IHasComboInformation c) c.NewCombo = combo.NewValue; } }; } private void updateTogglesFromSelection() => NewCombo.Value = Beatmap.SelectedHitObjects.OfType().All(c => c.NewCombo); public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; public virtual IEnumerable> Toggles => new[] { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. NewCombo }; #region Placement /// /// Refreshes the current placement tool. /// private void refreshTool() { removePlacement(); createPlacement(); } private void updatePlacementPosition() { var snapResult = composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); currentPlacement.UpdatePosition(snapResult); } #endregion protected override void Update() { base.Update(); if (composer.CursorInPlacementArea) createPlacement(); else if (currentPlacement?.PlacementActive == false) removePlacement(); if (currentPlacement != null) { updatePlacementPosition(); } } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { var drawable = drawableHitObjects.FirstOrDefault(d => d.HitObject == hitObject); if (drawable == null) return null; return CreateBlueprintFor(drawable); } public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; protected override void AddBlueprintFor(HitObject hitObject) { refreshTool(); base.AddBlueprintFor(hitObject); } private void createPlacement() { if (currentPlacement != null) return; var blueprint = CurrentTool?.CreatePlacementBlueprint(); if (blueprint != null) { placementBlueprintContainer.Child = currentPlacement = blueprint; // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame updatePlacementPosition(); } } private void removePlacement() { if (currentPlacement == null) return; currentPlacement.EndPlacement(false); currentPlacement.Expire(); currentPlacement = null; } private HitObjectCompositionTool currentTool; /// /// The current placement tool. /// public HitObjectCompositionTool CurrentTool { get => currentTool; set { if (currentTool == value) return; currentTool = value; refreshTool(); } } } }