Selaa lähdekoodia

Merge remote-tracking branch 'origin/kenric' into frank

frogsoftware 1 kuukausi sitten
vanhempi
commit
0a7ac89c99

+ 269 - 0
InABox.Avalonia/Components/Accordion/Accordion.cs

@@ -0,0 +1,269 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.LogicalTree;
+using Avalonia.Media;
+using Avalonia.Metadata;
+using CommunityToolkit.Mvvm.Input;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace InABox.Avalonia.Components;
+
+[TemplatePart(Name = "PART_Grid")]
+public partial class Accordion : TemplatedControl
+{
+    public static readonly StyledProperty<ObservableCollection<AccordionItem>> ItemsProperty =
+        AvaloniaProperty.Register<Accordion, ObservableCollection<AccordionItem>>(nameof(Items));
+    
+    public static readonly StyledProperty<object?> SelectedItemProperty = 
+        AvaloniaProperty.Register<Accordion, object?>(nameof(SelectedItem));
+
+    public static readonly DirectProperty<Accordion, int> SelectedIndexProperty =
+        AvaloniaProperty.RegisterDirect<Accordion, int>(nameof(SelectedIndex), x => x.SelectedIndex, (x, v) => x.SelectedIndex = v);
+
+    [Content]
+    public ObservableCollection<AccordionItem> Items
+    {
+        get => GetValue(ItemsProperty);
+        set => SetValue(ItemsProperty, value);
+    }
+
+    private AccordionItem? SelectedButton
+    {
+        get => Items.FirstOrDefault(x => x.Selected);
+        set
+        {
+            var i = 0;
+            var index = -1;
+            foreach(var item in Items)
+            {
+                item.Selected = item == value;
+                if(item == value)
+                {
+                    index = i;
+                }
+                if(Grid is not null)
+                {
+                    Grid.RowDefinitions[i].Height = item == value ? GridLength.Star : GridLength.Auto;
+                }
+                ++i;
+            }
+            SelectedItem = value?.Tag;
+        }
+    }
+
+    public object? SelectedItem
+    {
+        get => GetValue(SelectedItemProperty);
+        private set => SetValue(SelectedItemProperty, value);
+    }
+
+    public int SelectedIndex
+    {
+        get => SelectedButton != null && Items.Contains(SelectedButton) ? Items.IndexOf(SelectedButton) : -1;
+        set => SelectedButton = value > -1 && value < Items.Count ? Items[value] : null;
+    }
+
+    public event EventHandler? SelectionChanged;
+
+    private Grid? Grid;
+
+    static Accordion()
+    {
+        ItemsProperty.Changed.AddClassHandler<Accordion>(Items_Changed);
+    }
+
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        base.OnApplyTemplate(e);
+        
+        Grid = e.NameScope.Get<Grid>("PART_Grid");
+        if(Grid is not null)
+        {
+            foreach(var item in Items)
+            {
+                Grid.SetRow(item, Grid.RowDefinitions.Count);
+                Grid.RowDefinitions.Add(new(item.Selected ? GridLength.Star : GridLength.Auto));
+                Grid.Children.Add(item);
+            }
+        }
+    }
+
+    private static void Items_Changed(Accordion strip, AvaloniaPropertyChangedEventArgs args)
+    {
+        strip.LogicalChildren.Clear();
+        strip.Grid?.Children.Clear();
+
+        if (strip.Items is not null)
+        {
+            strip.SelectedButton = strip.Items.FirstOrDefault();
+
+            strip.LogicalChildren.AddRange(strip.Items);
+            foreach(var item in strip.Items)
+            {
+                item.Command = strip.ItemSelectedCommand;
+                if(strip.Grid is not null)
+                {
+                    Grid.SetRow(item, strip.Grid.RowDefinitions.Count);
+                    strip.Grid.RowDefinitions.Add(new(item.Selected ? GridLength.Star : GridLength.Auto));
+                    strip.Grid.Children.Add(item);
+                }
+            }
+        }
+    }
+
+    public Accordion()
+    {
+        Items = new();
+        Items.CollectionChanged += Items_CollectionChanged;
+    }
+
+    private void Items_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+    {
+        SelectedButton ??= Items.FirstOrDefault();
+        switch (e.Action)
+        {
+            case NotifyCollectionChangedAction.Add:
+                AddControlItemsToLogicalChildren(e.NewItems);
+                break;
+            case NotifyCollectionChangedAction.Remove:
+                RemoveControlItemsFromLogicalChildren(e.OldItems);
+                break;
+        }
+        foreach(var item in Items)
+        {
+            item.Command = ItemSelectedCommand;
+        }
+    }
+
+    private void AddControlItemsToLogicalChildren(IEnumerable? items)
+    {
+        if (items is null) return;
+
+        List<Control>? toAdd = null;
+        foreach(var i in items)
+        {
+            if(i is Control control && !LogicalChildren.Contains(control))
+            {
+                toAdd ??= new();
+                toAdd.Add(control);
+            }
+        }
+        if(toAdd is not null)
+        {
+            LogicalChildren.AddRange(toAdd);
+            if(Grid is not null)
+            {
+                foreach(var item in toAdd)
+                {
+                    Grid.SetRow(item, Grid.RowDefinitions.Count);
+                    Grid.RowDefinitions.Add(new(item is AccordionItem aItem && aItem.Selected ? GridLength.Star : GridLength.Auto));
+                    Grid.Children.Add(item);
+                }
+            }
+        }
+    }
+    private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
+    {
+        if (items is null) return;
+
+        List<Control>? toRemove = null;
+        foreach(var i in items)
+        {
+            if(i is Control control)
+            {
+                toRemove ??= new();
+                toRemove.Add(control);
+            }
+        }
+        if(toRemove is not null)
+        {
+            foreach(var item in toRemove)
+            {
+                var index = LogicalChildren.IndexOf(item);
+                if(index != -1)
+                {
+                    Grid?.RowDefinitions.RemoveAt(index);
+                    Grid?.Children.Remove(item);
+                }
+            }
+            LogicalChildren.RemoveAll(toRemove);
+            if(Grid is not null)
+            {
+                var i = 0;
+                foreach(var item in LogicalChildren)
+                {
+                    if(item is Control control)
+                    {
+                        Grid.SetRow(control, i);
+                        ++i;
+                    }
+                }
+            }
+        }
+    }
+
+    [RelayCommand]
+    private void ItemSelected(AccordionItem item)
+    {
+        var children = this.GetLogicalChildren().ToArray();
+        SelectedButton = item;
+        SelectionChanged?.Invoke(this, new());
+    }
+}
+
+[PseudoClasses(":selected")]
+public class AccordionItem : TemplatedControl
+{
+    public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<AccordionItem, string>(nameof(Text));
+    public static readonly StyledProperty<object?> ContentProperty = AvaloniaProperty.Register<AccordionItem, object?>(nameof(Content));
+    public static readonly StyledProperty<bool> SelectedProperty = AvaloniaProperty.Register<AccordionItem, bool>(nameof(Selected));
+    public static readonly StyledProperty<int> IndexProperty = AvaloniaProperty.Register<AccordionItem, int>(nameof(Index));
+    public static readonly StyledProperty<ICommand> CommandProperty = AvaloniaProperty.Register<AccordionItem, ICommand>(nameof(Command));
+
+    public string Text
+    {
+        get => GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
+    }
+    [Content]
+    public object? Content
+    {
+        get => GetValue(ContentProperty);
+        set => SetValue(ContentProperty, value);
+    }
+    public bool Selected
+    {
+        get => GetValue(SelectedProperty);
+        set => SetValue(SelectedProperty, value);
+    }
+    public int Index
+    {
+        get => GetValue(IndexProperty);
+        set => SetValue(IndexProperty, value);
+    }
+    public ICommand Command
+    {
+        get => GetValue(CommandProperty);
+        set => SetValue(CommandProperty, value);
+    }
+
+    static AccordionItem()
+    {
+        SelectedProperty.Changed.AddClassHandler<AccordionItem>(Selected_Changed);
+    }
+
+    private static void Selected_Changed(AccordionItem item, AvaloniaPropertyChangedEventArgs args)
+    {
+        item.PseudoClasses.Set(":selected", item.Selected);
+    }
+}

