diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs index 559d612037..70a9c03e65 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -7,6 +7,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Input; @@ -72,7 +73,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorClock.Seek(slider.StartTime); EditorBeatmap.SelectedHitObjects.Add(slider); }); - AddStep("change beat divisor", () => beatDivisor.Value = 3); + AddStep("change beat divisor", () => + { + beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS; + beatDivisor.Value = 3; + }); convertToStream(); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index ed7bb9e301..6a0950c6dd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; @@ -20,30 +22,31 @@ namespace osu.Game.Tests.Visual.Editing private BeatDivisorControl beatDivisorControl; private BindableBeatDivisor bindableBeatDivisor; - private SliderBar tickSliderBar; - private EquilateralTriangle tickMarkerHead; + private SliderBar tickSliderBar => beatDivisorControl.ChildrenOfType>().Single(); + private EquilateralTriangle tickMarkerHead => tickSliderBar.ChildrenOfType().Single(); [SetUp] public void SetUp() => Schedule(() => { - Child = beatDivisorControl = new BeatDivisorControl(bindableBeatDivisor = new BindableBeatDivisor(16)) + Child = new PopoverContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(90, 90) + RelativeSizeAxes = Axes.Both, + Child = beatDivisorControl = new BeatDivisorControl(bindableBeatDivisor = new BindableBeatDivisor(16)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(90, 90) + } }; - - tickSliderBar = beatDivisorControl.ChildrenOfType>().Single(); - tickMarkerHead = tickSliderBar.ChildrenOfType().Single(); }); [Test] public void TestBindableBeatDivisor() { - AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 4); + AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 2); AddAssert("divisor is 4", () => bindableBeatDivisor.Value == 4); - AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 3); - AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 12); + AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 1); + AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 8); } [Test] @@ -79,5 +82,115 @@ namespace osu.Game.Tests.Visual.Editing sliderDrawQuad.Centre.Y ); } + + [Test] + public void TestBeatChevronNavigation() + { + switchBeatSnap(1); + assertBeatSnap(1); + + switchBeatSnap(3); + assertBeatSnap(8); + + switchBeatSnap(-1); + assertBeatSnap(4); + + switchBeatSnap(-3); + assertBeatSnap(16); + } + + [Test] + public void TestBeatPresetNavigation() + { + assertPreset(BeatDivisorType.Common); + + switchPresets(1); + assertPreset(BeatDivisorType.Triplets); + + switchPresets(1); + assertPreset(BeatDivisorType.Common); + + switchPresets(-1); + assertPreset(BeatDivisorType.Triplets); + + switchPresets(-1); + assertPreset(BeatDivisorType.Common); + + setDivisorViaInput(3); + assertPreset(BeatDivisorType.Triplets); + + setDivisorViaInput(8); + assertPreset(BeatDivisorType.Common); + + setDivisorViaInput(15); + assertPreset(BeatDivisorType.Custom, 15); + + switchBeatSnap(-1); + assertBeatSnap(5); + + switchBeatSnap(-1); + assertBeatSnap(3); + + setDivisorViaInput(5); + assertPreset(BeatDivisorType.Custom, 15); + + switchPresets(1); + assertPreset(BeatDivisorType.Common); + + switchPresets(-1); + assertPreset(BeatDivisorType.Triplets); + } + + private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () => + { + int chevronIndex = direction > 0 ? 1 : 0; + var chevronButton = beatDivisorControl.ChildrenOfType().ElementAt(chevronIndex); + InputManager.MoveMouseTo(chevronButton); + InputManager.Click(MouseButton.Left); + }, Math.Abs(direction)); + + private void assertBeatSnap(int expected) => AddAssert($"beat snap is {expected}", + () => bindableBeatDivisor.Value == expected); + + private void switchPresets(int direction) => AddRepeatStep($"move presets {(direction > 0 ? "forward" : "backward")}", () => + { + int chevronIndex = direction > 0 ? 3 : 2; + var chevronButton = beatDivisorControl.ChildrenOfType().ElementAt(chevronIndex); + InputManager.MoveMouseTo(chevronButton); + InputManager.Click(MouseButton.Left); + }, Math.Abs(direction)); + + private void assertPreset(BeatDivisorType type, int? maxDivisor = null) + { + AddAssert($"preset is {type}", () => bindableBeatDivisor.ValidDivisors.Value.Type == type); + + if (type == BeatDivisorType.Custom) + { + Debug.Assert(maxDivisor != null); + AddAssert($"max divisor is {maxDivisor}", () => bindableBeatDivisor.ValidDivisors.Value.Presets.Max() == maxDivisor.Value); + } + } + + private void setDivisorViaInput(int divisor) + { + AddStep("open divisor input popover", () => + { + var button = beatDivisorControl.ChildrenOfType().Single(); + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + BeatDivisorControl.CustomDivisorPopover popover = null; + AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType().SingleOrDefault()) != null && popover.IsLoaded); + AddStep($"set divisor to {divisor}", () => + { + var textBox = popover.ChildrenOfType().Single(); + InputManager.MoveMouseTo(textBox); + InputManager.Click(MouseButton.Left); + textBox.Text = divisor.ToString(); + InputManager.Key(Key.Enter); + }); + AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 246d1f8af5..af03d639be 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -164,7 +164,7 @@ namespace osu.Game.Beatmaps.ControlPoints int closestDivisor = 0; double closestTime = double.MaxValue; - foreach (int divisor in BindableBeatDivisor.VALID_DIVISORS) + foreach (int divisor in BindableBeatDivisor.PREDEFINED_DIVISORS) { double distanceFromSnap = Math.Abs(time - getClosestSnappedTime(timingPoint, time, divisor)); diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 1a350d7261..af958e3448 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -1,46 +1,64 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Framework.Bindables; using osu.Game.Graphics; +using osu.Game.Screens.Edit.Compose.Components; using osuTK.Graphics; namespace osu.Game.Screens.Edit { public class BindableBeatDivisor : BindableInt { - public static readonly int[] VALID_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; + public static readonly int[] PREDEFINED_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; + + public Bindable ValidDivisors { get; } = new Bindable(BeatDivisorPresetCollection.COMMON); public BindableBeatDivisor(int value = 1) : base(value) { + ValidDivisors.BindValueChanged(_ => updateBindableProperties(), true); + BindValueChanged(_ => ensureValidDivisor()); } - public void Next() => Value = VALID_DIVISORS[Math.Min(VALID_DIVISORS.Length - 1, Array.IndexOf(VALID_DIVISORS, Value) + 1)]; - - public void Previous() => Value = VALID_DIVISORS[Math.Max(0, Array.IndexOf(VALID_DIVISORS, Value) - 1)]; - - public override int Value + private void updateBindableProperties() { - get => base.Value; - set - { - if (!VALID_DIVISORS.Contains(value)) - { - // If it doesn't match, value will be 0, but will be clamped to the valid range via DefaultMinValue - value = Array.FindLast(VALID_DIVISORS, d => d < value); - } + ensureValidDivisor(); - base.Value = value; - } + MinValue = ValidDivisors.Value.Presets.Min(); + MaxValue = ValidDivisors.Value.Presets.Max(); + } + + private void ensureValidDivisor() + { + if (!ValidDivisors.Value.Presets.Contains(Value)) + Value = 1; + } + + public void Next() + { + var presets = ValidDivisors.Value.Presets; + Value = presets.Cast().SkipWhile(preset => preset != Value).ElementAtOrDefault(1) ?? presets[0]; + } + + public void Previous() + { + var presets = ValidDivisors.Value.Presets; + Value = presets.Cast().TakeWhile(preset => preset != Value).LastOrDefault() ?? presets[^1]; } - protected override int DefaultMinValue => VALID_DIVISORS.First(); - protected override int DefaultMaxValue => VALID_DIVISORS.Last(); protected override int DefaultPrecision => 1; + public override void BindTo(Bindable them) + { + // bind to valid divisors first (if applicable) to ensure correct transfer of the actual divisor. + if (them is BindableBeatDivisor otherBeatDivisor) + ValidDivisors.BindTo(otherBeatDivisor.ValidDivisors); + + base.BindTo(them); + } + protected override Bindable CreateInstance() => new BindableBeatDivisor(); /// @@ -92,7 +110,7 @@ namespace osu.Game.Screens.Edit { int beat = index % beatDivisor; - foreach (int divisor in BindableBeatDivisor.VALID_DIVISORS) + foreach (int divisor in PREDEFINED_DIVISORS) { if ((beat * divisor) % beatDivisor == 0) return divisor; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index de63b265d2..370c9016c7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -2,19 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -62,7 +69,7 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.Both, Colour = Color4.Black }, - new TickSliderBar(beatDivisor, BindableBeatDivisor.VALID_DIVISORS) + new TickSliderBar(beatDivisor) { RelativeSizeAxes = Axes.Both, } @@ -84,7 +91,6 @@ namespace osu.Game.Screens.Edit.Compose.Components new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5 }, Child = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -92,13 +98,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { new Drawable[] { - new DivisorButton + new ChevronButton { Icon = FontAwesome.Solid.ChevronLeft, Action = beatDivisor.Previous }, - new DivisorText(beatDivisor), - new DivisorButton + new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } }, + new ChevronButton { Icon = FontAwesome.Solid.ChevronRight, Action = beatDivisor.Next @@ -121,49 +127,233 @@ namespace osu.Game.Screens.Edit.Compose.Components new TextFlowContainer(s => s.Font = s.Font.With(size: 14)) { Padding = new MarginPadding { Horizontal = 15 }, - Text = "beat snap divisor", + Text = "beat snap", RelativeSizeAxes = Axes.X, TextAnchor = Anchor.TopCentre }, - } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray4 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new ChevronButton + { + Icon = FontAwesome.Solid.ChevronLeft, + Action = () => cycleDivisorType(-1) + }, + new DivisorTypeText { BeatDivisor = { BindTarget = beatDivisor } }, + new ChevronButton + { + Icon = FontAwesome.Solid.ChevronRight, + Action = () => cycleDivisorType(1) + } + }, + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 20), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 20) + } + } + } + } + } + }, }, RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 30), - new Dimension(GridSizeMode.Absolute, 25), + new Dimension(GridSizeMode.Absolute, 20), + new Dimension(GridSizeMode.Absolute, 15) } } }; } - private class DivisorText : SpriteText + private void cycleDivisorType(int direction) { - private readonly Bindable beatDivisor = new Bindable(); + Debug.Assert(Math.Abs(direction) == 1); + int nextDivisorType = (int)beatDivisor.ValidDivisors.Value.Type + direction; + if (nextDivisorType > (int)BeatDivisorType.Triplets) + nextDivisorType = (int)BeatDivisorType.Common; + else if (nextDivisorType < (int)BeatDivisorType.Common) + nextDivisorType = (int)BeatDivisorType.Triplets; - public DivisorText(BindableBeatDivisor beatDivisor) + switch ((BeatDivisorType)nextDivisorType) { - this.beatDivisor.BindTo(beatDivisor); + case BeatDivisorType.Common: + beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.COMMON; + break; + case BeatDivisorType.Triplets: + beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS; + break; + + case BeatDivisorType.Custom: + beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.Custom(beatDivisor.ValidDivisors.Value.Presets.Max()); + break; + } + } + + internal class DivisorDisplay : OsuAnimatedButton, IHasPopover + { + public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor(); + + private readonly OsuSpriteText divisorText; + + public DivisorDisplay() + { Anchor = Anchor.Centre; Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Both; + + Add(divisorText = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 20), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding + { + Horizontal = 5 + } + }); + + Action = this.ShowPopover; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - Colour = colours.BlueLighter; + divisorText.Colour = colours.BlueLighter; } protected override void LoadComplete() { base.LoadComplete(); - beatDivisor.BindValueChanged(val => Text = $"1/{val.NewValue}", true); + updateState(); + } + + private void updateState() + { + BeatDivisor.BindValueChanged(val => divisorText.Text = $"1/{val.NewValue}", true); + } + + public Popover GetPopover() => new CustomDivisorPopover + { + BeatDivisor = { BindTarget = BeatDivisor } + }; + } + + internal class CustomDivisorPopover : OsuPopover + { + public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor(); + + private readonly OsuNumberBox divisorTextBox; + + public CustomDivisorPopover() + { + Child = new FillFlowContainer + { + Width = 150, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Children = new Drawable[] + { + divisorTextBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + PlaceholderText = "Beat divisor" + }, + new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = "Related divisors will be added to the list of presets." + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + BeatDivisor.BindValueChanged(_ => updateState(), true); + divisorTextBox.OnCommit += (_, __) => setPresets(); + + Schedule(() => GetContainingInputManager().ChangeFocus(divisorTextBox)); + } + + private void setPresets() + { + if (!int.TryParse(divisorTextBox.Text, out int divisor) || divisor < 1 || divisor > 64) + { + updateState(); + return; + } + + if (!BeatDivisor.ValidDivisors.Value.Presets.Contains(divisor)) + { + if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor)) + BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.COMMON; + else if (BeatDivisorPresetCollection.TRIPLETS.Presets.Contains(divisor)) + BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS; + else + BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.Custom(divisor); + } + + BeatDivisor.Value = divisor; + + this.HidePopover(); + } + + private void updateState() + { + divisorTextBox.Text = BeatDivisor.Value.ToString(); } } - private class DivisorButton : IconButton + private class DivisorTypeText : OsuSpriteText { - public DivisorButton() + public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor(); + + public DivisorTypeText() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Font = OsuFont.Default.With(size: 14); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + BeatDivisor.ValidDivisors.BindValueChanged(val => Text = val.NewValue.Type.Humanize(LetterCasing.LowerCase), true); + } + } + + internal class ChevronButton : IconButton + { + public ChevronButton() { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -192,20 +382,27 @@ namespace osu.Game.Screens.Edit.Compose.Components private OsuColour colours { get; set; } private readonly BindableBeatDivisor beatDivisor; - private readonly int[] availableDivisors; - public TickSliderBar(BindableBeatDivisor beatDivisor, params int[] divisors) + public TickSliderBar(BindableBeatDivisor beatDivisor) { CurrentNumber.BindTo(this.beatDivisor = beatDivisor); - availableDivisors = divisors; Padding = new MarginPadding { Horizontal = 5 }; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - foreach (int t in availableDivisors) + base.LoadComplete(); + + beatDivisor.ValidDivisors.BindValueChanged(_ => updateDivisors(), true); + } + + private void updateDivisors() + { + ClearInternal(); + CurrentNumber.ValueChanged -= moveMarker; + + foreach (int t in beatDivisor.ValidDivisors.Value.Presets) { AddInternal(new Tick { @@ -218,17 +415,14 @@ namespace osu.Game.Screens.Edit.Compose.Components } AddInternal(marker = new Marker()); + CurrentNumber.ValueChanged += moveMarker; + CurrentNumber.TriggerChange(); } - protected override void LoadComplete() + private void moveMarker(ValueChangedEvent divisor) { - base.LoadComplete(); - - CurrentNumber.BindValueChanged(div => - { - marker.MoveToX(getMappedPosition(div.NewValue), 100, Easing.OutQuint); - marker.Flash(); - }, true); + marker.MoveToX(getMappedPosition(divisor.NewValue), 100, Easing.OutQuint); + marker.Flash(); } protected override void UpdateValue(float value) @@ -289,11 +483,11 @@ namespace osu.Game.Screens.Edit.Compose.Components // copied from SliderBar so we can do custom spacing logic. float xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; - CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); + CurrentNumber.Value = beatDivisor.ValidDivisors.Value.Presets.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); OnUserChange(Current.Value); } - private float getMappedPosition(float divisor) => MathF.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f); + private float getMappedPosition(float divisor) => MathF.Pow((divisor - 1) / (beatDivisor.ValidDivisors.Value.Presets.Last() - 1), 0.90f); private class Tick : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorPresetCollection.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorPresetCollection.cs new file mode 100644 index 0000000000..4616669c6d --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorPresetCollection.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class BeatDivisorPresetCollection + { + public BeatDivisorType Type { get; } + public IReadOnlyList Presets { get; } + + private BeatDivisorPresetCollection(BeatDivisorType type, IEnumerable presets) + { + Type = type; + Presets = presets.ToArray(); + } + + public static readonly BeatDivisorPresetCollection COMMON = new BeatDivisorPresetCollection(BeatDivisorType.Common, new[] { 1, 2, 4, 8, 16 }); + + public static readonly BeatDivisorPresetCollection TRIPLETS = new BeatDivisorPresetCollection(BeatDivisorType.Triplets, new[] { 1, 3, 6, 12 }); + + public static BeatDivisorPresetCollection Custom(int maxDivisor) + { + var presets = new List(); + + for (int candidate = 1; candidate <= Math.Sqrt(maxDivisor); ++candidate) + { + if (maxDivisor % candidate != 0) + continue; + + presets.Add(candidate); + presets.Add(maxDivisor / candidate); + } + + return new BeatDivisorPresetCollection(BeatDivisorType.Custom, presets.Distinct().OrderBy(d => d)); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorType.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorType.cs new file mode 100644 index 0000000000..4a25144881 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorType.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public enum BeatDivisorType + { + /// + /// Most common divisors, all with denominators being powers of two. + /// + Common, + + /// + /// Divisors with denominators divisible by 3. + /// + Triplets, + + /// + /// Fully arbitrary/custom beat divisors. + /// + Custom, + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index cc4041394d..3a32dc18e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private OsuColour colours { get; set; } - private static readonly int highest_divisor = BindableBeatDivisor.VALID_DIVISORS.Last(); + private static readonly int highest_divisor = BindableBeatDivisor.PREDEFINED_DIVISORS.Last(); public TimelineTickDisplay() {