// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; namespace osu.Game.Rulesets.Edit { /// /// A blueprint which governs the creation of a new to actualisation. /// public abstract class PlacementBlueprint : CompositeDrawable { /// /// Whether the is currently mid-placement, but has not necessarily finished being placed. /// public bool PlacementActive { get; private set; } /// /// The that is being placed. /// public readonly HitObject HitObject; [Resolved(canBeNull: true)] protected EditorClock EditorClock { get; private set; } private readonly IBindable beatmap = new Bindable(); private Bindable startTimeBindable; [Resolved] private IPlacementHandler placementHandler { get; set; } protected PlacementBlueprint(HitObject hitObject) { HitObject = hitObject; // adding the default hit sample should be the case regardless of the ruleset. HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); RelativeSizeAxes = Axes.Both; // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle // on the same frame it is made visible via a PlacementState change. AlwaysPresent = true; } [BackgroundDependencyLoader] private void load(IBindable beatmap) { this.beatmap.BindTo(beatmap); startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); } /// /// Signals that the placement of has started. /// /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. protected void BeginPlacement(bool commitStart = false) { placementHandler.BeginPlacement(HitObject); PlacementActive |= commitStart; } /// /// Signals that the placement of has finished. /// This will destroy this , and add the HitObject.StartTime to the . /// /// Whether the object should be committed. public void EndPlacement(bool commit) { if (!PlacementActive) BeginPlacement(); placementHandler.EndPlacement(HitObject, commit); PlacementActive = false; } /// /// Updates the time and position of this based on the provided snap information. /// /// The snap result information. public virtual void UpdateTimeAndPosition(SnapResult result) { if (!PlacementActive) HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; } /// /// Invokes , /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; protected override bool Handle(UIEvent e) { base.Handle(e); switch (e) { case ScrollEvent _: return false; case DoubleClickEvent _: return false; case MouseButtonEvent _: return true; default: return false; } } } }