committed by
GitHub
120 changed files with 7825 additions and 586 deletions
@ -0,0 +1,11 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselDemoPage"> |
|||
<NavigationPage x:Name="SampleNav"> |
|||
<NavigationPage.Styles> |
|||
<Style Selector="NavigationPage#SampleNav /template/ Border#PART_NavigationBar"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
</NavigationPage.Styles> |
|||
</NavigationPage> |
|||
</UserControl> |
|||
@ -0,0 +1,53 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselDemoPage : UserControl |
|||
{ |
|||
private static readonly (string Group, string Title, string Description, Func<UserControl> Factory)[] Demos = |
|||
{ |
|||
// Overview
|
|||
("Overview", "Getting Started", |
|||
"Basic Carousel with image items and previous/next navigation buttons.", |
|||
() => new CarouselGettingStartedPage()), |
|||
|
|||
// Features
|
|||
("Features", "Transitions", |
|||
"Configure page transitions: PageSlide, CrossFade, 3D Rotation, or None.", |
|||
() => new CarouselTransitionsPage()), |
|||
("Features", "Customization", |
|||
"Adjust orientation and transition type to tailor the carousel layout.", |
|||
() => new CarouselCustomizationPage()), |
|||
("Features", "Gestures & Keyboard", |
|||
"Navigate items via swipe gesture and arrow keys. Toggle each input mode on and off.", |
|||
() => new CarouselGesturesPage()), |
|||
("Features", "Vertical Orientation", |
|||
"Carousel with Orientation set to Vertical, navigated with Up/Down keys, swipe, or buttons.", |
|||
() => new CarouselVerticalPage()), |
|||
("Features", "Multi-Item Peek", |
|||
"Adjust ViewportFraction to show multiple items simultaneously with adjacent cards peeking.", |
|||
() => new CarouselMultiItemPage()), |
|||
("Features", "Data Binding", |
|||
"Bind Carousel to an ObservableCollection and add, remove, or shuffle items at runtime.", |
|||
() => new CarouselDataBindingPage()), |
|||
|
|||
// Showcases
|
|||
("Showcases", "Curated Gallery", |
|||
"Editorial art gallery app with DrawerPage navigation, hero Carousel with PipsPager dots, and a horizontal peek carousel for collection highlights.", |
|||
() => new CarouselGalleryAppPage()), |
|||
}; |
|||
|
|||
public CarouselDemoPage() |
|||
{ |
|||
InitializeComponent(); |
|||
Loaded += OnLoaded; |
|||
} |
|||
|
|||
private async void OnLoaded(object? sender, RoutedEventArgs e) |
|||
{ |
|||
await SampleNav.PushAsync(NavigationDemoHelper.CreateGalleryHomePage(SampleNav, Demos), null); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselCustomizationPage"> |
|||
<DockPanel> |
|||
<ScrollViewer DockPanel.Dock="Right" Width="260"> |
|||
<StackPanel Margin="12" Spacing="8"> |
|||
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" /> |
|||
|
|||
<Button x:Name="PreviousButton" |
|||
Content="Previous" |
|||
HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="NextButton" |
|||
Content="Next" |
|||
HorizontalAlignment="Stretch" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Orientation" FontWeight="SemiBold" FontSize="13" /> |
|||
<ComboBox x:Name="OrientationCombo" |
|||
HorizontalAlignment="Stretch" |
|||
SelectedIndex="0"> |
|||
<ComboBoxItem>Horizontal</ComboBoxItem> |
|||
<ComboBoxItem>Vertical</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="Viewport Fraction" FontWeight="SemiBold" FontSize="13" /> |
|||
<Grid ColumnDefinitions="*,48" ColumnSpacing="8"> |
|||
<Slider x:Name="ViewportSlider" |
|||
Minimum="0.33" |
|||
Maximum="1.0" |
|||
Value="1.0" |
|||
TickFrequency="0.01" |
|||
HorizontalAlignment="Stretch" /> |
|||
<TextBlock x:Name="ViewportLabel" |
|||
Grid.Column="1" |
|||
Text="1.00" |
|||
VerticalAlignment="Center" |
|||
HorizontalAlignment="Right" |
|||
FontWeight="SemiBold" /> |
|||
</Grid> |
|||
<TextBlock x:Name="ViewportHint" |
|||
Text="1.00 shows a single full page." |
|||
FontSize="11" |
|||
Opacity="0.6" |
|||
TextWrapping="Wrap" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="14" /> |
|||
|
|||
<CheckBox x:Name="WrapSelectionCheck" |
|||
Content="Wrap Selection" |
|||
IsChecked="False" |
|||
IsCheckedChanged="OnWrapSelectionChanged" /> |
|||
|
|||
<CheckBox x:Name="SwipeEnabledCheck" |
|||
Content="Swipe Enabled" |
|||
IsChecked="False" |
|||
IsCheckedChanged="OnSwipeEnabledChanged" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" /> |
|||
<TextBlock x:Name="StatusText" |
|||
Text="Orientation: Horizontal" |
|||
Opacity="0.7" |
|||
TextWrapping="Wrap" /> |
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border DockPanel.Dock="Right" Width="1" |
|||
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" /> |
|||
|
|||
<Border Margin="12" |
|||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" |
|||
CornerRadius="6" |
|||
ClipToBounds="True"> |
|||
<Carousel x:Name="DemoCarousel" Height="300"> |
|||
<Carousel.PageTransition> |
|||
<PageSlide Duration="0.25" Orientation="Horizontal" /> |
|||
</Carousel.PageTransition> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 1: Delicate Arch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 2: Hirsch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 3: Maple Leaf" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
</Carousel> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,48 @@ |
|||
using System; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselCustomizationPage : UserControl |
|||
{ |
|||
public CarouselCustomizationPage() |
|||
{ |
|||
InitializeComponent(); |
|||
PreviousButton.Click += (_, _) => DemoCarousel.Previous(); |
|||
NextButton.Click += (_, _) => DemoCarousel.Next(); |
|||
OrientationCombo.SelectionChanged += (_, _) => ApplyOrientation(); |
|||
ViewportSlider.ValueChanged += OnViewportFractionChanged; |
|||
} |
|||
|
|||
private void ApplyOrientation() |
|||
{ |
|||
var horizontal = OrientationCombo.SelectedIndex == 0; |
|||
var axis = horizontal ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical; |
|||
DemoCarousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), axis); |
|||
StatusText.Text = $"Orientation: {(horizontal ? "Horizontal" : "Vertical")}"; |
|||
} |
|||
|
|||
private void OnViewportFractionChanged(object? sender, RangeBaseValueChangedEventArgs e) |
|||
{ |
|||
var value = Math.Round(e.NewValue, 2); |
|||
DemoCarousel.ViewportFraction = value; |
|||
ViewportLabel.Text = value.ToString("0.00"); |
|||
ViewportHint.Text = value >= 1d ? |
|||
"1.00 shows a single full page." : |
|||
$"{1d / value:0.##} pages fit in view. Try 0.80 for peeking."; |
|||
} |
|||
|
|||
private void OnWrapSelectionChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DemoCarousel.WrapSelection = WrapSelectionCheck.IsChecked == true; |
|||
} |
|||
|
|||
private void OnSwipeEnabledChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DemoCarousel.IsSwipeEnabled = SwipeEnabledCheck.IsChecked == true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:pages="clr-namespace:ControlCatalog.Pages" |
|||
x:Class="ControlCatalog.Pages.CarouselDataBindingPage"> |
|||
<DockPanel> |
|||
<ScrollViewer DockPanel.Dock="Right" Width="260"> |
|||
<StackPanel Margin="12" Spacing="8"> |
|||
<TextBlock Text="Collection" FontWeight="SemiBold" FontSize="16" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" /> |
|||
<Button x:Name="PreviousButton" Content="Previous" HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="NextButton" Content="Next" HorizontalAlignment="Stretch" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Modify Items" FontWeight="SemiBold" FontSize="13" /> |
|||
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.6" |
|||
Text="The Carousel is bound to an ObservableCollection. Changes reflect immediately." /> |
|||
<Button x:Name="AddButton" Content="Add Item" HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="RemoveButton" Content="Remove Current" HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="ShuffleButton" Content="Shuffle" HorizontalAlignment="Stretch" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" /> |
|||
<TextBlock x:Name="StatusText" Text="Item: 1 / 4" |
|||
Opacity="0.7" TextWrapping="Wrap" /> |
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border DockPanel.Dock="Right" Width="1" |
|||
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" /> |
|||
|
|||
<Border Margin="12" |
|||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" CornerRadius="6" ClipToBounds="True"> |
|||
<Carousel x:Name="DemoCarousel" |
|||
Height="280" |
|||
IsSwipeEnabled="True"> |
|||
<Carousel.PageTransition> |
|||
<CrossFade Duration="0.3" /> |
|||
</Carousel.PageTransition> |
|||
<Carousel.ItemTemplate> |
|||
<DataTemplate x:DataType="pages:CarouselCardItem"> |
|||
<Border CornerRadius="14" Margin="14,12" ClipToBounds="True" |
|||
Background="{Binding Background}"> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8"> |
|||
<TextBlock Text="{Binding Number}" FontSize="52" FontWeight="Bold" |
|||
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="{Binding Title}" FontSize="15" FontWeight="SemiBold" |
|||
Foreground="{Binding Accent}" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</DataTemplate> |
|||
</Carousel.ItemTemplate> |
|||
</Carousel> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,95 @@ |
|||
using System; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class CarouselCardItem |
|||
{ |
|||
public string Number { get; set; } = ""; |
|||
public string Title { get; set; } = ""; |
|||
public IBrush Background { get; set; } = Brushes.Gray; |
|||
public IBrush Accent { get; set; } = Brushes.White; |
|||
} |
|||
|
|||
public partial class CarouselDataBindingPage : UserControl |
|||
{ |
|||
private static readonly (string Title, string Color, string Accent)[] Palette = |
|||
{ |
|||
("Neon Pulse", "#3525CD", "#C3C0FF"), ("Ephemeral Blue", "#0891B2", "#BAF0FA"), |
|||
("Forest Forms", "#059669", "#A7F3D0"), ("Golden Hour", "#D97706", "#FDE68A"), |
|||
("Crimson Wave", "#BE185D", "#FBCFE8"), ("Stone Age", "#57534E", "#D6D3D1"), |
|||
}; |
|||
|
|||
private readonly ObservableCollection<CarouselCardItem> _items = new(); |
|||
private int _addCounter; |
|||
|
|||
public CarouselDataBindingPage() |
|||
{ |
|||
InitializeComponent(); |
|||
DemoCarousel.ItemsSource = _items; |
|||
DemoCarousel.SelectionChanged += OnSelectionChanged; |
|||
|
|||
for (var i = 0; i < 4; i++) |
|||
AppendItem(); |
|||
|
|||
PreviousButton.Click += (_, _) => DemoCarousel.Previous(); |
|||
NextButton.Click += (_, _) => DemoCarousel.Next(); |
|||
AddButton.Click += OnAddItem; |
|||
RemoveButton.Click += OnRemoveCurrent; |
|||
ShuffleButton.Click += OnShuffle; |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void AppendItem() |
|||
{ |
|||
var (title, color, accent) = Palette[_addCounter % Palette.Length]; |
|||
_items.Add(new CarouselCardItem |
|||
{ |
|||
Number = $"{_items.Count + 1:D2}", |
|||
Title = title, |
|||
Background = new SolidColorBrush(Color.Parse(color)), |
|||
Accent = new SolidColorBrush(Color.Parse(accent)), |
|||
}); |
|||
_addCounter++; |
|||
} |
|||
|
|||
private void OnAddItem(object? sender, RoutedEventArgs e) |
|||
{ |
|||
AppendItem(); |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void OnRemoveCurrent(object? sender, RoutedEventArgs e) |
|||
{ |
|||
if (_items.Count == 0) |
|||
return; |
|||
var idx = Math.Clamp(DemoCarousel.SelectedIndex, 0, _items.Count - 1); |
|||
_items.RemoveAt(idx); |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void OnShuffle(object? sender, RoutedEventArgs e) |
|||
{ |
|||
var rng = new Random(); |
|||
var shuffled = _items.OrderBy(_ => rng.Next()).ToList(); |
|||
_items.Clear(); |
|||
foreach (var item in shuffled) |
|||
_items.Add(item); |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void UpdateStatus() |
|||
{ |
|||
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {_items.Count}"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,557 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselGalleryAppPage" |
|||
Background="#F8F9FB"> |
|||
<UserControl.Resources> |
|||
<!-- White pip colors for the hero dark background --> |
|||
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForeground" Color="#7FFFFFFF" /> |
|||
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundPointerOver" Color="#BFFFFFFF" /> |
|||
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundPressed" Color="#BFFFFFFF" /> |
|||
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundSelected" Color="#FFFFFFFF" /> |
|||
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundDisabled" Color="#3FFFFFFF" /> |
|||
<SolidColorBrush x:Key="PipsPagerNavigationButtonForeground" Color="#7FFFFFFF" /> |
|||
<SolidColorBrush x:Key="PipsPagerNavigationButtonForegroundPointerOver" Color="#BFFFFFFF" /> |
|||
<SolidColorBrush x:Key="PipsPagerNavigationButtonForegroundPressed" Color="#BFFFFFFF" /> |
|||
</UserControl.Resources> |
|||
|
|||
|
|||
<DockPanel> |
|||
|
|||
<!-- Right info panel — visible when width >= 640px --> |
|||
<ScrollViewer x:Name="InfoPanel" DockPanel.Dock="Right" Width="290" IsVisible="False"> |
|||
<StackPanel Margin="16" Spacing="16"> |
|||
|
|||
<TextBlock Text="Curated Gallery" FontSize="16" FontWeight="SemiBold" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.7" |
|||
Text="Art gallery editorial app showcasing a full-bleed hero Carousel synced with a pill-shaped PipsPager, a peek Collection Highlights scroll list, Curators' Choice cards, and a Join the Circle subscription section. Navigation via DrawerPage side menu." /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Navigation Flow" FontSize="13" FontWeight="SemiBold" /> |
|||
<StackPanel Spacing="4"> |
|||
<TextBlock FontSize="12" TextWrapping="Wrap" Text="1. DrawerPage: root, side placement" /> |
|||
<TextBlock FontSize="12" TextWrapping="Wrap" Text="2. Hamburger overlaid on hero opens the drawer pane" /> |
|||
<TextBlock FontSize="12" TextWrapping="Wrap" Text="3. Hero: full-bleed Carousel + PipsPager (pill dots)" /> |
|||
<TextBlock FontSize="12" TextWrapping="Wrap" Text="4. PipsPager synced bidirectionally with Carousel" /> |
|||
<TextBlock FontSize="12" TextWrapping="Wrap" Text="5. Mouse drag on hero navigates carousel slides" /> |
|||
</StackPanel> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Key Code" FontSize="13" FontWeight="SemiBold" /> |
|||
<Border Background="{DynamicResource SystemControlBackgroundBaseLowBrush}" |
|||
CornerRadius="4" Padding="8"> |
|||
<TextBlock FontFamily="Cascadia Code,Consolas,Menlo,monospace" |
|||
FontSize="10" TextWrapping="Wrap" |
|||
Text="HeroCarousel.SelectionChanged
 += OnHeroSelectionChanged;
HeroPager.SelectedIndexChanged
 += OnPagerIndexChanged;

// Bidirectional sync guard
if (_syncing) return;
_syncing = true;
HeroPager.SelectedPageIndex
 = HeroCarousel.SelectedIndex;
_syncing = false;" /> |
|||
</Border> |
|||
|
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" CornerRadius="8" ClipToBounds="True"> |
|||
|
|||
<DrawerPage x:Name="RootDrawer" |
|||
DrawerLength="260" |
|||
IsGestureEnabled="True"> |
|||
<DrawerPage.Styles> |
|||
<!-- Hide the DrawerPage built-in top bar so only our custom hero overlay bar is shown --> |
|||
<Style Selector="DrawerPage#RootDrawer /template/ Border#PART_TopBar"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
</DrawerPage.Styles> |
|||
|
|||
<!-- Drawer header --> |
|||
<DrawerPage.DrawerHeader> |
|||
<Border Background="#3525CD" Padding="20,32,20,20"> |
|||
<StackPanel Spacing="4"> |
|||
<TextBlock Text="CURATED" |
|||
FontSize="20" |
|||
FontWeight="Bold" |
|||
Foreground="White" |
|||
LetterSpacing="-0.4" /> |
|||
<TextBlock Text="The Digital Gallery" |
|||
FontSize="12" |
|||
Foreground="#C3C0FF" |
|||
Opacity="0.85" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</DrawerPage.DrawerHeader> |
|||
|
|||
<!-- Drawer menu --> |
|||
<DrawerPage.Drawer> |
|||
<StackPanel Background="#F8F9FB"> |
|||
<ListBox x:Name="DrawerMenu" |
|||
Background="Transparent" |
|||
SelectionChanged="OnDrawerMenuSelectionChanged"> |
|||
<ListBoxItem Padding="20,14"> |
|||
<StackPanel Orientation="Horizontal" Spacing="16"> |
|||
<Path Data="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" |
|||
Fill="#3525CD" Width="20" Height="20" Stretch="Uniform" /> |
|||
<TextBlock Text="Discover" FontSize="15" FontWeight="SemiBold" |
|||
Foreground="#191C1E" VerticalAlignment="Center" /> |
|||
</StackPanel> |
|||
</ListBoxItem> |
|||
<ListBoxItem Padding="20,14"> |
|||
<StackPanel Orientation="Horizontal" Spacing="16"> |
|||
<Path Data="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z" |
|||
Fill="#464555" Width="20" Height="20" Stretch="Uniform" /> |
|||
<TextBlock Text="Collection" FontSize="15" |
|||
Foreground="#464555" VerticalAlignment="Center" /> |
|||
</StackPanel> |
|||
</ListBoxItem> |
|||
<ListBoxItem Padding="20,14"> |
|||
<StackPanel Orientation="Horizontal" Spacing="16"> |
|||
<Path Data="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l7.59-7.59L21 8l-9 9z" |
|||
Fill="#464555" Width="20" Height="20" Stretch="Uniform" /> |
|||
<TextBlock Text="Archive" FontSize="15" |
|||
Foreground="#464555" VerticalAlignment="Center" /> |
|||
</StackPanel> |
|||
</ListBoxItem> |
|||
<ListBoxItem Padding="20,14"> |
|||
<StackPanel Orientation="Horizontal" Spacing="16"> |
|||
<Path Data="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" |
|||
Fill="#464555" Width="20" Height="20" Stretch="Uniform" /> |
|||
<TextBlock Text="Profile" FontSize="15" |
|||
Foreground="#464555" VerticalAlignment="Center" /> |
|||
</StackPanel> |
|||
</ListBoxItem> |
|||
</ListBox> |
|||
|
|||
<Separator Margin="20,8" /> |
|||
|
|||
<StackPanel Margin="20,8" Spacing="0"> |
|||
<TextBlock Text="EXHIBITIONS" |
|||
FontSize="11" |
|||
FontWeight="Bold" |
|||
Foreground="#777587" |
|||
LetterSpacing="1.2" |
|||
Margin="0,0,0,16" /> |
|||
|
|||
<Grid ColumnDefinitions="4,*" Margin="0,0,0,14"> |
|||
<Border Grid.Column="0" Width="4" Height="38" |
|||
CornerRadius="2" Background="#3525CD" |
|||
VerticalAlignment="Center" Margin="0,0,14,0" /> |
|||
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center"> |
|||
<TextBlock Text="Neon Pulse" FontSize="14" FontWeight="SemiBold" |
|||
Foreground="#191C1E" /> |
|||
<TextBlock Text="Opens March 20" FontSize="11" Foreground="#777587" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
|
|||
<Grid ColumnDefinitions="4,*" Margin="0,0,0,14"> |
|||
<Border Grid.Column="0" Width="4" Height="38" |
|||
CornerRadius="2" Background="#4F46E5" |
|||
VerticalAlignment="Center" Margin="0,0,14,0" /> |
|||
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center"> |
|||
<TextBlock Text="Fragmented Forms" FontSize="14" FontWeight="SemiBold" |
|||
Foreground="#191C1E" /> |
|||
<TextBlock Text="Now Open" FontSize="11" Foreground="#4F46E5" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
|
|||
<Grid ColumnDefinitions="4,*"> |
|||
<Border Grid.Column="0" Width="4" Height="38" |
|||
CornerRadius="2" Background="#B84B00" |
|||
VerticalAlignment="Center" Margin="0,0,14,0" /> |
|||
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center"> |
|||
<TextBlock Text="The Digital Horizon" FontSize="14" FontWeight="SemiBold" |
|||
Foreground="#191C1E" /> |
|||
<TextBlock Text="Closing Soon" FontSize="11" Foreground="#B84B00" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
</DrawerPage.Drawer> |
|||
|
|||
<!-- Main content: hero carousel IS the header --> |
|||
<DrawerPage.Content> |
|||
<Grid RowDefinitions="Auto,*"> |
|||
|
|||
<!-- Row 0: Hero carousel header — also handles mouse drag for swipe navigation --> |
|||
<Grid Height="320" |
|||
PointerPressed="OnHeroPointerPressed" |
|||
PointerReleased="OnHeroPointerReleased" |
|||
PointerCaptureLost="OnHeroPointerCaptureLost"> |
|||
|
|||
<!-- Full-bleed hero carousel --> |
|||
<Carousel x:Name="HeroCarousel" |
|||
IsSwipeEnabled="True"> |
|||
<Carousel.PageTransition> |
|||
<PageSlide Duration="0.35" Orientation="Horizontal" /> |
|||
</Carousel.PageTransition> |
|||
|
|||
<!-- Hero 1 --> |
|||
<Grid> |
|||
<Image Source="/Assets/ModernApp/gallery_city.jpg" Stretch="UniformToFill" /> |
|||
<Border> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> |
|||
<GradientStop Color="#88000000" Offset="0" /> |
|||
<GradientStop Color="#00000000" Offset="0.35" /> |
|||
<GradientStop Color="#00000000" Offset="0.55" /> |
|||
<GradientStop Color="#CC000000" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
</Border> |
|||
<StackPanel VerticalAlignment="Bottom" Margin="20,0,20,44" Spacing="4"> |
|||
<TextBlock Text="FEATURED EXHIBITION" |
|||
FontSize="11" FontWeight="Bold" |
|||
Foreground="#C3C0FF" LetterSpacing="1.5" /> |
|||
<TextBlock Text="Neon Pulse: The New Abstract" |
|||
FontSize="22" FontWeight="Bold" |
|||
Foreground="White" TextWrapping="Wrap" LetterSpacing="-0.4" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
|
|||
<!-- Hero 2 --> |
|||
<Grid> |
|||
<Image Source="/Assets/ModernApp/gallery_alpine.jpg" Stretch="UniformToFill" /> |
|||
<Border> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> |
|||
<GradientStop Color="#88000000" Offset="0" /> |
|||
<GradientStop Color="#00000000" Offset="0.35" /> |
|||
<GradientStop Color="#00000000" Offset="0.55" /> |
|||
<GradientStop Color="#CC000000" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
</Border> |
|||
<StackPanel VerticalAlignment="Bottom" Margin="20,0,20,44" Spacing="4"> |
|||
<TextBlock Text="NOW OPEN" |
|||
FontSize="11" FontWeight="Bold" |
|||
Foreground="#C3C0FF" LetterSpacing="1.5" /> |
|||
<TextBlock Text="Fragmented Forms: Sculpture Today" |
|||
FontSize="22" FontWeight="Bold" |
|||
Foreground="White" TextWrapping="Wrap" LetterSpacing="-0.4" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
|
|||
<!-- Hero 3 --> |
|||
<Grid> |
|||
<Image Source="/Assets/ModernApp/gallery_venice.jpg" Stretch="UniformToFill" /> |
|||
<Border> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> |
|||
<GradientStop Color="#88000000" Offset="0" /> |
|||
<GradientStop Color="#00000000" Offset="0.35" /> |
|||
<GradientStop Color="#00000000" Offset="0.55" /> |
|||
<GradientStop Color="#CC000000" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
</Border> |
|||
<StackPanel VerticalAlignment="Bottom" Margin="20,0,20,44" Spacing="4"> |
|||
<TextBlock Text="CLOSING SOON" |
|||
FontSize="11" FontWeight="Bold" |
|||
Foreground="#FFCDD2" LetterSpacing="1.5" /> |
|||
<TextBlock Text="The Digital Horizon: Web3 & Generative Art" |
|||
FontSize="22" FontWeight="Bold" |
|||
Foreground="White" TextWrapping="Wrap" LetterSpacing="-0.4" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
</Carousel> |
|||
|
|||
<!-- PipsPager overlaid near bottom of hero — pill-shaped, no nav arrows --> |
|||
<PipsPager x:Name="HeroPager" |
|||
NumberOfPages="3" |
|||
IsPreviousButtonVisible="False" |
|||
IsNextButtonVisible="False" |
|||
HorizontalAlignment="Center" |
|||
VerticalAlignment="Bottom" |
|||
Margin="0,0,0,18"> |
|||
<PipsPager.Styles> |
|||
<Style Selector="PipsPager /template/ ListBox ListBoxItem"> |
|||
<Setter Property="Width" Value="24" /> |
|||
<Setter Property="Height" Value="24" /> |
|||
<Setter Property="Padding" Value="0" /> |
|||
<Setter Property="Margin" Value="2,0" /> |
|||
<Setter Property="MinWidth" Value="0" /> |
|||
<Setter Property="MinHeight" Value="0" /> |
|||
<Setter Property="ClipToBounds" Value="False" /> |
|||
<Setter Property="VerticalAlignment" Value="Center" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid Background="Transparent"> |
|||
<Border Name="Pip" |
|||
Width="6" Height="6" CornerRadius="3" |
|||
HorizontalAlignment="Center" VerticalAlignment="Center" |
|||
Background="#7FFFFFFF"> |
|||
<Border.Transitions> |
|||
<Transitions> |
|||
<DoubleTransition Property="Width" Duration="0:0:0.2" Easing="CubicEaseOut" /> |
|||
<CornerRadiusTransition Property="CornerRadius" Duration="0:0:0.2" Easing="CubicEaseOut" /> |
|||
<BrushTransition Property="Background" Duration="0:0:0.2" /> |
|||
</Transitions> |
|||
</Border.Transitions> |
|||
</Border> |
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="PipsPager /template/ ListBox ListBoxItem:pointerover /template/ Border#Pip"> |
|||
<Setter Property="Width" Value="8" /> |
|||
<Setter Property="Height" Value="8" /> |
|||
<Setter Property="CornerRadius" Value="4" /> |
|||
<Setter Property="Background" Value="#BFFFFFFF" /> |
|||
</Style> |
|||
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected /template/ Border#Pip"> |
|||
<Setter Property="Width" Value="22" /> |
|||
<Setter Property="Height" Value="6" /> |
|||
<Setter Property="CornerRadius" Value="3" /> |
|||
<Setter Property="Background" Value="#FFFFFFFF" /> |
|||
</Style> |
|||
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected:pointerover /template/ Border#Pip"> |
|||
<Setter Property="Width" Value="22" /> |
|||
<Setter Property="Height" Value="6" /> |
|||
<Setter Property="CornerRadius" Value="3" /> |
|||
<Setter Property="Background" Value="#E8FFFFFF" /> |
|||
</Style> |
|||
</PipsPager.Styles> |
|||
</PipsPager> |
|||
|
|||
<!-- Top bar overlaid on hero --> |
|||
<Grid ColumnDefinitions="Auto,*,Auto" |
|||
VerticalAlignment="Top" |
|||
Margin="4,8,4,0"> |
|||
<Button Grid.Column="0" |
|||
Background="Transparent" |
|||
BorderThickness="0" |
|||
Padding="12,8" |
|||
Click="OnHamburgerClick"> |
|||
<Path Data="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" |
|||
Fill="White" Width="22" Height="22" Stretch="Uniform" /> |
|||
</Button> |
|||
<TextBlock Grid.Column="1" |
|||
Text="Curated" |
|||
FontSize="18" |
|||
FontWeight="Bold" |
|||
Foreground="White" |
|||
VerticalAlignment="Center" |
|||
HorizontalAlignment="Center" |
|||
LetterSpacing="-0.3" /> |
|||
<Button Grid.Column="2" |
|||
Background="Transparent" |
|||
BorderThickness="0" |
|||
Padding="12,8"> |
|||
<Path Data="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" |
|||
Fill="White" Width="22" Height="22" Stretch="Uniform" /> |
|||
</Button> |
|||
</Grid> |
|||
|
|||
</Grid> |
|||
|
|||
<!-- Row 1: Scrollable body --> |
|||
<ScrollViewer Grid.Row="1" |
|||
VerticalScrollBarVisibility="Auto" |
|||
HorizontalScrollBarVisibility="Disabled"> |
|||
<StackPanel> |
|||
|
|||
<!-- Collection Highlights --> |
|||
<StackPanel Margin="0,28,0,0"> |
|||
<Grid ColumnDefinitions="*,Auto" Margin="20,0,20,16"> |
|||
<TextBlock Text="Collection Highlights" |
|||
FontSize="18" FontWeight="Bold" |
|||
Foreground="#191C1E" LetterSpacing="-0.3" /> |
|||
<TextBlock Grid.Column="1" |
|||
Text="SEE ALL" |
|||
FontSize="12" FontWeight="Bold" |
|||
Foreground="#3525CD" LetterSpacing="0.8" |
|||
VerticalAlignment="Center" /> |
|||
</Grid> |
|||
|
|||
<ScrollViewer HorizontalScrollBarVisibility="Hidden" |
|||
VerticalScrollBarVisibility="Disabled" |
|||
Margin="20,0,0,0"> |
|||
<StackPanel Orientation="Horizontal" Spacing="10"> |
|||
|
|||
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/ModernApp/gallery_paris.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10"> |
|||
<StackPanel Spacing="2"> |
|||
<TextBlock Text="SCULPTURE" FontSize="10" FontWeight="Bold" |
|||
Foreground="#C3C0FF" LetterSpacing="1" /> |
|||
<TextBlock Text="Fragmented Grace" FontSize="13" |
|||
FontWeight="SemiBold" Foreground="White" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/ModernApp/gallery_bay.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10"> |
|||
<StackPanel Spacing="2"> |
|||
<TextBlock Text="OIL PAINTING" FontSize="10" FontWeight="Bold" |
|||
Foreground="#C3C0FF" LetterSpacing="1" /> |
|||
<TextBlock Text="Ephemeral Blue" FontSize="13" |
|||
FontWeight="SemiBold" Foreground="White" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/ModernApp/gallery_tropical.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10"> |
|||
<StackPanel Spacing="2"> |
|||
<TextBlock Text="TEXTILE" FontSize="10" FontWeight="Bold" |
|||
Foreground="#C3C0FF" LetterSpacing="1" /> |
|||
<TextBlock Text="Interwoven Lines" FontSize="13" |
|||
FontWeight="SemiBold" Foreground="White" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/ModernApp/gallery_alpine.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10"> |
|||
<StackPanel Spacing="2"> |
|||
<TextBlock Text="PHOTOGRAPHY" FontSize="10" FontWeight="Bold" |
|||
Foreground="#C3C0FF" LetterSpacing="1" /> |
|||
<TextBlock Text="Silent Mountains" FontSize="13" |
|||
FontWeight="SemiBold" Foreground="White" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<!-- Padding card to reveal peek of last item --> |
|||
<Border Width="20" Height="210" /> |
|||
|
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
</StackPanel> |
|||
|
|||
<!-- Curators' Choice --> |
|||
<StackPanel Margin="20,32,20,0" Spacing="12"> |
|||
<TextBlock Text="Curators' Choice" |
|||
FontSize="20" FontWeight="Bold" |
|||
Foreground="#191C1E" HorizontalAlignment="Center" |
|||
LetterSpacing="-0.3" /> |
|||
<TextBlock Text="Hand-picked selections from our global network of artists." |
|||
FontSize="13" Foreground="#777587" |
|||
HorizontalAlignment="Center" TextAlignment="Center" |
|||
TextWrapping="Wrap" Margin="0,0,0,8" /> |
|||
|
|||
<!-- Two-column layout: large card left, two stacked badge cards right --> |
|||
<Grid ColumnDefinitions="*,130"> |
|||
|
|||
<!-- Left: main feature card --> |
|||
<Border Grid.Column="0" Background="White" CornerRadius="16" |
|||
Padding="20" Margin="0,0,10,0" |
|||
BoxShadow="0 2 16 0 #12191C1E"> |
|||
<StackPanel Spacing="10"> |
|||
<Path Data="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5z" |
|||
Fill="#3525CD" Width="22" Height="22" Stretch="Uniform" |
|||
HorizontalAlignment="Left" /> |
|||
<TextBlock Text="The Digital Horizon" |
|||
FontSize="17" FontWeight="Bold" |
|||
Foreground="#191C1E" TextWrapping="Wrap" /> |
|||
<TextBlock Text="Exploring Web3 and Generative Art" |
|||
FontSize="13" Foreground="#777587" TextWrapping="Wrap" /> |
|||
<Button Content="EXPLORE" |
|||
Margin="0,10,0,0" Padding="20,11" |
|||
FontSize="11" FontWeight="Bold" LetterSpacing="0.8" |
|||
CornerRadius="22" Foreground="White" HorizontalAlignment="Left"> |
|||
<Button.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> |
|||
<GradientStop Color="#3525CD" Offset="0" /> |
|||
<GradientStop Color="#4F46E5" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Button.Background> |
|||
</Button> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<!-- Right: two stacked badge cards --> |
|||
<StackPanel Grid.Column="1" Spacing="10"> |
|||
|
|||
<Border Background="White" CornerRadius="16" |
|||
BoxShadow="0 2 16 0 #12191C1E" |
|||
Padding="12"> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" |
|||
Spacing="8" Margin="0,12"> |
|||
<Path Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z" |
|||
Fill="#B84B00" Width="28" Height="28" Stretch="Uniform" |
|||
HorizontalAlignment="Center" /> |
|||
<TextBlock Text="TRENDING" |
|||
FontSize="10" FontWeight="Bold" |
|||
Foreground="#B84B00" LetterSpacing="1" |
|||
HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Background="#EDEEF0" CornerRadius="16" Padding="12"> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" |
|||
Spacing="8" Margin="0,12"> |
|||
<Path Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1.41 14.06L6.17 11.64l1.42-1.41 2.99 3 6.01-6.01 1.42 1.41-7.42 7.43z" |
|||
Fill="#464555" Width="28" Height="28" Stretch="Uniform" |
|||
HorizontalAlignment="Center" /> |
|||
<TextBlock Text="VERIFIED" |
|||
FontSize="10" FontWeight="Bold" |
|||
Foreground="#464555" LetterSpacing="1" |
|||
HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
</StackPanel> |
|||
</Grid> |
|||
</StackPanel> |
|||
|
|||
<!-- Join the Circle --> |
|||
<Border Margin="20,32,20,32" CornerRadius="20" Padding="24" ClipToBounds="True"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> |
|||
<GradientStop Color="#3525CD" Offset="0" /> |
|||
<GradientStop Color="#4F46E5" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel Spacing="10"> |
|||
<TextBlock Text="Join the Circle" |
|||
FontSize="20" FontWeight="Bold" |
|||
Foreground="White" LetterSpacing="-0.3" /> |
|||
<TextBlock Text="Receive exclusive invitations to private viewings and new drop alerts." |
|||
FontSize="13" Foreground="#C3C0FF" |
|||
TextWrapping="Wrap" Opacity="0.9" LineHeight="20" /> |
|||
<TextBox PlaceholderText="Your email address" |
|||
Margin="0,6,0,0" |
|||
CornerRadius="12" |
|||
BorderThickness="1" |
|||
Padding="14,12" |
|||
Foreground="White" |
|||
PlaceholderForeground="#9896D8"> |
|||
<TextBox.Resources> |
|||
<SolidColorBrush x:Key="TextControlBackground" Color="#3C38B5" /> |
|||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#4440BE" /> |
|||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#3C38B5" /> |
|||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#5552C8" /> |
|||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#7370D8" /> |
|||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#8B88E8" /> |
|||
</TextBox.Resources> |
|||
</TextBox> |
|||
<Button Content="SUBSCRIBE" |
|||
Margin="0,2,0,0" Padding="24,12" |
|||
FontSize="12" FontWeight="Bold" LetterSpacing="1" |
|||
CornerRadius="24" Foreground="#3525CD" Background="White" |
|||
HorizontalAlignment="Left" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
</Grid> |
|||
</DrawerPage.Content> |
|||
</DrawerPage> |
|||
|
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,101 @@ |
|||
using System; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselGalleryAppPage : UserControl |
|||
{ |
|||
private bool _syncing; |
|||
private Point _dragStart; |
|||
private bool _isDragging; |
|||
private const double SwipeThreshold = 50; |
|||
|
|||
private ScrollViewer? _infoPanel; |
|||
|
|||
public CarouselGalleryAppPage() |
|||
{ |
|||
InitializeComponent(); |
|||
_infoPanel = this.FindControl<ScrollViewer>("InfoPanel"); |
|||
HeroCarousel.SelectionChanged += OnHeroSelectionChanged; |
|||
HeroPager.SelectedIndexChanged += OnPagerIndexChanged; |
|||
} |
|||
|
|||
protected override void OnLoaded(RoutedEventArgs e) |
|||
{ |
|||
base.OnLoaded(e); |
|||
UpdateInfoPanelVisibility(); |
|||
} |
|||
|
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
base.OnPropertyChanged(change); |
|||
if (change.Property == BoundsProperty) |
|||
UpdateInfoPanelVisibility(); |
|||
} |
|||
|
|||
private void UpdateInfoPanelVisibility() |
|||
{ |
|||
if (_infoPanel != null) |
|||
_infoPanel.IsVisible = Bounds.Width >= 640; |
|||
} |
|||
|
|||
private void OnHamburgerClick(object? sender, RoutedEventArgs e) |
|||
{ |
|||
RootDrawer.IsOpen = !RootDrawer.IsOpen; |
|||
} |
|||
|
|||
private void OnHeroSelectionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
if (_syncing) |
|||
return; |
|||
_syncing = true; |
|||
HeroPager.SelectedPageIndex = HeroCarousel.SelectedIndex; |
|||
_syncing = false; |
|||
} |
|||
|
|||
private void OnPagerIndexChanged(object? sender, PipsPagerSelectedIndexChangedEventArgs e) |
|||
{ |
|||
if (_syncing) |
|||
return; |
|||
_syncing = true; |
|||
HeroCarousel.SelectedIndex = e.NewIndex; |
|||
_syncing = false; |
|||
} |
|||
|
|||
private void OnDrawerMenuSelectionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
RootDrawer.IsOpen = false; |
|||
DrawerMenu.SelectedItem = null; |
|||
} |
|||
|
|||
private void OnHeroPointerPressed(object? sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (!e.GetCurrentPoint(null).Properties.IsLeftButtonPressed) |
|||
return; |
|||
_dragStart = e.GetPosition((Visual?)sender); |
|||
_isDragging = true; |
|||
} |
|||
|
|||
private void OnHeroPointerReleased(object? sender, PointerReleasedEventArgs e) |
|||
{ |
|||
if (!_isDragging) |
|||
return; |
|||
_isDragging = false; |
|||
var delta = e.GetPosition((Visual?)sender).X - _dragStart.X; |
|||
if (Math.Abs(delta) < SwipeThreshold) |
|||
return; |
|||
if (delta < 0) |
|||
HeroCarousel.Next(); |
|||
else |
|||
HeroCarousel.Previous(); |
|||
} |
|||
|
|||
private void OnHeroPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e) |
|||
{ |
|||
_isDragging = false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselGesturesPage"> |
|||
<DockPanel> |
|||
<ScrollViewer DockPanel.Dock="Right" Width="260"> |
|||
<StackPanel Margin="12" Spacing="8"> |
|||
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="13" /> |
|||
|
|||
<CheckBox x:Name="SwipeCheck" |
|||
Content="Swipe Gesture" |
|||
IsChecked="True" |
|||
IsCheckedChanged="OnSwipeEnabledChanged" /> |
|||
|
|||
<CheckBox x:Name="WrapCheck" |
|||
Content="Wrap Selection" |
|||
IsChecked="False" |
|||
IsCheckedChanged="OnWrapSelectionChanged" /> |
|||
|
|||
<CheckBox x:Name="KeyboardCheck" |
|||
Content="Keyboard Navigation" |
|||
IsChecked="True" |
|||
IsCheckedChanged="OnKeyboardEnabledChanged" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" /> |
|||
<TextBlock x:Name="StatusText" |
|||
Text="Item: 1 / 3" |
|||
Opacity="0.7" |
|||
TextWrapping="Wrap" /> |
|||
<TextBlock x:Name="LastActionText" |
|||
Text="Action: —" |
|||
Opacity="0.7" |
|||
TextWrapping="Wrap" /> |
|||
<TextBlock Text="Swipe left/right or use arrow keys to navigate." |
|||
FontSize="11" |
|||
Opacity="0.5" |
|||
TextWrapping="Wrap" /> |
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border DockPanel.Dock="Right" Width="1" |
|||
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" /> |
|||
|
|||
<Border Margin="12" |
|||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" |
|||
CornerRadius="6" |
|||
ClipToBounds="True"> |
|||
<Carousel x:Name="DemoCarousel" |
|||
Focusable="True" |
|||
IsSwipeEnabled="True" |
|||
Height="300"> |
|||
<Carousel.PageTransition> |
|||
<PageSlide Duration="0.25" Orientation="Horizontal" /> |
|||
</Carousel.PageTransition> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 1: Delicate Arch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 2: Hirsch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 3: Maple Leaf" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
</Carousel> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,59 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselGesturesPage : UserControl |
|||
{ |
|||
private bool _keyboardEnabled = true; |
|||
|
|||
public CarouselGesturesPage() |
|||
{ |
|||
InitializeComponent(); |
|||
DemoCarousel.AddHandler(InputElement.KeyDownEvent, OnKeyDown, handledEventsToo: true); |
|||
DemoCarousel.SelectionChanged += OnSelectionChanged; |
|||
DemoCarousel.Loaded += (_, _) => DemoCarousel.Focus(); |
|||
} |
|||
|
|||
private void OnKeyDown(object? sender, KeyEventArgs e) |
|||
{ |
|||
if (!_keyboardEnabled) |
|||
return; |
|||
|
|||
switch (e.Key) |
|||
{ |
|||
case Key.Left: |
|||
case Key.Up: |
|||
LastActionText.Text = $"Action: Key {e.Key} (Previous)"; |
|||
break; |
|||
case Key.Right: |
|||
case Key.Down: |
|||
LastActionText.Text = $"Action: Key {e.Key} (Next)"; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {DemoCarousel.ItemCount}"; |
|||
if (DemoCarousel.IsSwiping) |
|||
LastActionText.Text = "Action: Swipe"; |
|||
} |
|||
|
|||
private void OnSwipeEnabledChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DemoCarousel.IsSwipeEnabled = SwipeCheck.IsChecked == true; |
|||
} |
|||
|
|||
private void OnWrapSelectionChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DemoCarousel.WrapSelection = WrapCheck.IsChecked == true; |
|||
} |
|||
|
|||
private void OnKeyboardEnabledChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
_keyboardEnabled = KeyboardCheck.IsChecked == true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselGettingStartedPage"> |
|||
<DockPanel> |
|||
<ScrollViewer DockPanel.Dock="Right" Width="260"> |
|||
<StackPanel Margin="12" Spacing="8"> |
|||
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" /> |
|||
|
|||
<Button x:Name="PreviousButton" |
|||
Content="Previous" |
|||
HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="NextButton" |
|||
Content="Next" |
|||
HorizontalAlignment="Stretch" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" /> |
|||
<TextBlock x:Name="StatusText" |
|||
Text="Item: 1 / 3" |
|||
Opacity="0.7" |
|||
TextWrapping="Wrap" /> |
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border DockPanel.Dock="Right" Width="1" |
|||
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" /> |
|||
|
|||
<Border Margin="12" |
|||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" |
|||
CornerRadius="6" |
|||
ClipToBounds="True"> |
|||
<Carousel x:Name="DemoCarousel" Height="300" IsSwipeEnabled="True"> |
|||
<Carousel.PageTransition> |
|||
<PageSlide Duration="0.25" Orientation="Horizontal" /> |
|||
</Carousel.PageTransition> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 1: Delicate Arch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 2: Hirsch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 3: Maple Leaf" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
</Carousel> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,40 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselGettingStartedPage : UserControl |
|||
{ |
|||
public CarouselGettingStartedPage() |
|||
{ |
|||
InitializeComponent(); |
|||
PreviousButton.Click += OnPrevious; |
|||
NextButton.Click += OnNext; |
|||
DemoCarousel.SelectionChanged += OnSelectionChanged; |
|||
} |
|||
|
|||
private void OnPrevious(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DemoCarousel.Previous(); |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void OnNext(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DemoCarousel.Next(); |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
UpdateStatus(); |
|||
} |
|||
|
|||
private void UpdateStatus() |
|||
{ |
|||
var index = DemoCarousel.SelectedIndex + 1; |
|||
var count = DemoCarousel.ItemCount; |
|||
StatusText.Text = $"Item: {index} / {count}"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselMultiItemPage"> |
|||
<DockPanel> |
|||
<ScrollViewer DockPanel.Dock="Right" Width="260"> |
|||
<StackPanel Margin="12" Spacing="8"> |
|||
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" /> |
|||
<Button x:Name="PreviousButton" Content="Previous" HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="NextButton" Content="Next" HorizontalAlignment="Stretch" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Viewport Fraction" FontWeight="SemiBold" FontSize="13" /> |
|||
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.6" |
|||
Text="Values below 1.0 show adjacent items peeking into the viewport." /> |
|||
<Grid ColumnDefinitions="*,48" ColumnSpacing="8"> |
|||
<Slider x:Name="ViewportSlider" |
|||
Minimum="0.2" Maximum="1.0" Value="0.5" |
|||
TickFrequency="0.01" IsSnapToTickEnabled="True" |
|||
HorizontalAlignment="Stretch" |
|||
ValueChanged="OnViewportFractionChanged" /> |
|||
<TextBlock x:Name="ViewportLabel" Grid.Column="1" |
|||
Text="0.50" VerticalAlignment="Center" |
|||
HorizontalAlignment="Right" FontWeight="SemiBold" /> |
|||
</Grid> |
|||
<TextBlock x:Name="ViewportHint" |
|||
Text="~2 items visible." |
|||
FontSize="11" Opacity="0.6" TextWrapping="Wrap" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="13" /> |
|||
<CheckBox x:Name="WrapCheck" Content="Wrap Selection" IsChecked="False" |
|||
IsCheckedChanged="OnWrapChanged" /> |
|||
<CheckBox x:Name="SwipeCheck" Content="Swipe / Drag" IsChecked="True" |
|||
IsCheckedChanged="OnSwipeChanged" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" /> |
|||
<TextBlock x:Name="StatusText" Text="Item: 1 / 5" |
|||
Opacity="0.7" TextWrapping="Wrap" /> |
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border DockPanel.Dock="Right" Width="1" |
|||
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" /> |
|||
|
|||
<Border Margin="12" |
|||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" CornerRadius="6" ClipToBounds="True"> |
|||
<Carousel x:Name="DemoCarousel" |
|||
Height="280" |
|||
ViewportFraction="0.5" |
|||
IsSwipeEnabled="True"> |
|||
<Carousel.PageTransition> |
|||
<PageSlide Duration="0.3" Orientation="Horizontal" /> |
|||
</Carousel.PageTransition> |
|||
|
|||
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> |
|||
<GradientStop Color="#3525CD" Offset="0" /> |
|||
<GradientStop Color="#6B5CE7" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8"> |
|||
<TextBlock Text="01" FontSize="52" FontWeight="Bold" Foreground="White" |
|||
HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Neon Pulse" FontSize="15" FontWeight="SemiBold" |
|||
Foreground="#C3C0FF" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> |
|||
<GradientStop Color="#0891B2" Offset="0" /> |
|||
<GradientStop Color="#06B6D4" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8"> |
|||
<TextBlock Text="02" FontSize="52" FontWeight="Bold" Foreground="White" |
|||
HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Ephemeral Blue" FontSize="15" FontWeight="SemiBold" |
|||
Foreground="#BAF0FA" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> |
|||
<GradientStop Color="#059669" Offset="0" /> |
|||
<GradientStop Color="#10B981" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8"> |
|||
<TextBlock Text="03" FontSize="52" FontWeight="Bold" Foreground="White" |
|||
HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Forest Forms" FontSize="15" FontWeight="SemiBold" |
|||
Foreground="#A7F3D0" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> |
|||
<GradientStop Color="#D97706" Offset="0" /> |
|||
<GradientStop Color="#F59E0B" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8"> |
|||
<TextBlock Text="04" FontSize="52" FontWeight="Bold" Foreground="White" |
|||
HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Golden Hour" FontSize="15" FontWeight="SemiBold" |
|||
Foreground="#FDE68A" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> |
|||
<GradientStop Color="#BE185D" Offset="0" /> |
|||
<GradientStop Color="#EC4899" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8"> |
|||
<TextBlock Text="05" FontSize="52" FontWeight="Bold" Foreground="White" |
|||
HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Crimson Wave" FontSize="15" FontWeight="SemiBold" |
|||
Foreground="#FBCFE8" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</Carousel> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,47 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselMultiItemPage : UserControl |
|||
{ |
|||
public CarouselMultiItemPage() |
|||
{ |
|||
InitializeComponent(); |
|||
PreviousButton.Click += (_, _) => DemoCarousel.Previous(); |
|||
NextButton.Click += (_, _) => DemoCarousel.Next(); |
|||
DemoCarousel.SelectionChanged += OnSelectionChanged; |
|||
} |
|||
|
|||
private void OnViewportFractionChanged(object? sender, RangeBaseValueChangedEventArgs e) |
|||
{ |
|||
if (DemoCarousel is null) |
|||
return; |
|||
var value = Math.Round(e.NewValue, 2); |
|||
DemoCarousel.ViewportFraction = value; |
|||
ViewportLabel.Text = value.ToString("0.00"); |
|||
ViewportHint.Text = value >= 1d ? "1.00 — single full item." : $"~{1d / value:0.#} items visible."; |
|||
} |
|||
|
|||
private void OnWrapChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
if (DemoCarousel is null) |
|||
return; |
|||
DemoCarousel.WrapSelection = WrapCheck.IsChecked == true; |
|||
} |
|||
|
|||
private void OnSwipeChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
if (DemoCarousel is null) |
|||
return; |
|||
DemoCarousel.IsSwipeEnabled = SwipeCheck.IsChecked == true; |
|||
} |
|||
|
|||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {DemoCarousel.ItemCount}"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselTransitionsPage"> |
|||
<DockPanel> |
|||
<ScrollViewer DockPanel.Dock="Right" Width="260"> |
|||
<StackPanel Margin="12" Spacing="8"> |
|||
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" /> |
|||
|
|||
<Button x:Name="PreviousButton" |
|||
Content="Previous" |
|||
HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="NextButton" |
|||
Content="Next" |
|||
HorizontalAlignment="Stretch" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Transition" FontWeight="SemiBold" FontSize="13" /> |
|||
<ComboBox x:Name="TransitionCombo" |
|||
HorizontalAlignment="Stretch" |
|||
SelectedIndex="1"> |
|||
<ComboBoxItem>None</ComboBoxItem> |
|||
<ComboBoxItem>Page Slide</ComboBoxItem> |
|||
<ComboBoxItem>Cross Fade</ComboBoxItem> |
|||
<ComboBoxItem>Rotate 3D</ComboBoxItem> |
|||
<ComboBoxItem>Card Stack</ComboBoxItem> |
|||
<ComboBoxItem>Wave Reveal</ComboBoxItem> |
|||
<ComboBoxItem>Composite (Slide + Fade)</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="Orientation" FontWeight="SemiBold" FontSize="13" /> |
|||
<ComboBox x:Name="OrientationCombo" |
|||
HorizontalAlignment="Stretch" |
|||
SelectedIndex="0"> |
|||
<ComboBoxItem>Horizontal</ComboBoxItem> |
|||
<ComboBoxItem>Vertical</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" /> |
|||
<TextBlock x:Name="StatusText" |
|||
Text="Transition: Page Slide" |
|||
Opacity="0.7" |
|||
TextWrapping="Wrap" /> |
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border DockPanel.Dock="Right" Width="1" |
|||
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" /> |
|||
|
|||
<Border Margin="12" |
|||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" |
|||
CornerRadius="6" |
|||
ClipToBounds="True"> |
|||
<Carousel x:Name="DemoCarousel" Height="300"> |
|||
<Carousel.PageTransition> |
|||
<PageSlide Duration="0.25" Orientation="Horizontal" /> |
|||
</Carousel.PageTransition> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 1: Delicate Arch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 2: Hirsch" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True"> |
|||
<Grid> |
|||
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" /> |
|||
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12"> |
|||
<TextBlock Text="Item 3: Maple Leaf" Foreground="White" |
|||
HorizontalAlignment="Center" FontWeight="SemiBold" /> |
|||
</Border> |
|||
</Grid> |
|||
</Border> |
|||
</Carousel> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,66 @@ |
|||
using System; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls; |
|||
using ControlCatalog.Pages.Transitions; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselTransitionsPage : UserControl |
|||
{ |
|||
public CarouselTransitionsPage() |
|||
{ |
|||
InitializeComponent(); |
|||
PreviousButton.Click += (_, _) => DemoCarousel.Previous(); |
|||
NextButton.Click += (_, _) => DemoCarousel.Next(); |
|||
TransitionCombo.SelectionChanged += (_, _) => ApplyTransition(); |
|||
OrientationCombo.SelectionChanged += (_, _) => ApplyTransition(); |
|||
} |
|||
|
|||
private void ApplyTransition() |
|||
{ |
|||
var axis = OrientationCombo.SelectedIndex == 0 ? |
|||
PageSlide.SlideAxis.Horizontal : |
|||
PageSlide.SlideAxis.Vertical; |
|||
var label = axis == PageSlide.SlideAxis.Horizontal ? "Horizontal" : "Vertical"; |
|||
|
|||
switch (TransitionCombo.SelectedIndex) |
|||
{ |
|||
case 0: |
|||
DemoCarousel.PageTransition = null; |
|||
StatusText.Text = "Transition: None"; |
|||
break; |
|||
case 1: |
|||
DemoCarousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), axis); |
|||
StatusText.Text = $"Transition: Page Slide ({label})"; |
|||
break; |
|||
case 2: |
|||
DemoCarousel.PageTransition = new CrossFade(TimeSpan.FromSeconds(0.25)); |
|||
StatusText.Text = "Transition: Cross Fade"; |
|||
break; |
|||
case 3: |
|||
DemoCarousel.PageTransition = new Rotate3DTransition(TimeSpan.FromSeconds(0.5), axis); |
|||
StatusText.Text = $"Transition: Rotate 3D ({label})"; |
|||
break; |
|||
case 4: |
|||
DemoCarousel.PageTransition = new CardStackPageTransition(TimeSpan.FromSeconds(0.5), axis); |
|||
StatusText.Text = $"Transition: Card Stack ({label})"; |
|||
break; |
|||
case 5: |
|||
DemoCarousel.PageTransition = new WaveRevealPageTransition(TimeSpan.FromSeconds(0.8), axis); |
|||
StatusText.Text = $"Transition: Wave Reveal ({label})"; |
|||
break; |
|||
case 6: |
|||
DemoCarousel.PageTransition = new CompositePageTransition |
|||
{ |
|||
PageTransitions = |
|||
{ |
|||
new PageSlide(TimeSpan.FromSeconds(0.25), axis), |
|||
new CrossFade(TimeSpan.FromSeconds(0.25)), |
|||
} |
|||
}; |
|||
StatusText.Text = "Transition: Composite (Slide + Fade)"; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.CarouselVerticalPage"> |
|||
<DockPanel> |
|||
<ScrollViewer DockPanel.Dock="Right" Width="260"> |
|||
<StackPanel Margin="12" Spacing="8"> |
|||
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16" |
|||
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" /> |
|||
|
|||
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" /> |
|||
<Button x:Name="PreviousButton" Content="Up" HorizontalAlignment="Stretch" /> |
|||
<Button x:Name="NextButton" Content="Down" HorizontalAlignment="Stretch" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Transition" FontWeight="SemiBold" FontSize="13" /> |
|||
<ComboBox x:Name="TransitionCombo" |
|||
HorizontalAlignment="Stretch" |
|||
SelectedIndex="0"> |
|||
<ComboBoxItem>PageSlide</ComboBoxItem> |
|||
<ComboBoxItem>CrossFade</ComboBoxItem> |
|||
<ComboBoxItem>None</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="13" /> |
|||
<CheckBox x:Name="WrapCheck" |
|||
Content="Wrap Selection" |
|||
IsChecked="False" |
|||
IsCheckedChanged="OnWrapSelectionChanged" /> |
|||
|
|||
<Separator /> |
|||
|
|||
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" /> |
|||
<TextBlock x:Name="StatusText" |
|||
Text="Item: 1 / 4" |
|||
Opacity="0.7" |
|||
TextWrapping="Wrap" /> |
|||
<TextBlock Text="Use Up/Down arrow keys or buttons to navigate." |
|||
FontSize="11" |
|||
Opacity="0.5" |
|||
TextWrapping="Wrap" /> |
|||
</StackPanel> |
|||
</ScrollViewer> |
|||
|
|||
<Border DockPanel.Dock="Right" Width="1" |
|||
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" /> |
|||
|
|||
<Border Margin="12" |
|||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" |
|||
BorderThickness="1" |
|||
CornerRadius="6" |
|||
ClipToBounds="True"> |
|||
<Carousel x:Name="DemoCarousel" |
|||
Focusable="True" |
|||
IsSwipeEnabled="True"> |
|||
<Carousel.PageTransition> |
|||
<PageSlide Duration="0.3" Orientation="Vertical" /> |
|||
</Carousel.PageTransition> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> |
|||
<GradientStop Color="#1A1A2E" Offset="0" /> |
|||
<GradientStop Color="#3525CD" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10"> |
|||
<TextBlock Text="01" FontSize="64" FontWeight="Bold" |
|||
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Neon Pulse" FontSize="18" FontWeight="SemiBold" |
|||
Foreground="#C3C0FF" HorizontalAlignment="Center" /> |
|||
<TextBlock Text="Slide down to explore" FontSize="12" |
|||
Foreground="#80FFFFFF" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> |
|||
<GradientStop Color="#0C1A1F" Offset="0" /> |
|||
<GradientStop Color="#0891B2" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10"> |
|||
<TextBlock Text="02" FontSize="64" FontWeight="Bold" |
|||
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Ephemeral Blue" FontSize="18" FontWeight="SemiBold" |
|||
Foreground="#BAF0FA" HorizontalAlignment="Center" /> |
|||
<TextBlock Text="Vertical PageSlide in action" FontSize="12" |
|||
Foreground="#80FFFFFF" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> |
|||
<GradientStop Color="#0A1F18" Offset="0" /> |
|||
<GradientStop Color="#059669" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10"> |
|||
<TextBlock Text="03" FontSize="64" FontWeight="Bold" |
|||
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Forest Forms" FontSize="18" FontWeight="SemiBold" |
|||
Foreground="#A7F3D0" HorizontalAlignment="Center" /> |
|||
<TextBlock Text="Swipe up or down on touch screens" FontSize="12" |
|||
Foreground="#80FFFFFF" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
|
|||
<Border Margin="14,12" CornerRadius="12"> |
|||
<Border.Background> |
|||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> |
|||
<GradientStop Color="#1F1208" Offset="0" /> |
|||
<GradientStop Color="#D97706" Offset="1" /> |
|||
</LinearGradientBrush> |
|||
</Border.Background> |
|||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10"> |
|||
<TextBlock Text="04" FontSize="64" FontWeight="Bold" |
|||
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" /> |
|||
<TextBlock Text="Golden Hour" FontSize="18" FontWeight="SemiBold" |
|||
Foreground="#FDE68A" HorizontalAlignment="Center" /> |
|||
<TextBlock Text="Switch transitions in the panel" FontSize="12" |
|||
Foreground="#80FFFFFF" HorizontalAlignment="Center" /> |
|||
</StackPanel> |
|||
</Border> |
|||
</Carousel> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,39 @@ |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public partial class CarouselVerticalPage : UserControl |
|||
{ |
|||
public CarouselVerticalPage() |
|||
{ |
|||
InitializeComponent(); |
|||
PreviousButton.Click += (_, _) => DemoCarousel.Previous(); |
|||
NextButton.Click += (_, _) => DemoCarousel.Next(); |
|||
DemoCarousel.SelectionChanged += OnSelectionChanged; |
|||
TransitionCombo.SelectionChanged += OnTransitionChanged; |
|||
DemoCarousel.Loaded += (_, _) => DemoCarousel.Focus(); |
|||
} |
|||
|
|||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {DemoCarousel.ItemCount}"; |
|||
} |
|||
|
|||
private void OnTransitionChanged(object? sender, SelectionChangedEventArgs e) |
|||
{ |
|||
DemoCarousel.PageTransition = TransitionCombo.SelectedIndex switch |
|||
{ |
|||
1 => new CrossFade(System.TimeSpan.FromSeconds(0.3)), |
|||
2 => null, |
|||
_ => new PageSlide(System.TimeSpan.FromSeconds(0.3), PageSlide.SlideAxis.Vertical), |
|||
}; |
|||
} |
|||
|
|||
private void OnWrapSelectionChanged(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DemoCarousel.WrapSelection = WrapCheck.IsChecked == true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,447 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Animation.Easings; |
|||
using Avalonia.Media; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace ControlCatalog.Pages.Transitions; |
|||
|
|||
/// <summary>
|
|||
/// Transitions between two pages with a card-stack effect:
|
|||
/// the top page moves/rotates away while the next page scales up underneath.
|
|||
/// </summary>
|
|||
public class CardStackPageTransition : PageSlide |
|||
{ |
|||
private const double ViewportLiftScale = 0.03; |
|||
private const double ViewportPromotionScale = 0.02; |
|||
private const double ViewportDepthOpacityFalloff = 0.08; |
|||
private const double SidePeekAngle = 4.0; |
|||
private const double FarPeekAngle = 7.0; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CardStackPageTransition"/> class.
|
|||
/// </summary>
|
|||
public CardStackPageTransition() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CardStackPageTransition"/> class.
|
|||
/// </summary>
|
|||
/// <param name="duration">The duration of the animation.</param>
|
|||
/// <param name="orientation">The axis on which the animation should occur.</param>
|
|||
public CardStackPageTransition(TimeSpan duration, PageSlide.SlideAxis orientation = PageSlide.SlideAxis.Horizontal) |
|||
: base(duration, orientation) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum rotation angle (degrees) applied to the top card.
|
|||
/// </summary>
|
|||
public double MaxSwipeAngle { get; set; } = 15.0; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scale reduction applied to the back card (0.05 = 5%).
|
|||
/// </summary>
|
|||
public double BackCardScale { get; set; } = 0.05; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the vertical offset (pixels) applied to the back card.
|
|||
/// </summary>
|
|||
public double BackCardOffset { get; set; } = 0.0; |
|||
|
|||
/// <inheritdoc />
|
|||
public override async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) |
|||
{ |
|||
if (cancellationToken.IsCancellationRequested) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var tasks = new List<Task>(); |
|||
var parent = GetVisualParent(from, to); |
|||
var distance = Orientation == PageSlide.SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; |
|||
var translateProperty = Orientation == PageSlide.SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty; |
|||
var rotationTarget = Orientation == PageSlide.SlideAxis.Horizontal ? (forward ? -MaxSwipeAngle : MaxSwipeAngle) : 0.0; |
|||
var startScale = 1.0 - BackCardScale; |
|||
|
|||
if (from != null) |
|||
{ |
|||
var (rotate, translate) = EnsureTopTransforms(from); |
|||
rotate.Angle = 0; |
|||
translate.X = 0; |
|||
translate.Y = 0; |
|||
from.Opacity = 1; |
|||
from.ZIndex = 1; |
|||
|
|||
var animation = new Animation |
|||
{ |
|||
Easing = SlideOutEasing, |
|||
Duration = Duration, |
|||
FillMode = FillMode, |
|||
Children = |
|||
{ |
|||
new KeyFrame |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter(translateProperty, 0d), |
|||
new Setter(RotateTransform.AngleProperty, 0d) |
|||
}, |
|||
Cue = new Cue(0d) |
|||
}, |
|||
new KeyFrame |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter(translateProperty, forward ? -distance : distance), |
|||
new Setter(RotateTransform.AngleProperty, rotationTarget) |
|||
}, |
|||
Cue = new Cue(1d) |
|||
} |
|||
} |
|||
}; |
|||
tasks.Add(animation.RunAsync(from, cancellationToken)); |
|||
} |
|||
|
|||
if (to != null) |
|||
{ |
|||
var (scale, translate) = EnsureBackTransforms(to); |
|||
scale.ScaleX = startScale; |
|||
scale.ScaleY = startScale; |
|||
translate.X = 0; |
|||
translate.Y = BackCardOffset; |
|||
to.IsVisible = true; |
|||
to.Opacity = 1; |
|||
to.ZIndex = 0; |
|||
|
|||
var animation = new Animation |
|||
{ |
|||
Easing = SlideInEasing, |
|||
Duration = Duration, |
|||
FillMode = FillMode, |
|||
Children = |
|||
{ |
|||
new KeyFrame |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter(ScaleTransform.ScaleXProperty, startScale), |
|||
new Setter(ScaleTransform.ScaleYProperty, startScale), |
|||
new Setter(TranslateTransform.YProperty, BackCardOffset) |
|||
}, |
|||
Cue = new Cue(0d) |
|||
}, |
|||
new KeyFrame |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter(ScaleTransform.ScaleXProperty, 1d), |
|||
new Setter(ScaleTransform.ScaleYProperty, 1d), |
|||
new Setter(TranslateTransform.YProperty, 0d) |
|||
}, |
|||
Cue = new Cue(1d) |
|||
} |
|||
} |
|||
}; |
|||
|
|||
tasks.Add(animation.RunAsync(to, cancellationToken)); |
|||
} |
|||
|
|||
await Task.WhenAll(tasks); |
|||
|
|||
if (from != null && !cancellationToken.IsCancellationRequested) |
|||
{ |
|||
from.IsVisible = false; |
|||
} |
|||
|
|||
if (!cancellationToken.IsCancellationRequested && to != null) |
|||
{ |
|||
var (scale, translate) = EnsureBackTransforms(to); |
|||
scale.ScaleX = 1; |
|||
scale.ScaleY = 1; |
|||
translate.X = 0; |
|||
translate.Y = 0; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Update( |
|||
double progress, |
|||
Visual? from, |
|||
Visual? to, |
|||
bool forward, |
|||
double pageLength, |
|||
IReadOnlyList<PageTransitionItem> visibleItems) |
|||
{ |
|||
if (visibleItems.Count > 0) |
|||
{ |
|||
UpdateVisibleItems(progress, from, to, forward, pageLength, visibleItems); |
|||
return; |
|||
} |
|||
|
|||
if (from is null && to is null) |
|||
return; |
|||
|
|||
var parent = GetVisualParent(from, to); |
|||
var size = parent.Bounds.Size; |
|||
var isHorizontal = Orientation == PageSlide.SlideAxis.Horizontal; |
|||
var distance = pageLength > 0 |
|||
? pageLength |
|||
: (isHorizontal ? size.Width : size.Height); |
|||
var rotationTarget = isHorizontal ? (forward ? -MaxSwipeAngle : MaxSwipeAngle) : 0.0; |
|||
var startScale = 1.0 - BackCardScale; |
|||
|
|||
if (from != null) |
|||
{ |
|||
var (rotate, translate) = EnsureTopTransforms(from); |
|||
if (isHorizontal) |
|||
{ |
|||
translate.X = forward ? -distance * progress : distance * progress; |
|||
translate.Y = 0; |
|||
} |
|||
else |
|||
{ |
|||
translate.X = 0; |
|||
translate.Y = forward ? -distance * progress : distance * progress; |
|||
} |
|||
|
|||
rotate.Angle = rotationTarget * progress; |
|||
from.IsVisible = true; |
|||
from.Opacity = 1; |
|||
from.ZIndex = 1; |
|||
} |
|||
|
|||
if (to != null) |
|||
{ |
|||
var (scale, translate) = EnsureBackTransforms(to); |
|||
var currentScale = startScale + (1.0 - startScale) * progress; |
|||
var currentOffset = BackCardOffset * (1.0 - progress); |
|||
|
|||
scale.ScaleX = currentScale; |
|||
scale.ScaleY = currentScale; |
|||
if (isHorizontal) |
|||
{ |
|||
translate.X = 0; |
|||
translate.Y = currentOffset; |
|||
} |
|||
else |
|||
{ |
|||
translate.X = currentOffset; |
|||
translate.Y = 0; |
|||
} |
|||
|
|||
to.IsVisible = true; |
|||
to.Opacity = 1; |
|||
to.ZIndex = 0; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Reset(Visual visual) |
|||
{ |
|||
visual.RenderTransform = null; |
|||
visual.RenderTransformOrigin = default; |
|||
visual.Opacity = 1; |
|||
visual.ZIndex = 0; |
|||
} |
|||
|
|||
private void UpdateVisibleItems( |
|||
double progress, |
|||
Visual? from, |
|||
Visual? to, |
|||
bool forward, |
|||
double pageLength, |
|||
IReadOnlyList<PageTransitionItem> visibleItems) |
|||
{ |
|||
var isHorizontal = Orientation == PageSlide.SlideAxis.Horizontal; |
|||
var rotationTarget = isHorizontal |
|||
? (forward ? -MaxSwipeAngle : MaxSwipeAngle) |
|||
: 0.0; |
|||
var stackOffset = GetViewportStackOffset(pageLength); |
|||
var lift = Math.Sin(Math.Clamp(progress, 0.0, 1.0) * Math.PI); |
|||
|
|||
foreach (var item in visibleItems) |
|||
{ |
|||
var visual = item.Visual; |
|||
var (rotate, scale, translate) = EnsureViewportTransforms(visual); |
|||
var depth = GetViewportDepth(item.ViewportCenterOffset); |
|||
var scaleValue = Math.Max(0.84, 1.0 - (BackCardScale * depth)); |
|||
var stackValue = stackOffset * depth; |
|||
var baseOpacity = Math.Max(0.8, 1.0 - (ViewportDepthOpacityFalloff * depth)); |
|||
var restingAngle = isHorizontal ? GetViewportRestingAngle(item.ViewportCenterOffset) : 0.0; |
|||
|
|||
rotate.Angle = restingAngle; |
|||
scale.ScaleX = scaleValue; |
|||
scale.ScaleY = scaleValue; |
|||
translate.X = 0; |
|||
translate.Y = 0; |
|||
|
|||
if (ReferenceEquals(visual, from)) |
|||
{ |
|||
rotate.Angle = restingAngle + (rotationTarget * progress); |
|||
stackValue -= stackOffset * 0.2 * lift; |
|||
baseOpacity = Math.Min(1.0, baseOpacity + 0.08); |
|||
} |
|||
|
|||
if (ReferenceEquals(visual, to)) |
|||
{ |
|||
var promotedScale = Math.Min(1.0, scaleValue + (ViewportLiftScale * lift) + (ViewportPromotionScale * progress)); |
|||
scale.ScaleX = promotedScale; |
|||
scale.ScaleY = promotedScale; |
|||
rotate.Angle = restingAngle * (1.0 - progress); |
|||
stackValue = Math.Max(0.0, stackValue - (stackOffset * (0.45 + (0.2 * lift)) * progress)); |
|||
baseOpacity = Math.Min(1.0, baseOpacity + (0.12 * lift)); |
|||
} |
|||
|
|||
if (isHorizontal) |
|||
translate.Y = stackValue; |
|||
else |
|||
translate.X = stackValue; |
|||
|
|||
visual.IsVisible = true; |
|||
visual.Opacity = baseOpacity; |
|||
visual.ZIndex = GetViewportZIndex(item.ViewportCenterOffset, visual, from, to); |
|||
} |
|||
} |
|||
|
|||
private static (RotateTransform rotate, TranslateTransform translate) EnsureTopTransforms(Visual visual) |
|||
{ |
|||
if (visual.RenderTransform is TransformGroup group && |
|||
group.Children.Count == 2 && |
|||
group.Children[0] is RotateTransform rotateTransform && |
|||
group.Children[1] is TranslateTransform translateTransform) |
|||
{ |
|||
visual.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative); |
|||
return (rotateTransform, translateTransform); |
|||
} |
|||
|
|||
var rotate = new RotateTransform(); |
|||
var translate = new TranslateTransform(); |
|||
visual.RenderTransform = new TransformGroup |
|||
{ |
|||
Children = |
|||
{ |
|||
rotate, |
|||
translate |
|||
} |
|||
}; |
|||
visual.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative); |
|||
return (rotate, translate); |
|||
} |
|||
|
|||
private static (ScaleTransform scale, TranslateTransform translate) EnsureBackTransforms(Visual visual) |
|||
{ |
|||
if (visual.RenderTransform is TransformGroup group && |
|||
group.Children.Count == 2 && |
|||
group.Children[0] is ScaleTransform scaleTransform && |
|||
group.Children[1] is TranslateTransform translateTransform) |
|||
{ |
|||
visual.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative); |
|||
return (scaleTransform, translateTransform); |
|||
} |
|||
|
|||
var scale = new ScaleTransform(); |
|||
var translate = new TranslateTransform(); |
|||
visual.RenderTransform = new TransformGroup |
|||
{ |
|||
Children = |
|||
{ |
|||
scale, |
|||
translate |
|||
} |
|||
}; |
|||
visual.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative); |
|||
return (scale, translate); |
|||
} |
|||
|
|||
private static (RotateTransform rotate, ScaleTransform scale, TranslateTransform translate) EnsureViewportTransforms(Visual visual) |
|||
{ |
|||
if (visual.RenderTransform is TransformGroup group && |
|||
group.Children.Count == 3 && |
|||
group.Children[0] is RotateTransform rotateTransform && |
|||
group.Children[1] is ScaleTransform scaleTransform && |
|||
group.Children[2] is TranslateTransform translateTransform) |
|||
{ |
|||
visual.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative); |
|||
return (rotateTransform, scaleTransform, translateTransform); |
|||
} |
|||
|
|||
var rotate = new RotateTransform(); |
|||
var scale = new ScaleTransform(1, 1); |
|||
var translate = new TranslateTransform(); |
|||
visual.RenderTransform = new TransformGroup |
|||
{ |
|||
Children = |
|||
{ |
|||
rotate, |
|||
scale, |
|||
translate |
|||
} |
|||
}; |
|||
visual.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative); |
|||
return (rotate, scale, translate); |
|||
} |
|||
|
|||
private double GetViewportStackOffset(double pageLength) |
|||
{ |
|||
if (BackCardOffset > 0) |
|||
return BackCardOffset; |
|||
|
|||
return Math.Clamp(pageLength * 0.045, 10.0, 18.0); |
|||
} |
|||
|
|||
private static double GetViewportDepth(double offsetFromCenter) |
|||
{ |
|||
var distance = Math.Abs(offsetFromCenter); |
|||
|
|||
if (distance <= 1.0) |
|||
return distance; |
|||
|
|||
if (distance <= 2.0) |
|||
return 1.0 + ((distance - 1.0) * 0.8); |
|||
|
|||
return 1.8; |
|||
} |
|||
|
|||
private static double GetViewportRestingAngle(double offsetFromCenter) |
|||
{ |
|||
var sign = Math.Sign(offsetFromCenter); |
|||
if (sign == 0) |
|||
return 0; |
|||
|
|||
var distance = Math.Abs(offsetFromCenter); |
|||
if (distance <= 1.0) |
|||
return sign * Lerp(0.0, SidePeekAngle, distance); |
|||
|
|||
if (distance <= 2.0) |
|||
return sign * Lerp(SidePeekAngle, FarPeekAngle, distance - 1.0); |
|||
|
|||
return sign * FarPeekAngle; |
|||
} |
|||
|
|||
private static double Lerp(double from, double to, double t) |
|||
{ |
|||
return from + ((to - from) * Math.Clamp(t, 0.0, 1.0)); |
|||
} |
|||
|
|||
private static int GetViewportZIndex(double offsetFromCenter, Visual visual, Visual? from, Visual? to) |
|||
{ |
|||
if (ReferenceEquals(visual, from)) |
|||
return 5; |
|||
|
|||
if (ReferenceEquals(visual, to)) |
|||
return 4; |
|||
|
|||
var distance = Math.Abs(offsetFromCenter); |
|||
if (distance < 0.5) |
|||
return 4; |
|||
if (distance < 1.5) |
|||
return 3; |
|||
return 2; |
|||
} |
|||
} |
|||
@ -0,0 +1,380 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Animation.Easings; |
|||
using Avalonia.Media; |
|||
|
|||
namespace ControlCatalog.Pages.Transitions; |
|||
|
|||
/// <summary>
|
|||
/// Transitions between two pages using a wave clip that reveals the next page.
|
|||
/// </summary>
|
|||
public class WaveRevealPageTransition : PageSlide |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WaveRevealPageTransition"/> class.
|
|||
/// </summary>
|
|||
public WaveRevealPageTransition() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WaveRevealPageTransition"/> class.
|
|||
/// </summary>
|
|||
/// <param name="duration">The duration of the animation.</param>
|
|||
/// <param name="orientation">The axis on which the animation should occur.</param>
|
|||
public WaveRevealPageTransition(TimeSpan duration, PageSlide.SlideAxis orientation = PageSlide.SlideAxis.Horizontal) |
|||
: base(duration, orientation) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum wave bulge (pixels) along the movement axis.
|
|||
/// </summary>
|
|||
public double MaxBulge { get; set; } = 120.0; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bulge factor along the movement axis (0-1).
|
|||
/// </summary>
|
|||
public double BulgeFactor { get; set; } = 0.35; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bulge factor along the cross axis (0-1).
|
|||
/// </summary>
|
|||
public double CrossBulgeFactor { get; set; } = 0.3; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a cross-axis offset (pixels) to shift the wave center.
|
|||
/// </summary>
|
|||
public double WaveCenterOffset { get; set; } = 0.0; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets how strongly the wave center follows the provided offset.
|
|||
/// </summary>
|
|||
public double CenterSensitivity { get; set; } = 1.0; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bulge exponent used to shape the wave (1.0 = linear).
|
|||
/// Higher values tighten the bulge; lower values broaden it.
|
|||
/// </summary>
|
|||
public double BulgeExponent { get; set; } = 1.0; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the easing applied to the wave progress (clip only).
|
|||
/// </summary>
|
|||
public Easing WaveEasing { get; set; } = new CubicEaseOut(); |
|||
|
|||
/// <inheritdoc />
|
|||
public override async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) |
|||
{ |
|||
if (cancellationToken.IsCancellationRequested) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (to != null) |
|||
{ |
|||
to.IsVisible = true; |
|||
to.ZIndex = 1; |
|||
} |
|||
|
|||
if (from != null) |
|||
{ |
|||
from.ZIndex = 0; |
|||
} |
|||
|
|||
await AnimateProgress(0.0, 1.0, from, to, forward, cancellationToken); |
|||
|
|||
if (to != null && !cancellationToken.IsCancellationRequested) |
|||
{ |
|||
to.Clip = null; |
|||
} |
|||
|
|||
if (from != null && !cancellationToken.IsCancellationRequested) |
|||
{ |
|||
from.IsVisible = false; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Update( |
|||
double progress, |
|||
Visual? from, |
|||
Visual? to, |
|||
bool forward, |
|||
double pageLength, |
|||
IReadOnlyList<PageTransitionItem> visibleItems) |
|||
{ |
|||
if (visibleItems.Count > 0) |
|||
{ |
|||
UpdateVisibleItems(from, to, forward, pageLength, visibleItems); |
|||
return; |
|||
} |
|||
|
|||
if (from is null && to is null) |
|||
return; |
|||
var parent = GetVisualParent(from, to); |
|||
var size = parent.Bounds.Size; |
|||
var centerOffset = WaveCenterOffset * CenterSensitivity; |
|||
var isHorizontal = Orientation == PageSlide.SlideAxis.Horizontal; |
|||
|
|||
if (to != null) |
|||
{ |
|||
to.IsVisible = progress > 0.0; |
|||
to.ZIndex = 1; |
|||
to.Opacity = 1; |
|||
|
|||
if (progress >= 1.0) |
|||
{ |
|||
to.Clip = null; |
|||
} |
|||
else |
|||
{ |
|||
var waveProgress = WaveEasing?.Ease(progress) ?? progress; |
|||
var clip = LiquidSwipeClipper.CreateWavePath( |
|||
waveProgress, |
|||
size, |
|||
centerOffset, |
|||
forward, |
|||
isHorizontal, |
|||
MaxBulge, |
|||
BulgeFactor, |
|||
CrossBulgeFactor, |
|||
BulgeExponent); |
|||
to.Clip = clip; |
|||
} |
|||
} |
|||
|
|||
if (from != null) |
|||
{ |
|||
from.IsVisible = true; |
|||
from.ZIndex = 0; |
|||
from.Opacity = 1; |
|||
} |
|||
} |
|||
|
|||
private void UpdateVisibleItems( |
|||
Visual? from, |
|||
Visual? to, |
|||
bool forward, |
|||
double pageLength, |
|||
IReadOnlyList<PageTransitionItem> visibleItems) |
|||
{ |
|||
if (from is null && to is null) |
|||
return; |
|||
|
|||
var parent = GetVisualParent(from, to); |
|||
var size = parent.Bounds.Size; |
|||
var centerOffset = WaveCenterOffset * CenterSensitivity; |
|||
var isHorizontal = Orientation == PageSlide.SlideAxis.Horizontal; |
|||
var resolvedPageLength = pageLength > 0 |
|||
? pageLength |
|||
: (isHorizontal ? size.Width : size.Height); |
|||
foreach (var item in visibleItems) |
|||
{ |
|||
var visual = item.Visual; |
|||
visual.IsVisible = true; |
|||
visual.Opacity = 1; |
|||
visual.Clip = null; |
|||
visual.ZIndex = ReferenceEquals(visual, to) ? 1 : 0; |
|||
|
|||
if (!ReferenceEquals(visual, to)) |
|||
continue; |
|||
|
|||
var visibleFraction = GetVisibleFraction(item.ViewportCenterOffset, size, resolvedPageLength, isHorizontal); |
|||
if (visibleFraction >= 1.0) |
|||
continue; |
|||
|
|||
visual.Clip = LiquidSwipeClipper.CreateWavePath( |
|||
visibleFraction, |
|||
size, |
|||
centerOffset, |
|||
forward, |
|||
isHorizontal, |
|||
MaxBulge, |
|||
BulgeFactor, |
|||
CrossBulgeFactor, |
|||
BulgeExponent); |
|||
} |
|||
} |
|||
|
|||
private static double GetVisibleFraction(double offsetFromCenter, Size viewportSize, double pageLength, bool isHorizontal) |
|||
{ |
|||
if (pageLength <= 0) |
|||
return 1.0; |
|||
|
|||
var viewportLength = isHorizontal ? viewportSize.Width : viewportSize.Height; |
|||
if (viewportLength <= 0) |
|||
return 0.0; |
|||
|
|||
var viewportUnits = viewportLength / pageLength; |
|||
var edgePeek = Math.Max(0.0, (viewportUnits - 1.0) / 2.0); |
|||
return Math.Clamp(1.0 + edgePeek - Math.Abs(offsetFromCenter), 0.0, 1.0); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Reset(Visual visual) |
|||
{ |
|||
visual.Clip = null; |
|||
visual.ZIndex = 0; |
|||
visual.Opacity = 1; |
|||
} |
|||
|
|||
private async Task AnimateProgress( |
|||
double from, |
|||
double to, |
|||
Visual? fromVisual, |
|||
Visual? toVisual, |
|||
bool forward, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
var parent = GetVisualParent(fromVisual, toVisual); |
|||
var pageLength = Orientation == PageSlide.SlideAxis.Horizontal |
|||
? parent.Bounds.Width |
|||
: parent.Bounds.Height; |
|||
var durationMs = Math.Max(Duration.TotalMilliseconds * Math.Abs(to - from), 50); |
|||
var startTicks = Stopwatch.GetTimestamp(); |
|||
var tickFreq = Stopwatch.Frequency; |
|||
|
|||
while (!cancellationToken.IsCancellationRequested) |
|||
{ |
|||
var elapsedMs = (Stopwatch.GetTimestamp() - startTicks) * 1000.0 / tickFreq; |
|||
var t = Math.Clamp(elapsedMs / durationMs, 0.0, 1.0); |
|||
var eased = SlideInEasing?.Ease(t) ?? t; |
|||
var progress = from + (to - from) * eased; |
|||
|
|||
Update(progress, fromVisual, toVisual, forward, pageLength, Array.Empty<PageTransitionItem>()); |
|||
|
|||
if (t >= 1.0) |
|||
break; |
|||
|
|||
await Task.Delay(16, cancellationToken); |
|||
} |
|||
|
|||
if (!cancellationToken.IsCancellationRequested) |
|||
{ |
|||
Update(to, fromVisual, toVisual, forward, pageLength, Array.Empty<PageTransitionItem>()); |
|||
} |
|||
} |
|||
|
|||
private static class LiquidSwipeClipper |
|||
{ |
|||
public static Geometry CreateWavePath( |
|||
double progress, |
|||
Size size, |
|||
double waveCenterOffset, |
|||
bool forward, |
|||
bool isHorizontal, |
|||
double maxBulge, |
|||
double bulgeFactor, |
|||
double crossBulgeFactor, |
|||
double bulgeExponent) |
|||
{ |
|||
var width = size.Width; |
|||
var height = size.Height; |
|||
|
|||
if (progress <= 0) |
|||
return new RectangleGeometry(new Rect(0, 0, 0, 0)); |
|||
|
|||
if (progress >= 1) |
|||
return new RectangleGeometry(new Rect(0, 0, width, height)); |
|||
|
|||
if (width <= 0 || height <= 0) |
|||
return new RectangleGeometry(new Rect(0, 0, 0, 0)); |
|||
|
|||
var mainLength = isHorizontal ? width : height; |
|||
var crossLength = isHorizontal ? height : width; |
|||
|
|||
var wavePhase = Math.Sin(progress * Math.PI); |
|||
var bulgeProgress = bulgeExponent == 1.0 ? wavePhase : Math.Pow(wavePhase, bulgeExponent); |
|||
var revealedLength = mainLength * progress; |
|||
var bulgeMain = Math.Min(mainLength * bulgeFactor, maxBulge) * bulgeProgress; |
|||
bulgeMain = Math.Min(bulgeMain, revealedLength * 0.45); |
|||
var bulgeCross = crossLength * crossBulgeFactor; |
|||
|
|||
var waveCenter = crossLength / 2 + waveCenterOffset; |
|||
waveCenter = Math.Clamp(waveCenter, bulgeCross, crossLength - bulgeCross); |
|||
|
|||
var geometry = new StreamGeometry(); |
|||
using (var context = geometry.Open()) |
|||
{ |
|||
if (isHorizontal) |
|||
{ |
|||
if (forward) |
|||
{ |
|||
var waveX = width * (1 - progress); |
|||
context.BeginFigure(new Point(width, 0), true); |
|||
context.LineTo(new Point(waveX, 0)); |
|||
context.CubicBezierTo( |
|||
new Point(waveX, waveCenter - bulgeCross), |
|||
new Point(waveX - bulgeMain, waveCenter - bulgeCross * 0.5), |
|||
new Point(waveX - bulgeMain, waveCenter)); |
|||
context.CubicBezierTo( |
|||
new Point(waveX - bulgeMain, waveCenter + bulgeCross * 0.5), |
|||
new Point(waveX, waveCenter + bulgeCross), |
|||
new Point(waveX, height)); |
|||
context.LineTo(new Point(width, height)); |
|||
context.EndFigure(true); |
|||
} |
|||
else |
|||
{ |
|||
var waveX = width * progress; |
|||
context.BeginFigure(new Point(0, 0), true); |
|||
context.LineTo(new Point(waveX, 0)); |
|||
context.CubicBezierTo( |
|||
new Point(waveX, waveCenter - bulgeCross), |
|||
new Point(waveX + bulgeMain, waveCenter - bulgeCross * 0.5), |
|||
new Point(waveX + bulgeMain, waveCenter)); |
|||
context.CubicBezierTo( |
|||
new Point(waveX + bulgeMain, waveCenter + bulgeCross * 0.5), |
|||
new Point(waveX, waveCenter + bulgeCross), |
|||
new Point(waveX, height)); |
|||
context.LineTo(new Point(0, height)); |
|||
context.EndFigure(true); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (forward) |
|||
{ |
|||
var waveY = height * (1 - progress); |
|||
context.BeginFigure(new Point(0, height), true); |
|||
context.LineTo(new Point(0, waveY)); |
|||
context.CubicBezierTo( |
|||
new Point(waveCenter - bulgeCross, waveY), |
|||
new Point(waveCenter - bulgeCross * 0.5, waveY - bulgeMain), |
|||
new Point(waveCenter, waveY - bulgeMain)); |
|||
context.CubicBezierTo( |
|||
new Point(waveCenter + bulgeCross * 0.5, waveY - bulgeMain), |
|||
new Point(waveCenter + bulgeCross, waveY), |
|||
new Point(width, waveY)); |
|||
context.LineTo(new Point(width, height)); |
|||
context.EndFigure(true); |
|||
} |
|||
else |
|||
{ |
|||
var waveY = height * progress; |
|||
context.BeginFigure(new Point(0, 0), true); |
|||
context.LineTo(new Point(0, waveY)); |
|||
context.CubicBezierTo( |
|||
new Point(waveCenter - bulgeCross, waveY), |
|||
new Point(waveCenter - bulgeCross * 0.5, waveY + bulgeMain), |
|||
new Point(waveCenter, waveY + bulgeMain)); |
|||
context.CubicBezierTo( |
|||
new Point(waveCenter + bulgeCross * 0.5, waveY + bulgeMain), |
|||
new Point(waveCenter + bulgeCross, waveY), |
|||
new Point(width, waveY)); |
|||
context.LineTo(new Point(width, 0)); |
|||
context.EndFigure(true); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return geometry; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// An <see cref="IPageTransition"/> that supports progress-driven updates.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Transitions implementing this interface can be driven by a normalized progress value
|
|||
/// (0.0 to 1.0) during swipe gestures or programmatic animations, rather than running
|
|||
/// as a timed animation via <see cref="IPageTransition.Start"/>.
|
|||
/// </remarks>
|
|||
public interface IProgressPageTransition : IPageTransition |
|||
{ |
|||
/// <summary>
|
|||
/// Updates the transition to reflect the given progress.
|
|||
/// </summary>
|
|||
/// <param name="progress">The normalized progress value from 0.0 (start) to 1.0 (complete).</param>
|
|||
/// <param name="from">The visual being transitioned away from. May be null.</param>
|
|||
/// <param name="to">The visual being transitioned to. May be null.</param>
|
|||
/// <param name="forward">Whether the transition direction is forward (next) or backward (previous).</param>
|
|||
/// <param name="pageLength">The size of a page along the transition axis.</param>
|
|||
/// <param name="visibleItems">The currently visible realized pages, if more than one page is visible.</param>
|
|||
void Update( |
|||
double progress, |
|||
Visual? from, |
|||
Visual? to, |
|||
bool forward, |
|||
double pageLength, |
|||
IReadOnlyList<PageTransitionItem> visibleItems); |
|||
|
|||
/// <summary>
|
|||
/// Resets any visual state applied to the given visual by this transition.
|
|||
/// </summary>
|
|||
/// <param name="visual">The visual to reset.</param>
|
|||
void Reset(Visual visual); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Describes a single visible page within a carousel viewport.
|
|||
/// </summary>
|
|||
public readonly record struct PageTransitionItem( |
|||
int Index, |
|||
Visual Visual, |
|||
double ViewportCenterOffset); |
|||
} |
|||
@ -1,17 +1,38 @@ |
|||
namespace Avalonia.Input.Navigation; |
|||
|
|||
internal class XYFocusOptions |
|||
internal sealed class XYFocusOptions |
|||
{ |
|||
public InputElement? SearchRoot { get; set; } |
|||
public Rect ExclusionRect { get; set; } |
|||
public Rect? FocusHintRectangle { get; set; } |
|||
public Rect? FocusedElementBounds { get; set; } |
|||
public XYFocusNavigationStrategy? NavigationStrategyOverride { get; set; } |
|||
public bool IgnoreClipping { get; set; } = true; |
|||
public bool IgnoreClipping { get; set; } |
|||
public bool IgnoreCone { get; set; } |
|||
public KeyDeviceType? KeyDeviceType { get; set; } |
|||
public bool ConsiderEngagement { get; set; } = true; |
|||
public bool UpdateManifold { get; set; } = true; |
|||
public bool ConsiderEngagement { get; set; } |
|||
public bool UpdateManifold { get; set; } |
|||
public bool UpdateManifoldsFromFocusHintRect { get; set; } |
|||
public bool IgnoreOcclusivity { get; set; } |
|||
|
|||
public XYFocusOptions() |
|||
{ |
|||
Reset(); |
|||
} |
|||
|
|||
internal void Reset() |
|||
{ |
|||
SearchRoot = null; |
|||
ExclusionRect = default; |
|||
FocusHintRectangle = null; |
|||
FocusedElementBounds = null; |
|||
NavigationStrategyOverride = null; |
|||
IgnoreClipping = true; |
|||
IgnoreCone = false; |
|||
KeyDeviceType = null; |
|||
ConsiderEngagement = true; |
|||
UpdateManifold = true; |
|||
UpdateManifoldsFromFocusHintRect = false; |
|||
IgnoreOcclusivity = false; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,28 @@ |
|||
namespace Avalonia.Input |
|||
{ |
|||
/// <summary>
|
|||
/// Specifies the direction of a swipe gesture.
|
|||
/// </summary>
|
|||
public enum SwipeDirection |
|||
{ |
|||
/// <summary>
|
|||
/// The swipe moved to the left.
|
|||
/// </summary>
|
|||
Left, |
|||
|
|||
/// <summary>
|
|||
/// The swipe moved to the right.
|
|||
/// </summary>
|
|||
Right, |
|||
|
|||
/// <summary>
|
|||
/// The swipe moved upward.
|
|||
/// </summary>
|
|||
Up, |
|||
|
|||
/// <summary>
|
|||
/// The swipe moved downward.
|
|||
/// </summary>
|
|||
Down |
|||
} |
|||
} |
|||
@ -1,50 +1,81 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
/// <summary>
|
|||
/// Specifies the direction of a swipe gesture.
|
|||
/// </summary>
|
|||
public enum SwipeDirection { Left, Right, Up, Down } |
|||
|
|||
/// <summary>
|
|||
/// Provides data for the <see cref="InputElement.SwipeGestureEvent"/> routed event.
|
|||
/// Provides data for swipe gesture events.
|
|||
/// </summary>
|
|||
public class SwipeGestureEventArgs : RoutedEventArgs |
|||
{ |
|||
private static int _nextId = 1; |
|||
internal static int GetNextFreeId() => _nextId++; |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SwipeGestureEventArgs"/> class.
|
|||
/// </summary>
|
|||
/// <param name="id">The unique identifier for this gesture.</param>
|
|||
/// <param name="delta">The pixel delta since the last event.</param>
|
|||
/// <param name="velocity">The current swipe velocity in pixels per second.</param>
|
|||
public SwipeGestureEventArgs(int id, Vector delta, Vector velocity) |
|||
: base(InputElement.SwipeGestureEvent) |
|||
{ |
|||
Id = id; |
|||
Delta = delta; |
|||
Velocity = velocity; |
|||
SwipeDirection = Math.Abs(delta.X) >= Math.Abs(delta.Y) |
|||
? (delta.X <= 0 ? SwipeDirection.Right : SwipeDirection.Left) |
|||
: (delta.Y <= 0 ? SwipeDirection.Down : SwipeDirection.Up); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the unique identifier for this swipe gesture instance.
|
|||
/// Gets the unique identifier for this gesture sequence.
|
|||
/// </summary>
|
|||
public int Id { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the direction of the swipe gesture.
|
|||
/// Gets the pixel delta since the last event.
|
|||
/// </summary>
|
|||
public SwipeDirection SwipeDirection { get; } |
|||
public Vector Delta { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the total translation vector of the swipe gesture.
|
|||
/// Gets the current swipe velocity in pixels per second.
|
|||
/// </summary>
|
|||
public Vector Delta { get; } |
|||
public Vector Velocity { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the position, relative to the target element, where the swipe started.
|
|||
/// Gets the direction of the dominant swipe axis.
|
|||
/// </summary>
|
|||
public Point StartPoint { get; } |
|||
public SwipeDirection SwipeDirection { get; } |
|||
|
|||
private static int s_nextId; |
|||
|
|||
internal static int GetNextFreeId() => Interlocked.Increment(ref s_nextId); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Provides data for the swipe gesture ended event.
|
|||
/// </summary>
|
|||
public class SwipeGestureEndedEventArgs : RoutedEventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="SwipeGestureEventArgs"/>.
|
|||
/// Initializes a new instance of the <see cref="SwipeGestureEndedEventArgs"/> class.
|
|||
/// </summary>
|
|||
public SwipeGestureEventArgs(int id, SwipeDirection direction, Vector delta, Point startPoint) |
|||
: base(InputElement.SwipeGestureEvent) |
|||
/// <param name="id">The unique identifier for this gesture.</param>
|
|||
/// <param name="velocity">The swipe velocity at release in pixels per second.</param>
|
|||
public SwipeGestureEndedEventArgs(int id, Vector velocity) |
|||
: base(InputElement.SwipeGestureEndedEvent) |
|||
{ |
|||
Id = id; |
|||
SwipeDirection = direction; |
|||
Delta = delta; |
|||
StartPoint = startPoint; |
|||
Velocity = velocity; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the unique identifier for this gesture sequence.
|
|||
/// </summary>
|
|||
public int Id { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the swipe velocity at release in pixels per second.
|
|||
/// </summary>
|
|||
public Vector Velocity { get; } |
|||
} |
|||
} |
|||
|
|||
File diff suppressed because it is too large
@ -0,0 +1,48 @@ |
|||
using System; |
|||
using System.Windows.Forms; |
|||
using static Avalonia.Win32.Interop.UnmanagedMethods; |
|||
|
|||
namespace Avalonia.Win32.Interoperability; |
|||
|
|||
/// <summary>
|
|||
/// Provides a message filter for integrating Avalonia within a WinForms application.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This filter ensures that key messages, which are typically handled specially by WinForms,
|
|||
/// are intercepted and routed to Avalonia's windows. This is necessary to preserve proper input handling
|
|||
/// in mixed WinForms and Avalonia application scenarios.
|
|||
/// </remarks>
|
|||
public class WinFormsAvaloniaMessageFilter : IMessageFilter |
|||
{ |
|||
/// <inheritdoc />
|
|||
public bool PreFilterMessage(ref Message m) |
|||
{ |
|||
// WinForms handles key messages specially, preventing them from reaching Avalonia's windows.
|
|||
// Handle them first.
|
|||
if (m.Msg >= (int)WindowsMessage.WM_KEYFIRST && |
|||
m.Msg <= (int)WindowsMessage.WM_KEYLAST && |
|||
WindowImpl.IsOurWindowGlobal(m.HWnd) && |
|||
!IsInsideWinForms(m.HWnd)) |
|||
{ |
|||
var msg = new MSG |
|||
{ |
|||
hwnd = m.HWnd, |
|||
message = (uint)m.Msg, |
|||
wParam = m.WParam, |
|||
lParam = m.LParam |
|||
}; |
|||
|
|||
TranslateMessage(ref msg); |
|||
DispatchMessage(ref msg); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private static bool IsInsideWinForms(IntPtr hwnd) |
|||
{ |
|||
var parentHwnd = GetParent(hwnd); |
|||
return parentHwnd != IntPtr.Zero && Control.FromHandle(parentHwnd) is WinFormsAvaloniaControlHost; |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue