1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Added pagination

This commit is contained in:
Robert 2023-07-21 22:11:35 +02:00
parent 07d4539add
commit cfb39b986d
6 changed files with 356 additions and 10 deletions

View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using ReactiveUI;
namespace Artemis.UI.Shared.Pagination;
/// <summary>
/// Represents a pagination control that can be used to switch between pages.
/// </summary>
[TemplatePart("PART_PreviousButton", typeof(Button))]
[TemplatePart("PART_NextButton", typeof(Button))]
[TemplatePart("PART_PagesView", typeof(StackPanel))]
public partial class Pagination : TemplatedControl
{
/// <inheritdoc />
public Pagination()
{
PropertyChanged += OnPropertyChanged;
}
public Button? PreviousButton { get; set; }
public Button? NextButton { get; set; }
public StackPanel? PagesView { get; set; }
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (PreviousButton != null)
PreviousButton.Click -= PreviousButtonOnClick;
if (NextButton != null)
NextButton.Click -= NextButtonOnClick;
PreviousButton = e.NameScope.Find<Button>("PART_PreviousButton");
NextButton = e.NameScope.Find<Button>("PART_NextButton");
PagesView = e.NameScope.Find<StackPanel>("PART_PagesView");
if (PreviousButton != null)
PreviousButton.Click += PreviousButtonOnClick;
if (NextButton != null)
NextButton.Click += NextButtonOnClick;
Update();
}
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ValueProperty)
Update();
}
private void NextButtonOnClick(object? sender, RoutedEventArgs e)
{
Value++;
}
private void PreviousButtonOnClick(object? sender, RoutedEventArgs e)
{
Value--;
}
private void Update()
{
if (PagesView == null)
return;
List<int> pages = GetPages(Value, Maximum);
// Remove extra children
while (PagesView.Children.Count > pages.Count)
{
PagesView.Children.RemoveAt(PagesView.Children.Count - 1);
}
if (PagesView.Children.Count > pages.Count)
PagesView.Children.RemoveRange(0, PagesView.Children.Count - pages.Count);
// Add/modify children
for (int i = 0; i < pages.Count; i++)
{
int page = pages[i];
// -1 indicates an ellipsis (...)
if (page == -1)
{
if (PagesView.Children.ElementAtOrDefault(i) is not PaginationEllipsis)
{
if (PagesView.Children.Count - 1 >= i)
PagesView.Children[i] = new PaginationEllipsis();
else
PagesView.Children.Add(new PaginationEllipsis());
}
}
// Anything else indicates a regular page
else
{
if (PagesView.Children.ElementAtOrDefault(i) is PaginationPage paginationPage)
{
paginationPage.Page = page;
paginationPage.Command = ReactiveCommand.Create(() => Value = page);
continue;
}
paginationPage = new PaginationPage {Page = page, Command = ReactiveCommand.Create(() => Value = page)};
if (PagesView.Children.Count - 1 >= i)
PagesView.Children[i] = paginationPage;
else
PagesView.Children.Add(paginationPage);
}
}
foreach (Control child in PagesView.Children)
{
if (child is PaginationPage paginationPage)
((IPseudoClasses) paginationPage.Classes).Set(":selected", paginationPage.Page == Value);
}
}
private void PaginationPageOnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (sender is PaginationPage paginationPage)
Value = paginationPage.Page;
}
private static List<int> GetPages(int currentPage, int pageCount)
{
// Determine the delta based on how close to the edge the current page is
int delta;
if (pageCount <= 7)
delta = 7;
else
delta = currentPage > 4 && currentPage < pageCount - 3 ? 2 : 4;
int start = currentPage - delta / 2;
int end = currentPage + delta / 2;
if (start - 1 == 1 || end + 1 == pageCount)
{
start += 1;
end += 1;
}
// Determine start and end numbers based on how close to the edge the current page is
start = currentPage > delta ? Math.Min(start, pageCount - delta) : 1;
end = currentPage > delta ? Math.Min(end, pageCount) : Math.Min(pageCount, delta + 1);
// Start with the pages neighbouring the current page
List<int> paginationItems = Enumerable.Range(start, end - start + 1).ToList();
// If not starting at the first page, add the first page and an ellipsis (-1)
if (paginationItems.First() != 1)
{
paginationItems.Insert(0, 1);
paginationItems.Insert(1, -1);
}
// If not ending at the last page, add an ellipsis (-1) and the last page
if (paginationItems.Last() < pageCount)
{
paginationItems.Add(-1);
paginationItems.Add(pageCount);
}
return paginationItems;
}
}

View File

