1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 10:47:25 +08:00

Merge pull request #6035 from EVAST9919/page-selector

Implement PageSelector component
This commit is contained in:
Bartłomiej Dach 2022-01-05 22:50:04 +01:00 committed by GitHub
commit e232c7bc85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 433 additions and 0 deletions

View File

@ -0,0 +1,79 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface.PageSelector;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestScenePageSelector : OsuTestScene
{
[Cached]
private OverlayColourProvider provider { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
private readonly PageSelector pageSelector;
public TestScenePageSelector()
{
AddRange(new Drawable[]
{
pageSelector = new PageSelector
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
});
}
[Test]
public void TestOmittedPages()
{
setAvailablePages(100);
selectPageIndex(0);
checkVisiblePageNumbers(new[] { 1, 2, 3, 100 });
selectPageIndex(6);
checkVisiblePageNumbers(new[] { 1, 5, 6, 7, 8, 9, 100 });
selectPageIndex(49);
checkVisiblePageNumbers(new[] { 1, 48, 49, 50, 51, 52, 100 });
selectPageIndex(99);
checkVisiblePageNumbers(new[] { 1, 98, 99, 100 });
}
[Test]
public void TestResetCurrentPage()
{
setAvailablePages(10);
selectPageIndex(6);
setAvailablePages(11);
AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0);
}
[Test]
public void TestOutOfBoundsSelection()
{
setAvailablePages(10);
selectPageIndex(11);
AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.AvailablePages.Value - 1);
selectPageIndex(-1);
AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0);
}
private void checkVisiblePageNumbers(int[] expected) => AddAssert($"Sequence is {string.Join(',', expected.Select(i => i.ToString()))}", () => pageSelector.ChildrenOfType<PageSelectorPageButton>().Select(p => p.PageNumber).SequenceEqual(expected));
private void selectPageIndex(int pageIndex) =>
AddStep($"Select page {pageIndex}", () => pageSelector.CurrentPage.Value = pageIndex);
private void setAvailablePages(int availablePages) =>
AddStep($"Set available pages to {availablePages}", () => pageSelector.AvailablePages.Value = availablePages);
}
}

View File

@ -0,0 +1,33 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
internal class PageEllipsis : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
Text = "...",
Colour = colourProvider.Light3,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
}
}
}

View File

@ -0,0 +1,102 @@
// 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;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public class PageSelector : CompositeDrawable
{
public readonly BindableInt CurrentPage = new BindableInt { MinValue = 0, };
public readonly BindableInt AvailablePages = new BindableInt(1) { MinValue = 1, };
private readonly FillFlowContainer itemsFlow;
private readonly PageSelectorPrevNextButton previousPageButton;
private readonly PageSelectorPrevNextButton nextPageButton;
public PageSelector()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
previousPageButton = new PageSelectorPrevNextButton(false, "prev")
{
Action = () => CurrentPage.Value -= 1,
},
itemsFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
},
nextPageButton = new PageSelectorPrevNextButton(true, "next")
{
Action = () => CurrentPage.Value += 1
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
CurrentPage.BindValueChanged(_ => Scheduler.AddOnce(redraw));
AvailablePages.BindValueChanged(_ =>
{
CurrentPage.Value = 0;
// AddOnce as the reset of CurrentPage may also trigger a redraw.
Scheduler.AddOnce(redraw);
}, true);
}
private void redraw()
{
if (CurrentPage.Value >= AvailablePages.Value)
{
CurrentPage.Value = AvailablePages.Value - 1;
return;
}
previousPageButton.Enabled.Value = CurrentPage.Value != 0;
nextPageButton.Enabled.Value = CurrentPage.Value < AvailablePages.Value - 1;
itemsFlow.Clear();
int totalPages = AvailablePages.Value;
bool lastWasEllipsis = false;
for (int i = 0; i < totalPages; i++)
{
int pageIndex = i;
bool shouldShowPage = pageIndex == 0 || pageIndex == totalPages - 1 || Math.Abs(pageIndex - CurrentPage.Value) <= 2;
if (shouldShowPage)
{
lastWasEllipsis = false;
itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1)
{
Action = () => CurrentPage.Value = pageIndex,
Selected = CurrentPage.Value == pageIndex,
});
}
else if (!lastWasEllipsis)
{
lastWasEllipsis = true;
itemsFlow.Add(new PageEllipsis());
}
}
}
}
}

