1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 11:12:54 +08:00

Merge branch 'master' into editor-bottom-bar-new-design

This commit is contained in:
Dean Herbert 2022-05-26 18:32:28 +09:00
commit fb586fe0b2
10 changed files with 227 additions and 140 deletions

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Components.Menus;
namespace osu.Game.Tests.Visual.Editing
@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorMenuBar : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
public TestSceneEditorMenuBar()
{
Add(new Container

View File

@ -9,7 +9,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Mods;
@ -73,19 +72,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createFreeModSelect();
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddStep("click select all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().ElementAt(1));
InputManager.MoveMouseTo(this.ChildrenOfType<SelectAllModsButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
AddAssert("select all button disabled", () => !this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddStep("click deselect all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any());
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
}
private void createFreeModSelect()

View File

@ -435,15 +435,19 @@ namespace osu.Game.Tests.Visual.UserInterface
createScreen();
changeRuleset(0);
AddAssert("deselect all button disabled", () => !this.ChildrenOfType<DeselectAllModsButton>().Single().Enabled.Value);
AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
AddAssert("deselect all button enabled", () => this.ChildrenOfType<DeselectAllModsButton>().Single().Enabled.Value);
AddStep("click deselect all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
AddAssert("deselect all button disabled", () => !this.ChildrenOfType<DeselectAllModsButton>().Single().Enabled.Value);
}
[Test]

View File

@ -0,0 +1,54 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Overlays.Mods
{
public class DeselectAllModsButton : ShearedButton, IKeyBindingHandler<GlobalAction>
{
private readonly Bindable<IReadOnlyList<Mod>> selectedMods = new Bindable<IReadOnlyList<Mod>>();
public DeselectAllModsButton(ModSelectOverlay modSelectOverlay)
: base(ModSelectOverlay.BUTTON_WIDTH)
{
Text = CommonStrings.DeselectAll;
Action = modSelectOverlay.DeselectAll;
selectedMods.BindTo(modSelectOverlay.SelectedMods);
}
protected override void LoadComplete()
{
base.LoadComplete();
selectedMods.BindValueChanged(_ => updateEnabledState(), true);
}
private void updateEnabledState()
{
Enabled.Value = selectedMods.Value.Any();
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (e.Repeat || e.Action != GlobalAction.DeselectAllMods)
return false;
TriggerClick();
return true;
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
}
}
}

View File

@ -29,11 +29,20 @@ namespace osu.Game.Overlays.Mods
{
public abstract class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler
{
protected const int BUTTON_WIDTH = 200;
public const int BUTTON_WIDTH = 200;
[Cached]
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// Contains a dictionary with the current <see cref="ModState"/> of all mods applicable for the current ruleset.
/// </summary>
/// <remarks>
/// Contrary to <see cref="OsuGameBase.AvailableMods"/> and <see cref="globalAvailableMods"/>, the <see cref="Mod"/> instances
/// inside the <see cref="ModState"/> objects are owned solely by this <see cref="ModSelectOverlay"/> instance.
/// </remarks>
public Bindable<Dictionary<ModType, IReadOnlyList<ModState>>> AvailableMods { get; } = new Bindable<Dictionary<ModType, IReadOnlyList<ModState>>>(new Dictionary<ModType, IReadOnlyList<ModState>>());
private Func<Mod, bool> isValidMod = m => true;
/// <summary>
@ -76,16 +85,12 @@ namespace osu.Game.Overlays.Mods
};
}
yield return deselectAllButton = new ShearedButton(BUTTON_WIDTH)
{
Text = CommonStrings.DeselectAll,
Action = DeselectAll
};
yield return new DeselectAllModsButton(this);
}
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
private readonly Dictionary<ModType, IReadOnlyList<ModState>> localAvailableMods = new Dictionary<ModType, IReadOnlyList<ModState>>();
private IEnumerable<ModState> allLocalAvailableMods => localAvailableMods.SelectMany(pair => pair.Value);
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
private IEnumerable<ModState> allAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
private readonly BindableBool customisationVisible = new BindableBool();
@ -98,7 +103,6 @@ namespace osu.Game.Overlays.Mods
private DifficultyMultiplierDisplay? multiplierDisplay;
private ShearedToggleButton? customisationButton;
private ShearedButton? deselectAllButton;
protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme)
@ -209,13 +213,13 @@ namespace osu.Game.Overlays.Mods
})
};
availableMods.BindTo(game.AvailableMods);
globalAvailableMods.BindTo(game.AvailableMods);
}
protected override void LoadComplete()
{
// this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly.
availableMods.BindValueChanged(_ => createLocalMods(), true);
globalAvailableMods.BindValueChanged(_ => createLocalMods(), true);
base.LoadComplete();
@ -247,7 +251,7 @@ namespace osu.Game.Overlays.Mods
/// <summary>
/// Select all visible mods in all columns.
/// </summary>
protected void SelectAll()
public void SelectAll()
{
foreach (var column in columnFlow.Columns)
column.SelectAll();
@ -256,7 +260,7 @@ namespace osu.Game.Overlays.Mods
/// <summary>
/// Deselect all visible mods in all columns.
/// </summary>
protected void DeselectAll()
public void DeselectAll()
{
foreach (var column in columnFlow.Columns)
column.DeselectAll();
@ -280,9 +284,9 @@ namespace osu.Game.Overlays.Mods
private void createLocalMods()
{
localAvailableMods.Clear();
var newLocalAvailableMods = new Dictionary<ModType, IReadOnlyList<ModState>>();
foreach (var (modType, mods) in availableMods.Value)
foreach (var (modType, mods) in globalAvailableMods.Value)
{
var modStates = mods.SelectMany(ModUtils.FlattenMod)
.Select(mod => new ModState(mod.DeepClone()))
@ -291,18 +295,19 @@ namespace osu.Game.Overlays.Mods
foreach (var modState in modStates)
modState.Active.BindValueChanged(_ => updateFromInternalSelection());
localAvailableMods[modType] = modStates;
newLocalAvailableMods[modType] = modStates;
}
AvailableMods.Value = newLocalAvailableMods;
filterMods();
foreach (var column in columnFlow.Columns)
column.AvailableMods = localAvailableMods.GetValueOrDefault(column.ModType, Array.Empty<ModState>());
column.AvailableMods = AvailableMods.Value.GetValueOrDefault(column.ModType, Array.Empty<ModState>());
}
private void filterMods()
{
foreach (var modState in allLocalAvailableMods)
foreach (var modState in allAvailableMods)
modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod);
}
@ -383,7 +388,7 @@ namespace osu.Game.Overlays.Mods
var newSelection = new List<Mod>();
foreach (var modState in allLocalAvailableMods)
foreach (var modState in allAvailableMods)
{
var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType());
@ -410,9 +415,9 @@ namespace osu.Game.Overlays.Mods
if (externalSelectionUpdateInProgress)
return;
var candidateSelection = allLocalAvailableMods.Where(modState => modState.Active.Value)
.Select(modState => modState.Mod)
.ToArray();
var candidateSelection = allAvailableMods.Where(modState => modState.Active.Value)
.Select(modState => modState.Mod)
.ToArray();
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
}
@ -514,10 +519,6 @@ namespace osu.Game.Overlays.Mods
hideOverlay(true);
return true;
}
case GlobalAction.DeselectAllMods:
deselectAllButton?.TriggerClick();
return true;
}
return base.OnPressed(e);