@ -0,0 +1,38 @@
using System;
using Avalonia;
using Avalonia.Controls.Primitives;
namespace Artemis.UI.Shared.Pagination;
public partial class Pagination : TemplatedControl
{
/// <summary>
/// Defines the <see cref="Value" /> property
/// </summary>
public static readonly StyledProperty<int> ValueProperty =
AvaloniaProperty.Register<Pagination, int>(nameof(Value), 1, enableDataValidation: true, coerce: (p, v) => Math.Clamp(v, 1, ((Pagination) p).Maximum));
/// <summary>
/// Defines the <see cref="Maximum" /> property
/// </summary>
public static readonly StyledProperty<int> MaximumProperty =
AvaloniaProperty.Register<Pagination, int>(nameof(Maximum), 10, coerce: (_, v) => Math.Max(1, v));
/// <summary>
/// Gets or sets the numeric value of a NumberBox.
/// </summary>
public int Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
/// <summary>
/// Gets or sets the numerical maximum for Value.
/// </summary>
public int Maximum
{
get => GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
}

View File

@ -0,0 +1,10 @@
using Avalonia.Controls.Primitives;
namespace Artemis.UI.Shared.Pagination;
/// <summary>
/// Represents a pagination ellipsis control that indicates a gap in pagination.
/// </summary>
public class PaginationEllipsis : TemplatedControl
{
}

View File

@ -0,0 +1,39 @@
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls.Primitives;
namespace Artemis.UI.Shared.Pagination;
/// <summary>
/// Represents a pagination page control that indicates a page in pagination.
/// </summary>
public class PaginationPage : TemplatedControl
{
/// <summary>
/// Defines the <see cref="Page" /> property.
/// </summary>
public static readonly StyledProperty<int> PageProperty = AvaloniaProperty.Register<PaginationPage, int>(nameof(Page));
/// <summary>
/// Gets or sets the page that is being represented.
/// </summary>
public int Page
{
get => GetValue(PageProperty);
set => SetValue(PageProperty, value);
}
/// <summary>
/// Defines the <see cref="Command" /> property.
/// </summary>
public static readonly StyledProperty<ICommand> CommandProperty = AvaloniaProperty.Register<PaginationPage, ICommand>(nameof(Command));
/// <summary>
/// Gets or sets the command to invoke when the page is clicked.
/// </summary>
public ICommand Command
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
}

View File

@ -0,0 +1,80 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:pagination="clr-namespace:Artemis.UI.Shared.Pagination"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="30" Width="400">
<StackPanel Spacing="20">
<pagination:Pagination Value="{CompiledBinding Value, ElementName=Numeric, Mode=TwoWay}" HorizontalAlignment="Center"/>
<pagination:Pagination Value="{CompiledBinding Value, ElementName=Numeric, Mode=TwoWay}" Maximum="999" HorizontalAlignment="Center"/>
<NumericUpDown Name="Numeric" Value="1" Width="120" HorizontalAlignment="Center"></NumericUpDown>
</StackPanel>
</Border>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type pagination:Pagination}" TargetType="pagination:Pagination">
<Setter Property="Template">
<ControlTemplate>
<StackPanel Orientation="Horizontal" Spacing="2">
<Button Name="PART_PreviousButton" Theme="{StaticResource TransparentButton}" Width="32" Height="30">
<ui:SymbolIcon Symbol="ChevronLeft" />
</Button>
<StackPanel Name="PART_PagesView" Orientation="Horizontal" Spacing="2"></StackPanel>
<Button Name="PART_NextButton" Theme="{StaticResource TransparentButton}" Width="32" Height="30">
<ui:SymbolIcon Symbol="ChevronRight" />
</Button>
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type pagination:PaginationPage}" TargetType="pagination:PaginationPage">
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Button Theme="{StaticResource TransparentButton}"
MinWidth="32"
Padding="6 5"
Content="{TemplateBinding Page}"
Command="{TemplateBinding Command}"/>
<Rectangle Name="SelectionIndicator"
Width="16"
Height="3"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
RadiusX="2"
RadiusY="2"
IsVisible="False"
RenderTransform="scaleX(0)"
Fill="{DynamicResource TreeViewItemSelectionIndicatorForeground}">
<Rectangle.Transitions>
<Transitions>
<TransformOperationsTransition Duration="00:00:00.167"
Property="RenderTransform"
Easing="0,0 0,1" />
</Transitions>
</Rectangle.Transitions>
</Rectangle>
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^:selected">
<Style Selector="^ /template/ Rectangle#SelectionIndicator">
<Setter Property="IsVisible" Value="True" />
<Setter Property="RenderTransform" Value="scaleX(1)" />
</Style>
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type pagination:PaginationEllipsis}" TargetType="pagination:PaginationEllipsis">
<Setter Property="Template">
<ControlTemplate>
<TextBlock VerticalAlignment="Bottom" Margin="11 5">...</TextBlock>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@ -1,18 +1,25 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<VisualBrush x:Key="CheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,12,12">
<VisualBrush.Visual>
<Canvas Width="12" Height="12">
<Rectangle Width="6" Height="6" Fill="Black" Opacity="0.15" />
<Rectangle Width="6" Height="6" Canvas.Left="6" />
<Rectangle Width="6" Height="6" Canvas.Top="6" />
<Rectangle Width="6" Height="6" Canvas.Left="6" Canvas.Top="6" Fill="Black" Opacity="0.15" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
<ResourceDictionary>
<VisualBrush x:Key="CheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,12,12">
<VisualBrush.Visual>
<Canvas Width="12" Height="12">
<Rectangle Width="6" Height="6" Fill="Black" Opacity="0.15" />
<Rectangle Width="6" Height="6" Canvas.Left="6" />
<Rectangle Width="6" Height="6" Canvas.Top="6" />
<Rectangle Width="6" Height="6" Canvas.Left="6" Canvas.Top="6" Fill="Black" Opacity="0.15" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
<ResourceDictionary.MergedDictionaries>
<MergeResourceInclude Source="/Controls/Pagination/PaginationStyles.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>
<!-- Custom controls -->
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
<StyleInclude Source="/Styles/Controls/GradientPickerButton.axaml" />