View File

@ -0,0 +1,71 @@
// 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 osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Events;
using JetBrains.Annotations;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public abstract class PageSelectorButton : OsuClickableContainer
{
protected const int DURATION = 200;
[Resolved]
protected OverlayColourProvider ColourProvider { get; private set; }
protected Box Background;
protected PageSelectorButton()
{
AutoSizeAxes = Axes.X;
Height = 20;
}
[BackgroundDependencyLoader]
private void load()
{
Add(new CircularContainer
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Masking = true,
Children = new[]
{
Background = new Box
{
RelativeSizeAxes = Axes.Both
},
CreateContent().With(content =>
{
content.Anchor = Anchor.Centre;
content.Origin = Anchor.Centre;
content.Margin = new MarginPadding { Horizontal = 10 };
})
}
});
}
[NotNull]
protected abstract Drawable CreateContent();
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
UpdateHoverState();
}
protected abstract void UpdateHoverState();
}
}

View File

@ -0,0 +1,70 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Framework.Allocation;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public class PageSelectorPageButton : PageSelectorButton
{
private readonly BindableBool selected = new BindableBool();
public bool Selected
{
set => selected.Value = value;
}
public int PageNumber { get; }
private OsuSpriteText text;
public PageSelectorPageButton(int pageNumber)
{
PageNumber = pageNumber;
Action = () =>
{
if (!selected.Value)
selected.Value = true;
};
}
protected override Drawable CreateContent() => text = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
Text = PageNumber.ToString(),
};
[BackgroundDependencyLoader]
private void load()
{
Background.Colour = ColourProvider.Highlight1;
Background.Alpha = 0;
}
protected override void LoadComplete()
{
base.LoadComplete();
selected.BindValueChanged(onSelectedChanged, true);
}
private void onSelectedChanged(ValueChangedEvent<bool> selected)
{
Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint);
text.FadeColour(selected.NewValue ? ColourProvider.Dark4 : ColourProvider.Light3, DURATION, Easing.OutQuint);
text.Font = text.Font.With(weight: IsHovered ? FontWeight.SemiBold : FontWeight.Regular);
}
protected override void UpdateHoverState()
{
if (selected.Value)
return;
text.FadeColour(IsHovered ? ColourProvider.Light2 : ColourProvider.Light1, DURATION, Easing.OutQuint);
}
}
}

View File

@ -0,0 +1,78 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public class PageSelectorPrevNextButton : PageSelectorButton
{
private readonly bool rightAligned;
private readonly string text;
private SpriteIcon icon;
private OsuSpriteText name;
public PageSelectorPrevNextButton(bool rightAligned, string text)
{
this.rightAligned = rightAligned;
this.text = text;
}
protected override Drawable CreateContent() => new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
name = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12),
Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
Text = text.ToUpper(),
},
icon = new SpriteIcon
{
Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft,
Size = new Vector2(8),
Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
},
}
},
}
};
[BackgroundDependencyLoader]
private void load()
{
Background.Colour = ColourProvider.Dark4;
name.Colour = icon.Colour = ColourProvider.Light1;
}
protected override void LoadComplete()
{
base.LoadComplete();
Enabled.BindValueChanged(enabled => Background.FadeTo(enabled.NewValue ? 1 : 0.5f, DURATION), true);
}
protected override void UpdateHoverState() =>
Background.FadeColour(IsHovered ? ColourProvider.Dark3 : ColourProvider.Dark4, DURATION, Easing.OutQuint);
}
}