View File

@ -0,0 +1,61 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay;
namespace osu.Game.Overlays.Mods
{
public class SelectAllModsButton : ShearedButton, IKeyBindingHandler<PlatformAction>
{
private readonly Bindable<IReadOnlyList<Mod>> selectedMods = new Bindable<IReadOnlyList<Mod>>();
private readonly Bindable<Dictionary<ModType, IReadOnlyList<ModState>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<ModState>>>();
public SelectAllModsButton(FreeModSelectOverlay modSelectOverlay)
: base(ModSelectOverlay.BUTTON_WIDTH)
{
Text = CommonStrings.SelectAll;
Action = modSelectOverlay.SelectAll;
selectedMods.BindTo(modSelectOverlay.SelectedMods);
availableMods.BindTo(modSelectOverlay.AvailableMods);
}
protected override void LoadComplete()
{
base.LoadComplete();
selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState));
availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState));
updateEnabledState();
}
private void updateEnabledState()
{
Enabled.Value = availableMods.Value
.SelectMany(pair => pair.Value)
.Any(modState => !modState.Active.Value && !modState.Filtered.Value);
}
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
{
if (e.Repeat || e.Action != PlatformAction.SelectAll)
return false;
TriggerClick();
return true;
}
public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e)
{
}
}
}

