mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Added pagination
This commit is contained in:
parent
07d4539add
commit
cfb39b986d
172
src/Artemis.UI.Shared/Controls/Pagination/Pagination.cs
Normal file
172
src/Artemis.UI.Shared/Controls/Pagination/Pagination.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
39
src/Artemis.UI.Shared/Controls/Pagination/PaginationPage.cs
Normal file
39
src/Artemis.UI.Shared/Controls/Pagination/PaginationPage.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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>
|
||||||
@ -1,18 +1,25 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<Styles.Resources>
|
<Styles.Resources>
|
||||||
<VisualBrush x:Key="CheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,12,12">
|
<ResourceDictionary>
|
||||||
<VisualBrush.Visual>
|
<VisualBrush x:Key="CheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,12,12">
|
||||||
<Canvas Width="12" Height="12">
|
<VisualBrush.Visual>
|
||||||
<Rectangle Width="6" Height="6" Fill="Black" Opacity="0.15" />
|
<Canvas Width="12" Height="12">
|
||||||
<Rectangle Width="6" Height="6" Canvas.Left="6" />
|
<Rectangle Width="6" Height="6" Fill="Black" Opacity="0.15" />
|
||||||
<Rectangle Width="6" Height="6" Canvas.Top="6" />
|
<Rectangle Width="6" Height="6" Canvas.Left="6" />
|
||||||
<Rectangle Width="6" Height="6" Canvas.Left="6" Canvas.Top="6" Fill="Black" Opacity="0.15" />
|
<Rectangle Width="6" Height="6" Canvas.Top="6" />
|
||||||
</Canvas>
|
<Rectangle Width="6" Height="6" Canvas.Left="6" Canvas.Top="6" Fill="Black" Opacity="0.15" />
|
||||||
</VisualBrush.Visual>
|
</Canvas>
|
||||||
</VisualBrush>
|
</VisualBrush.Visual>
|
||||||
|
</VisualBrush>
|
||||||
|
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<MergeResourceInclude Source="/Controls/Pagination/PaginationStyles.axaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
|
|
||||||
|
|
||||||
<!-- Custom controls -->
|
<!-- Custom controls -->
|
||||||
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
|
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
|
||||||
<StyleInclude Source="/Styles/Controls/GradientPickerButton.axaml" />
|
<StyleInclude Source="/Styles/Controls/GradientPickerButton.axaml" />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user