amwx / FluentAvalonia

Control library focused on fluent design and bringing more WinUI controls into Avalonia
MIT License
1.05k stars 97 forks source link

WIP: ItemsRepeater #545

Closed amwx closed 6 months ago

amwx commented 8 months ago

The PR adds the ItemsRepeater to FA. I started this last summer and finally have had a chance to try and get this in here. A couple reasons why I'm doing this:

  1. Avalonia plans to deprecate their version of the ItemsRepeater in the future, and the NavView is built upon that (and I'm not changing that)
  2. I'd like to port the new ItemsView control in WinUI (that's used in File Explorer) at some point, which uses the ItemsRepeater
  3. Adding the BreadcrumbBar is on my list too and that uses the ItemsRepeater.
  4. I wanted to include the experimental WinUI features too
  5. I want to be able to fix things about it, if necessary

This is NOT a move of the Avalonia version, this is entirely ported from the WinUI source. Since I started this last summer, this code may be out of date - one of my TODOs still is to check the source with the latest 1.5 release of WinUI post-open source, since they're using this in Explorer now, bugs may have been fixed or some stuff may have changed.

Some things to note:

  1. As of this PR, which I'm planning on including with 2.1, the reference to Avalonia.Controls.ItemsRepeater will no longer come with FA and all usages within are replaced with the built in version. Naming conflicts may come up in your code because I am not adding the "FA" prefix, the control will still be called ItemsRepeater.
  2. ItemsSourceView is a part of WinUI and began with the ItemsRepeater and creates a conflict. ItemsSourceView from WinUI has been renamed FAItemsSourceView and is incompatible with the Avalonia version. If you reference the ItemsRepeater.ItemsSourceView property, it is the FA type, NOT the Avalonia version.
  3. I am only planning to port the Layouts available in WinUI. The WrapLayout that is found in Avalonia will not be included.

New Features Added:

Once I have a chance to check the source, run some more tests, and look everything over, I'll try to get some videos here to show off the animator and the content phasing (which at least last time I tested, they worked)

amwx commented 7 months ago

Code was updated to WinUI 1.5, which featured several changes (as I expected). I was going to try to provide a demo for the item container animations, but after some changes for WinUI 1.5, that's going to be harder to do. The new LinedFlowLayout, which is what's in FileExplorer now will come at a later date. It turns out that layout is several thousand lines of code so that's gonna take some time. In addition, that layout features a transition/animation manager, but relies on Composition Animations APIs that Avalonia doesn't have yet - so I need to figure out how that's gonna work - especially since I have very little hope of Avalonia's composition system expanding anytime soon.

Preview of phased rendering. Note that this will be an experimental feature with no guarantees this will stay - it depends on how well it works. This really isn't needed in most cases anyway. One caveat off the bat, if you want phased rendering - ensure you keep your item sizes the same through each phase. Bad things tend to happen around scrolling if a later phase starts messing with the sizes (in the scrolling direction) and I'm not really sure how that can be fixed (this relates to virtualization estimating viewport sizes)

https://github.com/amwx/FluentAvalonia/assets/40413319/bc650d47-5210-4fd6-bd70-09079d2c9052

This example brute forces everything, you could easily load a custom data template by each phase or anything. Finding named controls, however, will require searching the tree. Phasing occurs after the element is prepared, so the tree should be ready. Code for this:

<ScrollViewer Margin="50">
    <ui:ItemsRepeater Name="ItemsRepeater1">
        <ui:ItemsRepeater.ItemTemplate>
            <DataTemplate>
                <Button Height="50">

                </Button>
            </DataTemplate>
        </ui:ItemsRepeater.ItemTemplate>
    </ui:ItemsRepeater>
</ScrollViewer>
// Subscribe to ContainerContentChanging to initiate phased rendering
ItemsRepeater1.ContainerContentChanging += ItemsRepeater1ContainerContentChanging;

// Main callback - this is phase 0 - this should be fast and can be used for placeholders, etc.
private void ItemsRepeater1ContainerContentChanging(ItemsRepeater sender, ContainerContentChangingEventArgs args)
{
    if (args.ItemContainer is Button b)
    {
        b.Content = "Loading...";

        // Call RegisterUpdateCallback to tell the system to schedule another pass with this element for Phase 1
        args.RegisterUpdateCallback(ShowFirstContent);
    }
}

// Phase 1 Callback - this is called at some point in the future
private async void ShowFirstContent(ItemsRepeater sender, ContainerContentChangingEventArgs args)
{
    if (args.ItemContainer is Button b)
    {
        await Task.Delay(500); // Simulate long running task
        b.Content = args.Item;

        // If you want another phase, call RegisterCallback to schedule it
        args.RegisterUpdateCallback(ShowSecondContent);        
    }
}

// Second phase
private async void ShowSecondContent(ItemsRepeater sender, ContainerContentChangingEventArgs args)
{
    if (args.ItemContainer is Button b)
    {
        await Task.Delay(500); // Simulate long running task
        b.Content = new StackPanel
        {
            Orientation = Avalonia.Layout.Orientation.Horizontal,
            Children =
            {
                new Rectangle
                {
                    Fill = Brushes.Red,
                    Width = 10, Height = 10,
                    VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
                    Margin = new Thickness(12)
                },
                new TextBlock
                {
                    Text = args.Item?.ToString()
                }
            }
        };
    }
}