View File

@ -2,16 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
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.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Components.Menus
{
@ -24,7 +21,12 @@ namespace osu.Game.Screens.Edit.Components.Menus
MaskingContainer.CornerRadius = 0;
ItemsContainer.Padding = new MarginPadding { Left = 100 };
BackgroundColour = Color4Extensions.FromHex("111");
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
BackgroundColour = colourProvider.Background3;
}
protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu();
@ -33,29 +35,26 @@ namespace osu.Game.Screens.Edit.Components.Menus
private class DrawableEditorBarMenuItem : DrawableOsuMenuItem
{
private BackgroundBox background;
public DrawableEditorBarMenuItem(MenuItem item)
: base(item)
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
StateChanged += stateChanged;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
ForegroundColour = colours.BlueLight;
BackgroundColour = Color4.Transparent;
ForegroundColourHover = Color4.White;
BackgroundColourHover = colours.Gray3;
ForegroundColour = colourProvider.Light3;
BackgroundColour = colourProvider.Background2;
ForegroundColourHover = colourProvider.Content1;
BackgroundColourHover = colourProvider.Background1;
}
public override void SetFlowDirection(Direction direction)
protected override void LoadComplete()
{
AutoSizeAxes = Axes.Both;
base.LoadComplete();
Foreground.Anchor = Anchor.CentreLeft;
Foreground.Origin = Anchor.CentreLeft;
}
protected override void UpdateBackgroundColour()
@ -74,54 +73,16 @@ namespace osu.Game.Screens.Edit.Components.Menus
base.UpdateForegroundColour();
}
private void stateChanged(MenuItemState newState)
{
if (newState == MenuItemState.Selected)
background.Expand();
else
background.Contract();
}
protected override Drawable CreateBackground() => background = new BackgroundBox();
protected override DrawableOsuMenuItem.TextContainer CreateTextContainer() => new TextContainer();
private new class TextContainer : DrawableOsuMenuItem.TextContainer
{
public TextContainer()
{
NormalText.Font = NormalText.Font.With(size: 14);
BoldText.Font = BoldText.Font.With(size: 14);
NormalText.Margin = BoldText.Margin = new MarginPadding { Horizontal = 10, Vertical = MARGIN_VERTICAL };
NormalText.Font = OsuFont.TorusAlternate;
BoldText.Font = OsuFont.TorusAlternate.With(weight: FontWeight.Bold);
}
}
private class BackgroundBox : CompositeDrawable
{
private readonly Container innerBackground;
public BackgroundBox()
{
RelativeSizeAxes = Axes.Both;
Masking = true;
InternalChild = innerBackground = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 4,
Child = new Box { RelativeSizeAxes = Axes.Both }
};
}
/// <summary>
/// Expands the background such that it doesn't show the bottom corners.
/// </summary>
public void Expand() => innerBackground.Height = 2;
/// <summary>
/// Contracts the background such that it shows the bottom corners.
/// </summary>
public void Contract() => innerBackground.Height = 1;
}
}
private class SubMenu : OsuMenu
@ -129,14 +90,15 @@ namespace osu.Game.Screens.Edit.Components.Menus
public SubMenu()
: base(Direction.Vertical)
{
OriginPosition = new Vector2(5, 1);
ItemsContainer.Padding = new MarginPadding { Top = 5, Bottom = 5 };
ItemsContainer.Padding = new MarginPadding();
MaskingContainer.CornerRadius = 0;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
BackgroundColour = colours.Gray3;
BackgroundColour = colourProvider.Background2;
}
protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu();
@ -147,9 +109,27 @@ namespace osu.Game.Screens.Edit.Components.Menus
{
case EditorMenuItemSpacer spacer:
return new DrawableSpacer(spacer);
default:
return new EditorMenuItem(item);
}
}
private class EditorMenuItem : DrawableOsuMenuItem
{
public EditorMenuItem(MenuItem item)
: base(item)
{
}
return base.CreateDrawableMenuItem(item);
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
BackgroundColour = colourProvider.Background2;
BackgroundColourHover = colourProvider.Background1;
Foreground.Padding = new MarginPadding { Vertical = 2 };
}
}
private class DrawableSpacer : DrawableOsuMenuItem
@ -157,6 +137,7 @@ namespace osu.Game.Screens.Edit.Components.Menus
public DrawableSpacer(MenuItem item)
: base(item)
{
Scale = new Vector2(1, 0.3f);
}
protected override bool OnHover(HoverEvent e) => true;

