diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index 3e227169e0..121853d8d0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -17,6 +17,11 @@ namespace osu.Game.Tests.Visual.Editor { typeof(ControlPointTable), typeof(ControlPointSettings), + typeof(Section<>), + typeof(TimingSection), + typeof(EffectSection), + typeof(SampleSection), + typeof(DifficultySection), typeof(RowAttribute) }; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index d57baf25be..cb73ce884e 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { @@ -17,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double Time { get; } - public IReadOnlyList ControlPoints => controlPoints; + public IBindableList ControlPoints => controlPoints; - private readonly List controlPoints = new List(); + private readonly BindableList controlPoints = new BindableList(); public ControlPointGroup(double time) { diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 470750b860..e1182d9fa4 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -2,17 +2,12 @@ // 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.Shapes; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Timing { @@ -51,190 +46,5 @@ namespace osu.Game.Screens.Edit.Timing new SampleSection(), new EffectSection(), }; - - private class TimingSection : Section - { - private OsuSpriteText bpm; - private OsuSpriteText timeSignature; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bpm = new OsuSpriteText(), - timeSignature = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; - }); - } - } - - private class DifficultySection : Section - { - private OsuSpriteText multiplier; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - multiplier = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); - } - } - - private class SampleSection : Section - { - private OsuSpriteText bank; - private OsuSpriteText volume; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bank = new OsuSpriteText(), - volume = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; - }); - } - } - - private class EffectSection : Section - { - private OsuSpriteText kiai; - private OsuSpriteText omitBarLine; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - kiai = new OsuSpriteText(), - omitBarLine = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; - omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; - }); - } - } - - private class Section : CompositeDrawable - where T : ControlPoint - { - private OsuCheckbox checkbox; - private Container content; - - protected FillFlowContainer Flow { get; private set; } - - protected Bindable ControlPoint { get; } = new Bindable(); - - private const float header_height = 20; - - [Resolved] - private Bindable selectedPoints { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.X; - AutoSizeDuration = 200; - AutoSizeEasing = Easing.OutQuint; - AutoSizeAxes = Axes.Y; - - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Gray1, - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height, - Children = new Drawable[] - { - checkbox = new OsuCheckbox - { - LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) - } - } - }, - content = new Container - { - Y = header_height, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray2, - RelativeSizeAxes = Axes.Both, - }, - Flow = new FillFlowContainer - { - Padding = new MarginPadding(10), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedPoints.BindValueChanged(points => - { - ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); - - checkbox.Current.Value = ControlPoint.Value != null; - }, true); - - checkbox.Current.BindValueChanged(selected => { content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; }, true); - } - } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 8d5774cd09..9c11443199 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -92,35 +92,57 @@ namespace osu.Game.Screens.Edit.Timing Text = $"{group.Time:n0}ms", Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), - Padding = new MarginPadding(10), - Spacing = new Vector2(2) - }, + new ControlGroupAttributes(group), }; - private Drawable createAttribute(ControlPoint controlPoint) + private class ControlGroupAttributes : CompositeDrawable { - switch (controlPoint) + private readonly IBindableList controlPoints; + + private readonly FillFlowContainer fill; + + public ControlGroupAttributes(ControlPointGroup group) { - case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + InternalChild = fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Spacing = new Vector2(2) + }; - case DifficultyControlPoint difficulty: + controlPoints = group.ControlPoints.GetBoundCopy(); + controlPoints.ItemsAdded += _ => createChildren(); + controlPoints.ItemsRemoved += _ => createChildren(); - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); - - case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); - - case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + createChildren(); } - return null; + private void createChildren() + { + fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); + } + + private Drawable createAttribute(ControlPoint controlPoint) + { + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } } protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs new file mode 100644 index 0000000000..150c11f5ee --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -0,0 +1,37 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class DifficultySection : Section + { + private OsuSpriteText multiplier; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + multiplier = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + } + + protected override DifficultyControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time); + + return new DifficultyControlPoint + { + SpeedMultiplier = reference.SpeedMultiplier, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs new file mode 100644 index 0000000000..ff8817147a --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class EffectSection : Section + { + private OsuSpriteText kiai; + private OsuSpriteText omitBarLine; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + kiai = new OsuSpriteText(), + omitBarLine = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; + omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + }); + } + + protected override EffectControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time); + + return new EffectControlPoint + { + KiaiMode = reference.KiaiMode, + OmitFirstBarLine = reference.OmitFirstBarLine + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs new file mode 100644 index 0000000000..0d6bc74057 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class SampleSection : Section + { + private OsuSpriteText bank; + private OsuSpriteText volume; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bank = new OsuSpriteText(), + volume = new OsuSpriteText(), + }); + } + + protected override SampleControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); + + return new SampleControlPoint + { + SampleBank = reference.SampleBank, + SampleVolume = reference.SampleVolume, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + }); + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs new file mode 100644 index 0000000000..c6140ff497 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -0,0 +1,126 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Timing +{ + internal abstract class Section : CompositeDrawable + where T : ControlPoint + { + private OsuCheckbox checkbox; + private Container content; + + protected FillFlowContainer Flow { get; private set; } + + protected Bindable ControlPoint { get; } = new Bindable(); + + private const float header_height = 20; + + [Resolved] + protected IBindable Beatmap { get; private set; } + + [Resolved] + protected Bindable SelectedGroup { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + AutoSizeAxes = Axes.Y; + + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + checkbox = new OsuCheckbox + { + LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + } + } + }, + content = new Container + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + Flow = new FillFlowContainer + { + Padding = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + checkbox.Current.BindValueChanged(selected => + { + if (selected.NewValue) + { + if (SelectedGroup.Value == null) + { + checkbox.Current.Value = false; + return; + } + + if (ControlPoint.Value == null) + SelectedGroup.Value.Add(ControlPoint.Value = CreatePoint()); + } + else + { + if (ControlPoint.Value != null) + { + SelectedGroup.Value.Remove(ControlPoint.Value); + ControlPoint.Value = null; + } + } + + content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; + }, true); + + SelectedGroup.BindValueChanged(points => + { + ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); + checkbox.Current.Value = ControlPoint.Value != null; + }, true); + } + + protected abstract T CreatePoint(); + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs new file mode 100644 index 0000000000..dcbb6f8bbf --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class TimingSection : Section + { + private OsuSpriteText bpm; + private OsuSpriteText timeSignature; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bpm = new OsuSpriteText(), + timeSignature = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + }); + } + + protected override TimingControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time); + + return new TimingControlPoint + { + BeatLength = reference.BeatLength, + TimeSignature = reference.TimeSignature + }; + } + } +}