// 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.Graphics.Sprites; using osu.Framework.Input; using osu.Game.Graphics.UserInterface; 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 osu.Game.Screens.Edit.Components.TernaryButtons; 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() { TernaryStates = CreateTernaryButtons().ToArray(); AddInternal(placementBlueprintContainer); } protected override void LoadComplete() { base.LoadComplete(); inputManager = GetContainingInputManager(); // updates to selected are handled for us by SelectionHandler. NewCombo.BindTo(SelectionHandler.SelectionNewComboState); // we are responsible for current placement blueprint updated based on state changes. NewCombo.ValueChanged += combo => { if (currentPlacement == null) return; if (currentPlacement.HitObject is IHasComboInformation c) c.NewCombo = combo.NewValue == TernaryState.True; }; } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; /// /// A collection of states which will be displayed to the user in the toolbox. /// public TernaryButton[] TernaryStates { get; private set; } /// /// Create all ternary states required to be displayed to the user. /// protected virtual IEnumerable CreateTernaryButtons() { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); } #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(); } } } }