View File

@ -2,42 +2,38 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Components.Menus
{
public class ScreenSelectionTabControl : OsuTabControl<EditorScreenMode>
public class EditorScreenSwitcherControl : OsuTabControl<EditorScreenMode>
{
public ScreenSelectionTabControl()
public EditorScreenSwitcherControl()
{
AutoSizeAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
TabContainer.RelativeSizeAxes &= ~Axes.X;
TabContainer.AutoSizeAxes = Axes.X;
TabContainer.Padding = new MarginPadding();
AddInternal(new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
Colour = Color4.White.Opacity(0.2f),
});
TabContainer.Padding = new MarginPadding(10);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
AccentColour = colours.Yellow;
AccentColour = colourProvider.Light3;
AddInternal(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background2,
});
}
protected override Dropdown<EditorScreenMode> CreateDropdown() => null;
@ -54,6 +50,15 @@ namespace osu.Game.Screens.Edit.Components.Menus
Text.Margin = new MarginPadding();
Text.Anchor = Anchor.CentreLeft;
Text.Origin = Anchor.CentreLeft;
Text.Font = OsuFont.TorusAlternate;
Bar.Expire();
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
}
protected override void OnActivated()

View File

@ -273,7 +273,7 @@ namespace osu.Game.Screens.Edit
}
}
},
new ScreenSelectionTabControl
new EditorScreenSwitcherControl
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,

View File

@ -6,18 +6,14 @@ using osu.Game.Overlays;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osuTK.Input;
using osu.Game.Localisation;
namespace osu.Game.Screens.OnlinePlay
{
public class FreeModSelectOverlay : ModSelectOverlay, IKeyBindingHandler<PlatformAction>
public class FreeModSelectOverlay : ModSelectOverlay
{
protected override bool ShowTotalMultiplier => false;
@ -29,8 +25,6 @@ namespace osu.Game.Screens.OnlinePlay
set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m);
}
private ShearedButton selectAllButton;
public FreeModSelectOverlay()
: base(OverlayColourScheme.Plum)
{
@ -40,31 +34,10 @@ namespace osu.Game.Screens.OnlinePlay
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys);
protected override IEnumerable<ShearedButton> CreateFooterButtons() => base.CreateFooterButtons().Prepend(
selectAllButton = new ShearedButton(BUTTON_WIDTH)
new SelectAllModsButton(this)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = CommonStrings.SelectAll,
Action = SelectAll
});
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
{
if (e.Repeat)
return false;
switch (e.Action)
{
case PlatformAction.SelectAll:
selectAllButton.TriggerClick();
return true;
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e)
{
}
}
}