+ 4 - 4
InABox.Avalonia/Components/ButtonStrip/ButtonStrip.cs

@@ -234,10 +234,10 @@ public partial class ButtonStrip : TemplatedControl
 [PseudoClasses(":selected")]
 public class ButtonStripItem : TemplatedControl
 {
-    public static StyledProperty<string> TextProperty = AvaloniaProperty.Register<ButtonStripItem, string>(nameof(Text));
-    public static StyledProperty<bool> SelectedProperty = AvaloniaProperty.Register<ButtonStripItem, bool>(nameof(Selected));
-    public static StyledProperty<int> IndexProperty = AvaloniaProperty.Register<ButtonStripItem, int>(nameof(Index));
-    public static StyledProperty<ICommand> CommandProperty = AvaloniaProperty.Register<ButtonStripItem, ICommand>(nameof(Command));
+    public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<ButtonStripItem, string>(nameof(Text));
+    public static readonly StyledProperty<bool> SelectedProperty = AvaloniaProperty.Register<ButtonStripItem, bool>(nameof(Selected));
+    public static readonly StyledProperty<int> IndexProperty = AvaloniaProperty.Register<ButtonStripItem, int>(nameof(Index));
+    public static readonly StyledProperty<ICommand> CommandProperty = AvaloniaProperty.Register<ButtonStripItem, ICommand>(nameof(Command));
 
     public string Text
     {

+ 28 - 0
InABox.Avalonia/Theme/Classes/Accordion.axaml

@@ -0,0 +1,28 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:components="using:InABox.Avalonia.Components">
+    <Style Selector="components|Accordion">
+        <Setter Property="Template">
+            <ControlTemplate TargetType="components:Accordion">
+                <Grid Name="PART_Grid"/>
+            </ControlTemplate>
+        </Setter>
+    </Style>
+    <Style Selector="components|AccordionItem">
+        <Setter Property="Template">
+            <ControlTemplate TargetType="components:AccordionItem">
+                <Grid RowDefinitions="Auto,*">
+                    <Button Classes="Standard"
+                            Content="{TemplateBinding Text}"
+                            Command="{TemplateBinding Command}"
+                            CommandParameter="{TemplateBinding}"/>
+                    <Border Classes="Standard" Grid.Row="1"
+                            IsVisible="{TemplateBinding Selected}">
+                        <ContentPresenter Grid.Row="1"
+                                          Content="{TemplateBinding Content}" />
+                    </Border>
+                </Grid>
+            </ControlTemplate>
+        </Setter>
+    </Style>
+</Styles>

+ 83 - 83
InABox.Avalonia/Theme/Classes/ListViewButton.axaml

@@ -1,96 +1,96 @@
 <Styles xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-		xmlns:components="using:InABox.Avalonia.Components"
-		xmlns:converters="using:InABox.Avalonia.Converters">
-	<Style Selector="components|ListViewButton">
-		<Style.Resources>
-			<converters:DoubleToCornerRadiusConverter
-				x:Key="SphericalBorder"
-				Ratio="0.5" />
+        xmlns:components="using:InABox.Avalonia.Components"
+        xmlns:converters="using:InABox.Avalonia.Converters">
+    <Style Selector="components|ListViewButton">
+        <Style.Resources>
+            <converters:DoubleToCornerRadiusConverter
+                x:Key="SphericalBorder"
+                Ratio="0.5" />
 
-			<converters:DoubleToThicknessConverter
-				x:Key="MarginDoubler"
-				Ratio="2.0" />
-		</Style.Resources>
-		<Setter Property="Template">
-			<ControlTemplate>
-				<Button Classes="Standard">
-					<Button.Styles>
-						<Style Selector="Button.Standard">
-							<Setter Property="Height" Value="80"/>
-							<Setter Property="Padding" Value="0"/>
-							<Setter Property="Background" Value="{TemplateBinding Background}"/>
-							<Setter Property="Foreground" Value="{TemplateBinding Foreground}"/>
-							<Setter Property="BorderBrush" Value="{StaticResource PrsTileBorder}"/>
-							<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
-							<Setter Property="VerticalContentAlignment" Value="Stretch"/>
-							<Setter Property="Command" Value="{TemplateBinding Command}"/>
-							<Setter Property="CommandParameter" Value="{TemplateBinding CommandParameter}"/>
-						</Style>
-					</Button.Styles>
-					<Grid>
+            <converters:DoubleToThicknessConverter
+                x:Key="MarginDoubler"
+                Ratio="2.0" />
+        </Style.Resources>
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Button Classes="Standard">
+                    <Button.Styles>
+                        <Style Selector="Button.Standard">
+                            <Setter Property="Height" Value="80"/>
+                            <Setter Property="Padding" Value="0"/>
+                            <Setter Property="Background" Value="{TemplateBinding Background}"/>
+                            <Setter Property="Foreground" Value="{TemplateBinding Foreground}"/>
+                            <Setter Property="BorderBrush" Value="{StaticResource PrsTileBorder}"/>
+                            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+                            <Setter Property="VerticalContentAlignment" Value="Stretch"/>
+                            <Setter Property="Command" Value="{TemplateBinding Command}"/>
+                            <Setter Property="CommandParameter" Value="{TemplateBinding CommandParameter}"/>
+                        </Style>
+                    </Button.Styles>
+                    <Grid>
 
-						<Grid.RowDefinitions>
-							<RowDefinition Height="*" />
-							<RowDefinition Height="1.2*" />
-						</Grid.RowDefinitions>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="*" />
+                            <RowDefinition Height="1.2*" />
+                        </Grid.RowDefinitions>
 
-						<Grid.ColumnDefinitions>
-							<ColumnDefinition Width="70" />
-							<ColumnDefinition Width="*" />
-						</Grid.ColumnDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="70" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
 
-						<Image
-							Classes="Large"
-							Grid.Row="0"
-							Grid.Column="0"
-							Grid.RowSpan="2"
-							Source="{TemplateBinding Image}"
-							HorizontalAlignment="Center"
-							VerticalAlignment="Center" />
+                        <Image
+                            Classes="Large"
+                            Grid.Row="0"
+                            Grid.Column="0"
+                            Grid.RowSpan="2"
+                            Source="{TemplateBinding Image}"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center" />
 
-						<Label
-							Grid.Row="0"
-							Grid.Column="1"
-							VerticalAlignment="Stretch"
-							VerticalContentAlignment="Center"
-							FontSize="{StaticResource PrsFontSizeLarge}"
-							FontWeight="{StaticResource PrsFontWeightBold}"
-							Content="{TemplateBinding Title}" />
+                        <Label
+                            Grid.Row="0"
+                            Grid.Column="1"
+                            VerticalAlignment="Stretch"
+                            VerticalContentAlignment="Center"
+                            FontSize="{StaticResource PrsFontSizeLarge}"
+                            FontWeight="{StaticResource PrsFontWeightBold}"
+                            Content="{TemplateBinding Title}" />
 
-						<TextBlock
-							Grid.Row="1"
-							Grid.Column="1"
-							FontSize="{StaticResource PrsFontSizeNormal}"
-							FontStyle="{StaticResource PrsFontStylItalic}"
-							TextWrapping="WrapWithOverflow"
-							VerticalAlignment="Stretch"
-							Text="{TemplateBinding Description}" />
+                        <TextBlock
+                            Grid.Row="1"
+                            Grid.Column="1"
+                            FontSize="{StaticResource PrsFontSizeNormal}"
+                            FontStyle="{StaticResource PrsFontStylItalic}"
+                            TextWrapping="WrapWithOverflow"
+                            VerticalAlignment="Stretch"
+                            Text="{TemplateBinding Description}" />
 
-						<Border
-							Background="{StaticResource PrsTileBackground}"
-							BorderBrush="{StaticResource PrsTileBorder}"
-							Grid.Row="0"
-							Grid.RowSpan="2"
-							Grid.Column="1"
-							VerticalAlignment="Top"
-							HorizontalAlignment="Right"
-							IsVisible="{TemplateBinding Alert, Converter={StaticResource StringToBooleanConverter}}"
-							MinWidth="{Binding $self.Bounds.Height}"
-							Margin="{Binding $self, Converter={StaticResource MarginDoubler}, ConverterParameter={StaticResource PrsControlSpacing}}"
-							CornerRadius="{Binding $self.Bounds.Height, Converter={StaticResource SphericalBorder}}">
+                        <Border
+                            Background="{StaticResource PrsTileBackground}"
+                            BorderBrush="{StaticResource PrsTileBorder}"
+                            Grid.Row="0"
+                            Grid.RowSpan="2"
+                            Grid.Column="1"
+                            VerticalAlignment="Top"
+                            HorizontalAlignment="Right"
+                            IsVisible="{TemplateBinding Alert, Converter={StaticResource StringToBooleanConverter}}"
+                            MinWidth="{Binding $self.Bounds.Height}"
+                            Margin="{Binding $self, Converter={StaticResource MarginDoubler}, ConverterParameter={StaticResource PrsControlSpacing}}"
+                            CornerRadius="{Binding $self.Bounds.Height, Converter={StaticResource SphericalBorder}}">
 
-							<Label
-								Background="Transparent"
-								Foreground="{StaticResource PrsTileForeground}"
-								HorizontalContentAlignment="Center"
-								Content="{TemplateBinding Alert}" />
-						</Border>
+                            <Label
+                                Background="Transparent"
+                                Foreground="{StaticResource PrsTileForeground}"
+                                HorizontalContentAlignment="Center"
+                                Content="{TemplateBinding Alert}" />
+                        </Border>
 
-					</Grid>
-				</Button>
-			</ControlTemplate>
-		</Setter>
-	</Style>
+                    </Grid>
+                </Button>
+            </ControlTemplate>
+        </Setter>
+    </Style>
 
 </Styles>

+ 1 - 0
InABox.Avalonia/Theme/Styles.axaml

@@ -2,6 +2,7 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 	<StyleInclude Source="/Theme/Classes/ListViewButton.axaml"/>
 
+	<StyleInclude Source="/Theme/Classes/Accordion.axaml" />
 	<StyleInclude Source="/Theme/Classes/Border.axaml" />
 	<StyleInclude Source="/Theme/Classes/Button.axaml" />
 	<StyleInclude Source="/Theme/Classes/ButtonStrip.axaml" />