diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs new file mode 100644 index 0000000000..d5fc1b606b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuEditPlayfield : OsuPlayfield + { + protected override CursorContainer CreateCursor() => null; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs new file mode 100644 index 0000000000..1e9e4b4686 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuEditRulesetContainer : OsuRulesetContainer + { + public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs new file mode 100644 index 0000000000..ec3aa4661c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuHitObjectComposer : HitObjectComposer + { + public OsuHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap, true); + + protected override IReadOnlyList CompositionTools => new ICompositionTool[] + { + new HitObjectCompositionTool(), + new HitObjectCompositionTool(), + new HitObjectCompositionTool() + }; + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index fdf2a458b7..0d2343a33e 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -16,6 +16,8 @@ using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu { @@ -118,6 +120,8 @@ namespace osu.Game.Rulesets.Osu public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); + public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); + public override string Description => "osu!"; public override SettingsSubsection CreateSettings() => new OsuSettings(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 89f6a4e255..387a098a5a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -13,6 +13,7 @@ using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Rulesets.Osu.UI { @@ -65,7 +66,10 @@ namespace osu.Game.Rulesets.Osu.UI protected override void LoadComplete() { base.LoadComplete(); - AddInternal(new GameplayCursor()); + + var cursor = CreateCursor(); + if (cursor != null) + AddInternal(cursor); } public override void Add(DrawableHitObject h) @@ -102,5 +106,7 @@ namespace osu.Game.Rulesets.Osu.UI judgementLayer.Add(explosion); } + + protected virtual CursorContainer CreateCursor() => new GameplayCursor(); } } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 5a0581f48b..819b6d52a9 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -48,6 +48,9 @@ + + + diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs new file mode 100644 index 0000000000..d52f27f4ab --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Screens.Edit.Screens.Compose; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorCompose : OsuTestCase + { + private readonly Random random; + private readonly Compose compose; + + public TestCaseEditorCompose() + { + random = new Random(1337); + + Add(compose = new Compose()); + AddStep("Next beatmap", nextBeatmap); + } + + private OsuGameBase osuGame; + private BeatmapManager beatmaps; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame, BeatmapManager beatmaps) + { + this.osuGame = osuGame; + this.beatmaps = beatmaps; + + compose.Beatmap.BindTo(osuGame.Beatmap); + } + + private void nextBeatmap() + { + var sets = beatmaps.GetAllUsableBeatmapSets(); + if (sets.Count == 0) + return; + + var b = sets[random.Next(0, sets.Count)].Beatmaps[0]; + osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(b); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs new file mode 100644 index 0000000000..f8669cde4b --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorComposeRadioButtons : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) }; + + public TestCaseEditorComposeRadioButtons() + { + RadioButtonCollection collection; + Add(collection = new RadioButtonCollection + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 150, + Items = new[] + { + new RadioButton("Item 1", () => { }), + new RadioButton("Item 2", () => { }), + new RadioButton("Item 3", () => { }), + new RadioButton("Item 4", () => { }), + new RadioButton("Item 5", () => { }) + } + }); + + for (int i = 0; i < collection.Items.Count; i++) + { + int l = i; + AddStep($"Select item {l + 1}", () => collection.Items[l].Select()); + AddStep($"Deselect item {l + 1}", () => collection.Items[l].Deselect()); + } + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index b4242052d5..092ccfb9b5 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -102,6 +102,8 @@ + + diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs new file mode 100644 index 0000000000..3184b84e98 --- /dev/null +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class HitObjectComposer : CompositeDrawable + { + private readonly Ruleset ruleset; + + protected ICompositionTool CurrentTool { get; private set; } + + protected HitObjectComposer(Ruleset ruleset) + { + this.ruleset = ruleset; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + RulesetContainer rulesetContainer; + try + { + rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value); + } + catch (Exception e) + { + Logger.Log($"Could not load this beatmap sucessfully ({e})!", LoggingTarget.Runtime, LogLevel.Error); + return; + } + + RadioButtonCollection toolboxCollection; + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + Name = "Sidebar", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + rulesetContainer + } + } + }, + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 200), + } + }; + + rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock()); + + toolboxCollection.Items = + new[] { new RadioButton("Select", () => setCompositionTool(null)) } + .Concat( + CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) + ) + .ToList(); + + toolboxCollection.Items[0].Select(); + } + + private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; + + protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); + + protected abstract IReadOnlyList CompositionTools { get; } + } +} diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs new file mode 100644 index 0000000000..70e4d3a0c5 --- /dev/null +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Screens.Play.ReplaySettings; + +namespace osu.Game.Rulesets.Edit +{ + public class ToolboxGroup : ReplayGroup + { + protected override string Title => "toolbox"; + + public ToolboxGroup() + { + RelativeSizeAxes = Axes.X; + Width = 1; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs new file mode 100644 index 0000000000..dd182dcbdb --- /dev/null +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Tools +{ + public class HitObjectCompositionTool : ICompositionTool + where T : HitObject + { + public string Name => typeof(T).Name; + } +} diff --git a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs new file mode 100644 index 0000000000..eba873f0cf --- /dev/null +++ b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Edit.Tools +{ + public interface ICompositionTool + { + string Name { get; } + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d787da6a0a..df5e12a24f 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -52,6 +53,8 @@ namespace osu.Game.Rulesets public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null; + public virtual HitObjectComposer CreateHitObjectComposer() => null; + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; public abstract string Description { get; } diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index ec26f6f310..69bf6bba29 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -55,6 +55,11 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } + /// + /// The playfield. + /// + public Playfield Playfield { get; protected set; } + protected readonly Ruleset Ruleset; /// @@ -135,11 +140,6 @@ namespace osu.Game.Rulesets.UI public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - /// - /// The playfield. - /// - public Playfield Playfield { get; private set; } - protected override Container Content => content; private Container content; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index 2349c261cf..6bc7356f26 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -6,49 +6,99 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Screens.Edit.Screens.Compose { public class Compose : EditorScreen { + private const float vertical_margins = 10; + private const float horizontal_margins = 20; + + private readonly Container composerContainer; + public Compose() { ScrollableTimeline timeline; - Children = new[] + Children = new Drawable[] { - new Container + new GridContainer { - Name = "Timeline", - RelativeSizeAxes = Axes.X, - Height = 110, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Content = new[] { - new Box + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, - new Container - { - Name = "Content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 17, Vertical = 10 }, - Children = new Drawable[] + new Container { - new Container + Name = "Timeline", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 115 }, - Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new Container + { + Name = "Timeline content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 115 }, + Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + } + } + } } } + }, + new Drawable[] + { + composerContainer = new Container + { + Name = "Composer content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + } } - } - } + }, + RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) } + }, }; timeline.Beatmap.BindTo(Beatmap); + Beatmap.ValueChanged += beatmapChanged; + } + + private void beatmapChanged(WorkingBeatmap newBeatmap) + { + composerContainer.Clear(); + + var ruleset = newBeatmap.BeatmapInfo.Ruleset?.CreateInstance(); + if (ruleset == null) + { + Logger.Log("Beatmap doesn't have a ruleset assigned."); + // ExitRequested?.Invoke(); + return; + } + + var composer = ruleset.CreateHitObjectComposer(); + if (composer == null) + { + Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); + // ExitRequested?.Invoke(); + return; + } + + composerContainer.Child = composer; } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs new file mode 100644 index 0000000000..10b6c07f3d --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs @@ -0,0 +1,123 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class DrawableRadioButton : TriangleButton + { + /// + /// Invoked when this has been selected. + /// + public Action Selected; + + private Color4 defaultBackgroundColour; + private Color4 defaultBubbleColour; + private Color4 selectedBackgroundColour; + private Color4 selectedBubbleColour; + + private readonly Drawable bubble; + private readonly RadioButton button; + + public DrawableRadioButton(RadioButton button) + { + this.button = button; + + Text = button.Text; + Action = button.Action; + + RelativeSizeAxes = Axes.X; + + bubble = new CircularContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.5f), + X = 10, + Masking = true, + Blending = BlendingMode.Additive, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + defaultBackgroundColour = colours.Gray3; + defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); + selectedBackgroundColour = colours.BlueDark; + selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); + + Triangles.Alpha = 0; + + Content.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 2, + Offset = new Vector2(0, 1), + Colour = Color4.Black.Opacity(0.5f) + }; + + Add(bubble); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + button.Selected.ValueChanged += v => + { + updateSelectionState(); + if (v) + Selected?.Invoke(button); + }; + + updateSelectionState(); + } + + private void updateSelectionState() + { + if (!IsLoaded) + return; + + BackgroundColour = button.Selected ? selectedBackgroundColour : defaultBackgroundColour; + bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour; + } + + protected override bool OnClick(InputState state) + { + if (button.Selected) + return true; + + if (!Enabled) + return true; + + button.Selected.Value = true; + + return base.OnClick(state); + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 40f + }; + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs new file mode 100644 index 0000000000..055362d9e1 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Configuration; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class RadioButton + { + /// + /// Whether this is selected. + /// + /// + public readonly BindableBool Selected; + + /// + /// The text that should be displayed in this button. + /// + public string Text; + + /// + /// The that should be invoked when this button is selected. + /// + public Action Action; + + public RadioButton(string text, Action action) + { + Text = text; + Action = action; + Selected = new BindableBool(); + } + + public RadioButton(string text) + : this(text, null) + { + Text = text; + Action = null; + } + + /// + /// Selects this . + /// + public void Select() => Selected.Value = true; + + /// + /// Deselects this . + /// + public void Deselect() => Selected.Value = false; + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs new file mode 100644 index 0000000000..5f1def4a2e --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class RadioButtonCollection : CompositeDrawable + { + private IReadOnlyList items; + public IReadOnlyList Items + { + get { return items; } + set + { + if (ReferenceEquals(items, value)) + return; + items = value; + + buttonContainer.Clear(); + items.ForEach(addButton); + } + } + + private readonly FlowContainer buttonContainer; + + public RadioButtonCollection() + { + AutoSizeAxes = Axes.Y; + + InternalChild = buttonContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + }; + } + + private RadioButton currentlySelected; + private void addButton(RadioButton button) + { + button.Selected.ValueChanged += v => + { + if (v) + { + currentlySelected?.Deselect(); + currentlySelected = button; + } + else + currentlySelected = null; + }; + + buttonContainer.Add(new DrawableRadioButton(button)); + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8b6bdefc6c..4ca6123f04 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -303,7 +303,6 @@ - @@ -313,6 +312,10 @@ + + + + @@ -556,6 +559,10 @@ + + + +