// 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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { [Cached] public partial class Column : ScrollingPlayfield, IKeyBindingHandler { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; /// /// The index of this column as part of the whole playfield. /// public readonly int Index; public readonly Bindable Action = new Bindable(); public readonly ColumnHitObjectArea HitObjectArea; internal readonly Container BackgroundContainer = new Container { RelativeSizeAxes = Axes.Both }; internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }; private DrawablePool hitExplosionPool; private readonly OrderedHitPolicy hitPolicy; public Container UnderlayElements => HitObjectArea.UnderlayElements; private GameplaySampleTriggerSource sampleTriggerSource; /// /// Whether this is a special (ie. scratch) column. /// public readonly bool IsSpecial; public readonly Bindable AccentColour = new Bindable(Color4.Black); public Column(int index, bool isSpecial) { Index = index; IsSpecial = isSpecial; RelativeSizeAxes = Axes.Y; Width = COLUMN_WIDTH; hitPolicy = new OrderedHitPolicy(HitObjectContainer); HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }; } [Resolved] private ISkinSource skin { get; set; } [BackgroundDependencyLoader] private void load(GameHost host) { SkinnableDrawable keyArea; skin.SourceChanged += onSourceChanged; onSourceChanged(); InternalChildren = new Drawable[] { hitExplosionPool = new DrawablePool(5), sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer), HitObjectArea, keyArea = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both, }, // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements externally // (see `Stage.columnBackgrounds`). BackgroundContainer, TopLevelContainer, new ColumnTouchInputArea(this) }; var background = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both, }; background.ApplyGameWideClock(host); keyArea.ApplyGameWideClock(host); BackgroundContainer.Add(background); TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); RegisterPool(10, 50); RegisterPool(10, 50); RegisterPool(10, 50); RegisterPool(10, 50); } private void onSourceChanged() { AccentColour.Value = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, Index)?.Value ?? Color4.Black; } protected override void LoadComplete() { base.LoadComplete(); NewResult += OnNewResult; } protected override void Dispose(bool isDisposing) { // must happen before children are disposed in base call to prevent illegal accesses to the hit explosion pool. NewResult -= OnNewResult; base.Dispose(isDisposing); if (skin != null) skin.SourceChanged -= onSourceChanged; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs>(Action); return dependencies; } protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) { base.OnNewDrawableHitObject(drawableHitObject); DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject; maniaObject.AccentColour.BindTo(AccentColour); maniaObject.CheckHittable = hitPolicy.IsHittable; } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { if (result.IsHit) hitPolicy.HandleHit(judgedObject); if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } public bool OnPressed(KeyBindingPressEvent e) { if (e.Action != Action.Value) return false; sampleTriggerSource.Play(); return true; } public void OnReleased(KeyBindingReleaseEvent e) { } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); public partial class ColumnTouchInputArea : Drawable { private readonly Column column; [Resolved(canBeNull: true)] private ManiaInputManager maniaInputManager { get; set; } private KeyBindingContainer keyBindingContainer; public ColumnTouchInputArea(Column column) { RelativeSizeAxes = Axes.Both; this.column = column; } protected override void LoadComplete() { keyBindingContainer = maniaInputManager?.KeyBindingContainer; } protected override bool OnTouchDown(TouchDownEvent e) { keyBindingContainer?.TriggerPressed(column.Action.Value); return true; } protected override void OnTouchUp(TouchUpEvent e) { keyBindingContainer?.TriggerReleased(column.Action.Value); } } } }