├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── Avalonia.Flexbox.sln
├── Directory.Build.props
├── Directory.Build.targets
├── Icon.ico
├── Icon.png
├── Icon.svg
├── LICENSE.md
├── README.md
├── appveyor.yml
├── build
└── Targets
│ ├── Import.props
│ ├── Import.targets
│ ├── PackageProperties.props
│ ├── PackageProperties.targets
│ ├── PackageVersions.targets
│ └── RepoLayout.props
├── samples
├── Avalonia.Flexbox.Demo
│ ├── App.axaml
│ ├── App.axaml.cs
│ ├── Avalonia.Flexbox.Demo.csproj
│ ├── Converters
│ │ └── NumberToThicknessConverter.cs
│ ├── MainWindow.axaml
│ ├── MainWindow.axaml.cs
│ ├── Resources
│ │ └── Icon.png
│ └── ViewModels
│ │ ├── ItemViewModel.cs
│ │ └── MainWindowViewModel.cs
└── Directory.Build.props
└── src
└── Avalonia.Flexbox
├── AlignContent.cs
├── AlignItems.cs
├── Avalonia.Flexbox.csproj
├── Flex.cs
├── FlexDirection.cs
├── FlexLayout.cs
├── FlexPanel.cs
├── FlexWrap.cs
├── IFlexLayout.cs
├── JustifyContent.cs
├── PanelNonVirtualizingLayoutContext.cs
└── Uv.cs
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 4
5 | indent_style = space
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | end_of_line = crlf
9 |
10 | [*.cs]
11 | csharp_style_conditional_delegate_call = true:warning
12 | csharp_style_expression_bodied_accessors = true:suggestion
13 | csharp_style_expression_bodied_constructors = false:warning
14 | csharp_style_expression_bodied_indexers = true:suggestion
15 | csharp_style_expression_bodied_methods = true:suggestion
16 | csharp_style_expression_bodied_operators = true:suggestion
17 | csharp_style_expression_bodied_properties = true:suggestion
18 | csharp_style_inlined_variable_declaration = true:warning
19 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
20 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
21 | csharp_style_throw_expression = true:warning
22 | csharp_style_var_elsewhere = true:suggestion
23 | csharp_style_var_for_built_in_types = false:none
24 | csharp_style_var_when_type_is_apparent = true:suggestion
25 |
26 | csharp_new_line_before_catch = true:warning
27 | csharp_new_line_before_else = true:warning
28 | csharp_new_line_before_finally = true:warning
29 | csharp_new_line_before_members_in_anonymous_types = true:warning
30 | csharp_new_line_before_members_in_object_initializers = true:warning
31 | #csharp_new_line_before_open_brace = all:warning
32 | csharp_new_line_between_query_expression_clauses = true:warning
33 |
34 | csharp_indent_case_contents = true:warning
35 | csharp_indent_labels = one_less_than_current:suggestion
36 | csharp_indent_switch_labels = true:warning
37 |
38 | csharp_preserve_single_line_blocks = true:suggestion
39 | csharp_preserve_single_line_statements = false:warning
40 |
41 | csharp_space_after_cast = false:warning
42 | csharp_space_after_keywords_in_control_flow_statements = true:warning
43 | csharp_space_between_method_call_parameter_list_parentheses = false:warning
44 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning
45 | csharp_space_between_parentheses = false:warning
46 |
47 | csharp_prefer_braces = true:warning
48 | csharp_prefer_simple_default_expression = false:none
49 |
50 | dotnet_sort_system_directives_first = true:suggestion
51 |
52 | dotnet_style_coalesce_expression = true:warning
53 | dotnet_style_collection_initializer = true:suggestion
54 | dotnet_style_explicit_tuple_names = true:suggestion
55 | dotnet_style_null_propagation = true:warning
56 | dotnet_style_object_initializer = true:suggestion
57 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
58 | dotnet_style_predefined_type_for_member_access = false:warning
59 | dotnet_style_qualification_for_event = false:warning
60 | dotnet_style_qualification_for_field = false:warning
61 | dotnet_style_qualification_for_method = false:warning
62 | dotnet_style_qualification_for_property = false:warning
63 |
64 | dotnet_naming_rule.camel_case_for_private_fields.severity = suggestion
65 | dotnet_naming_rule.camel_case_for_private_fields.symbols = private_fields
66 | dotnet_naming_rule.camel_case_for_private_fields.style = camel_case_underscore_style
67 |
68 | dotnet_naming_symbols.private_fields.applicable_kinds = field
69 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private
70 |
71 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
72 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
73 |
74 | dotnet_code_quality.ca1801.api_surface = private, internal
75 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: jp2masa
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific files
2 |
3 | .vs/
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Build results
10 |
11 | [Aa]rtifacts/
12 | [Bb]in/
13 | [Oo]bj/
14 |
--------------------------------------------------------------------------------
/Avalonia.Flexbox.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32929.385
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Flexbox", "src\Avalonia.Flexbox\Avalonia.Flexbox.csproj", "{3474C152-4B81-4E94-B149-82BCC1B4A8E8}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4EC01977-A4B9-43F0-A9AD-F800A11B9429}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | appveyor.yml = appveyor.yml
12 | Directory.Build.props = Directory.Build.props
13 | Directory.Build.targets = Directory.Build.targets
14 | EndProjectSection
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5B227943-8228-466D-BECB-2BAFDC3DDF30}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{FC21DC29-CDDA-49DA-805A-BFF4A1084B81}"
19 | ProjectSection(SolutionItems) = preProject
20 | build\Targets\Import.props = build\Targets\Import.props
21 | build\Targets\Import.targets = build\Targets\Import.targets
22 | build\Targets\PackageProperties.props = build\Targets\PackageProperties.props
23 | build\Targets\PackageProperties.targets = build\Targets\PackageProperties.targets
24 | build\Targets\PackageVersions.targets = build\Targets\PackageVersions.targets
25 | build\Targets\RepoLayout.props = build\Targets\RepoLayout.props
26 | EndProjectSection
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Flexbox.Demo", "samples\Avalonia.Flexbox.Demo\Avalonia.Flexbox.Demo.csproj", "{64B2D521-D0AA-4D25-BC01-BBCA35A36948}"
29 | EndProject
30 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{0ED17017-116A-4D3B-A094-40AEDFE95385}"
31 | ProjectSection(SolutionItems) = preProject
32 | samples\Directory.Build.props = samples\Directory.Build.props
33 | EndProjectSection
34 | EndProject
35 | Global
36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
37 | Debug|Any CPU = Debug|Any CPU
38 | Release|Any CPU = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
41 | {3474C152-4B81-4E94-B149-82BCC1B4A8E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {3474C152-4B81-4E94-B149-82BCC1B4A8E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {3474C152-4B81-4E94-B149-82BCC1B4A8E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {3474C152-4B81-4E94-B149-82BCC1B4A8E8}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {64B2D521-D0AA-4D25-BC01-BBCA35A36948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {64B2D521-D0AA-4D25-BC01-BBCA35A36948}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {64B2D521-D0AA-4D25-BC01-BBCA35A36948}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {64B2D521-D0AA-4D25-BC01-BBCA35A36948}.Release|Any CPU.Build.0 = Release|Any CPU
49 | EndGlobalSection
50 | GlobalSection(SolutionProperties) = preSolution
51 | HideSolutionNode = FALSE
52 | EndGlobalSection
53 | GlobalSection(NestedProjects) = preSolution
54 | {5B227943-8228-466D-BECB-2BAFDC3DDF30} = {4EC01977-A4B9-43F0-A9AD-F800A11B9429}
55 | {FC21DC29-CDDA-49DA-805A-BFF4A1084B81} = {5B227943-8228-466D-BECB-2BAFDC3DDF30}
56 | {0ED17017-116A-4D3B-A094-40AEDFE95385} = {4EC01977-A4B9-43F0-A9AD-F800A11B9429}
57 | EndGlobalSection
58 | GlobalSection(ExtensibilityGlobals) = postSolution
59 | SolutionGuid = {8A1CEBA2-78F5-4365-9E9B-84E75DEA43E8}
60 | EndGlobalSection
61 | EndGlobal
62 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildThisFileDirectory)
5 |
6 |
7 |
8 | $(RepoRoot)\Icon.ico
9 |
10 |
11 |
12 | Latest
13 | Enable
14 |
15 |
16 |
17 | 0.3.0-beta.4
18 | -build.$(APPVEYOR_BUILD_NUMBER)+$(APPVEYOR_REPO_COMMIT.Substring(0, 7))
19 | -localbuild$([System.DateTime]::Now.ToString("yyyyMMddHHmmss"))
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/Avalonia.Flexbox/8fcef06197fd97741cb410d5f3e70faf17b1dc17/Icon.ico
--------------------------------------------------------------------------------
/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/Avalonia.Flexbox/8fcef06197fd97741cb410d5f3e70faf17b1dc17/Icon.png
--------------------------------------------------------------------------------
/Icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
132 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright © jp2masa
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the “Software”), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://ci.appveyor.com/project/jp2masa/avalonia-flexbox)
2 |
3 | # Avalonia.Flexbox
4 |
5 | 
6 |
7 | Avalonia.Flexbox is a flexbox implementation for Avalonia.
8 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.3.0-build{build}
2 | image: Visual Studio 2022
3 |
4 | shallow_clone: true
5 | clone_folder: c:\Avalonia.Flexbox
6 |
7 | configuration:
8 | - Debug
9 | - Release
10 | platform: Any CPU
11 |
12 | matrix:
13 | fast_finish: true
14 |
15 | nuget:
16 | account_feed: false
17 | project_feed: true
18 | disable_publish_on_pr: true
19 |
20 | build_script:
21 | - cmd: dotnet msbuild /t:Restore;Build;Pack Avalonia.Flexbox.sln
22 |
23 | test: off
24 |
25 | artifacts:
26 | - path: 'artifacts\Debug\nupkg\*.nupkg'
27 | name: DebugNupkg
28 | - path: 'artifacts\Release\nupkg\*.nupkg'
29 | name: ReleaseNupkg
30 |
31 | deploy:
32 | - provider: NuGet
33 | api_key:
34 | secure: xWkQ7tm0o3jWSCcsQ9QiBIPe5VDqJbCzf9aVi+zIgnbhXV2FGT34eOuJetk7QqLU
35 | artifact: ReleaseNupkg
36 | on:
37 | branch: master
38 | configuration: Release
39 | appveyor_repo_tag: true
40 | - provider: NuGet
41 | server: https://www.myget.org/F/jp2masa/api/v2/package
42 | artifact: DebugNupkg
43 | api_key:
44 | secure: puOcEbngEmaVMEnUL20u4mzATgvoyaTPRWGGwE98as1+8KGY3ypOKzt5OV63duwI
45 | on:
46 | branch: master
47 | configuration: Debug
48 |
--------------------------------------------------------------------------------
/build/Targets/Import.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/build/Targets/Import.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/build/Targets/PackageProperties.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(PackageVersion)$(PackageVersionSuffix)
5 |
6 |
7 |
8 | Avalonia.Flexbox is a flexbox implementation for Avalonia.
9 |
10 |
11 |
12 | jp2masa
13 | Copyright © $([System.DateTime]::Now.Year) jp2masa
14 | Icon.png
15 | http://github.com/jp2masa/Avalonia.Flexbox
16 | MIT
17 | True
18 | flex flexbox avalonia css dotnet core csharp vb visual basic fsharp
19 | git
20 | https://github.com/jp2masa/Avalonia.Flexbox
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/build/Targets/PackageProperties.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(AssemblyName)
5 | jp2masa.$(PackageId)
6 | $(PackageDescription)
7 |
8 | $(BaseDescription)
9 | $(BaseDescription)
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/build/Targets/PackageVersions.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 11.0.0-rc2.1
5 | 7.0.3
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/build/Targets/RepoLayout.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | $(Platform)
7 |
8 |
9 |
10 | $(RepoRoot)artifacts\
11 | $(ArtifactsDir)$(Configuration)\nupkg\
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls.ApplicationLifetimes;
2 | using Avalonia.Markup.Xaml;
3 | using Avalonia.ReactiveUI;
4 |
5 | using Avalonia.Flexbox.Demo.ViewModels;
6 |
7 | namespace Avalonia.Flexbox.Demo
8 | {
9 | public class App : Application
10 | {
11 | public override void Initialize() => AvaloniaXamlLoader.Load(this);
12 |
13 | public override void OnFrameworkInitializationCompleted()
14 | {
15 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
16 | {
17 | desktop.MainWindow = new MainWindow() { DataContext = new MainWindowViewModel() };
18 | }
19 |
20 | base.OnFrameworkInitializationCompleted();
21 | }
22 |
23 | public static void Main(string[] args) => BuildAvaloniaApp()
24 | .StartWithClassicDesktopLifetime(args);
25 |
26 | public static AppBuilder BuildAvaloniaApp() =>
27 | AppBuilder.Configure()
28 | .UsePlatformDetect()
29 | .UseReactiveUI()
30 | .LogToTrace();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/Avalonia.Flexbox.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | WinExe
6 | $(IconIcoPath)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/Converters/NumberToThicknessConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 |
4 | using Avalonia.Data.Converters;
5 |
6 | namespace Avalonia.Flexbox.Demo.Converters
7 | {
8 | internal sealed class NumberToThicknessConverter : IValueConverter
9 | {
10 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
11 | {
12 | if (value is int x && targetType.IsAssignableFrom(typeof(Thickness)))
13 | {
14 | var y = 16 + 2 * ((x * 5) % 9);
15 | return new Thickness(2 * y, y);
16 | }
17 |
18 | throw new NotSupportedException();
19 | }
20 |
21 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
22 | throw new NotSupportedException();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
17 |
20 |
21 |
24 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
40 |
43 |
48 |
49 |
50 |
51 |
52 |
55 |
56 |
57 |
58 |
60 |
61 |
62 |
64 |
66 |
67 |
68 |
69 |
70 |
72 |
73 |
74 |
75 |
76 |
78 |
79 |
80 |
81 |
82 |
84 |
85 |
86 |
87 |
88 |
90 |
91 |
92 |
93 |
94 |
96 |
97 |
98 |
99 |
100 |
102 |
103 |
104 |
105 |
106 |
108 |
109 |
110 |
113 |
114 |
115 |
116 |
119 |
120 |
121 |
122 |
123 |
125 |
126 |
127 |
128 |
129 |
134 |
135 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
152 |
153 |
159 |
160 |
161 |
162 |
169 |
170 |
171 |
172 |
173 |
176 |
177 |
179 |
180 |
186 |
187 |
188 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Interactivity;
3 | using Avalonia.ReactiveUI;
4 |
5 | using Avalonia.Flexbox.Demo.ViewModels;
6 |
7 | namespace Avalonia.Flexbox.Demo
8 | {
9 | public sealed partial class MainWindow : ReactiveWindow
10 | {
11 | public MainWindow()
12 | {
13 | InitializeComponent();
14 | }
15 |
16 | private void OnItemTapped(object? sender, RoutedEventArgs e)
17 | {
18 | if (sender is ListBoxItem control && control.DataContext is ItemViewModel item)
19 | {
20 | if (ViewModel.SelectedItem != null)
21 | {
22 | ViewModel.SelectedItem.IsSelected = false;
23 | }
24 |
25 | if (ViewModel.SelectedItem == item)
26 | {
27 | ViewModel.SelectedItem = null;
28 | }
29 | else
30 | {
31 | ViewModel.SelectedItem = item;
32 | ViewModel.SelectedItem.IsSelected = true;
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/Resources/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp2masa/Avalonia.Flexbox/8fcef06197fd97741cb410d5f3e70faf17b1dc17/samples/Avalonia.Flexbox.Demo/Resources/Icon.png
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/ViewModels/ItemViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Linq;
2 |
3 | using ReactiveUI;
4 |
5 | namespace Avalonia.Flexbox.Demo.ViewModels
6 | {
7 | public sealed class ItemViewModel : ReactiveObject
8 | {
9 | internal const AlignItems AlignSelfAuto = (AlignItems)(-1);
10 |
11 | private readonly ObservableAsPropertyHelper _alignSelf;
12 |
13 | private bool _isSelected;
14 | private bool _isVisible = true;
15 |
16 | private AlignItems _alignSelfItem = AlignSelfAuto;
17 | private int _order;
18 |
19 | public ItemViewModel(int value)
20 | {
21 | Value = value;
22 |
23 | _alignSelf = (from item in this.WhenAnyValue(vm => vm.AlignSelfItem)
24 | select item == AlignSelfAuto ? default(AlignItems?) : item).ToProperty(this, nameof(AlignSelf));
25 | }
26 |
27 | public int Value { get; }
28 |
29 | public bool IsSelected
30 | {
31 | get => _isSelected;
32 | set => this.RaiseAndSetIfChanged(ref _isSelected, value);
33 | }
34 |
35 | public bool IsVisible
36 | {
37 | get => _isVisible;
38 | set => this.RaiseAndSetIfChanged(ref _isVisible, value);
39 | }
40 |
41 | public AlignItems AlignSelfItem
42 | {
43 | get => _alignSelfItem;
44 | set => this.RaiseAndSetIfChanged(ref _alignSelfItem, value);
45 | }
46 |
47 | public AlignItems? AlignSelf => _alignSelf.Value;
48 |
49 | public int Order
50 | {
51 | get => _order;
52 | set => this.RaiseAndSetIfChanged(ref _order, value);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/samples/Avalonia.Flexbox.Demo/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Linq;
6 | using System.Reactive.Linq;
7 | using System.Windows.Input;
8 |
9 | using ReactiveUI;
10 |
11 | namespace Avalonia.Flexbox.Demo.ViewModels
12 | {
13 | public sealed class MainWindowViewModel : ReactiveObject
14 | {
15 | private readonly ObservableCollection _numbers;
16 |
17 | private bool _isItemsControl = true;
18 | private bool _isItemsRepeater;
19 |
20 | private FlexDirection _direction = FlexDirection.Row;
21 | private JustifyContent _justifyContent = JustifyContent.FlexStart;
22 | private AlignItems _alignItems = AlignItems.FlexStart;
23 | private AlignContent _alignContent = AlignContent.FlexStart;
24 | private FlexWrap _wrap = FlexWrap.Wrap;
25 |
26 | private int _columnSpacing = 8;
27 | private int _rowSpacing = 32;
28 |
29 | private int _currentNumber = 41;
30 |
31 | private ItemViewModel? _selectedItem;
32 |
33 | public MainWindowViewModel()
34 | {
35 | _numbers = new ObservableCollection(Enumerable.Range(1, 40).Select(x => new ItemViewModel(x)));
36 |
37 | Numbers = new ReadOnlyObservableCollection(_numbers);
38 |
39 | AddItemCommand = ReactiveCommand.Create(AddItem);
40 | RemoveItemCommand = ReactiveCommand.Create(RemoveItem, this.WhenAnyValue(vm => vm.SelectedItem).Select(x => x != null));
41 | }
42 |
43 | public IEnumerable DirectionValues { get; } = Enum.GetValues(typeof(FlexDirection));
44 |
45 | public IEnumerable JustifyContentValues { get; } = Enum.GetValues(typeof(JustifyContent));
46 |
47 | public IEnumerable AlignItemsValues { get; } = Enum.GetValues(typeof(AlignItems));
48 |
49 | public IEnumerable AlignContentValues { get; } = Enum.GetValues(typeof(AlignContent));
50 |
51 | public IEnumerable WrapValues { get; } = Enum.GetValues(typeof(FlexWrap));
52 |
53 | public IEnumerable AlignSelfValues { get; } = Enum.GetValues(typeof(AlignItems)).Cast().Prepend(ItemViewModel.AlignSelfAuto);
54 |
55 | public bool IsItemsControl
56 | {
57 | get => _isItemsControl;
58 | set => this.RaiseAndSetIfChanged(ref _isItemsControl, value);
59 | }
60 |
61 | public bool IsItemsRepeater
62 | {
63 | get => _isItemsRepeater;
64 | set => this.RaiseAndSetIfChanged(ref _isItemsRepeater, value);
65 | }
66 |
67 | public FlexDirection Direction
68 | {
69 | get => _direction;
70 | set => this.RaiseAndSetIfChanged(ref _direction, value);
71 | }
72 |
73 | public JustifyContent JustifyContent
74 | {
75 | get => _justifyContent;
76 | set => this.RaiseAndSetIfChanged(ref _justifyContent, value);
77 | }
78 |
79 | public AlignItems AlignItems
80 | {
81 | get => _alignItems;
82 | set => this.RaiseAndSetIfChanged(ref _alignItems, value);
83 | }
84 |
85 | public AlignContent AlignContent
86 | {
87 | get => _alignContent;
88 | set => this.RaiseAndSetIfChanged(ref _alignContent, value);
89 | }
90 |
91 | public FlexWrap Wrap
92 | {
93 | get => _wrap;
94 | set => this.RaiseAndSetIfChanged(ref _wrap, value);
95 | }
96 |
97 | public int ColumnSpacing
98 | {
99 | get => _columnSpacing;
100 | set => this.RaiseAndSetIfChanged(ref _columnSpacing, value);
101 | }
102 |
103 | public int RowSpacing
104 | {
105 | get => _rowSpacing;
106 | set => this.RaiseAndSetIfChanged(ref _rowSpacing, value);
107 | }
108 |
109 | public ReadOnlyObservableCollection Numbers { get; }
110 |
111 | public ItemViewModel? SelectedItem
112 | {
113 | get => _selectedItem;
114 | set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
115 | }
116 |
117 | public ICommand AddItemCommand { get; }
118 |
119 | public ICommand RemoveItemCommand { get; }
120 |
121 | private void AddItem() => _numbers.Add(new ItemViewModel(_currentNumber++));
122 |
123 | private void RemoveItem()
124 | {
125 | if (SelectedItem is null)
126 | {
127 | throw new InvalidOperationException();
128 | }
129 |
130 | _numbers.Remove(SelectedItem);
131 |
132 | SelectedItem.IsSelected = false;
133 | SelectedItem = null;
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/samples/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | False
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/AlignContent.cs:
--------------------------------------------------------------------------------
1 | namespace Avalonia.Flexbox
2 | {
3 | public enum AlignContent
4 | {
5 | FlexStart,
6 | FlexEnd,
7 | Center,
8 | Stretch,
9 | SpaceBetween,
10 | SpaceAround,
11 | SpaceEvenly
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/AlignItems.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace Avalonia.Flexbox
4 | {
5 | [SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names")]
6 | public enum AlignItems
7 | {
8 | FlexStart,
9 | FlexEnd,
10 | Center,
11 | Stretch
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/Avalonia.Flexbox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/Flex.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Avalonia.Layout;
4 |
5 | namespace Avalonia.Flexbox
6 | {
7 | public static class Flex
8 | {
9 | public static readonly AttachedProperty AlignSelfProperty =
10 | AvaloniaProperty.RegisterAttached("AlignSelf", typeof(Flex));
11 |
12 | public static readonly AttachedProperty OrderProperty =
13 | AvaloniaProperty.RegisterAttached("Order", typeof(Flex));
14 |
15 | public static AlignItems? GetAlignSelf(Layoutable layoutable)
16 | {
17 | if (layoutable is null)
18 | {
19 | throw new ArgumentNullException(nameof(layoutable));
20 | }
21 |
22 | return layoutable.GetValue(AlignSelfProperty);
23 | }
24 |
25 | public static void SetAlignSelf(Layoutable layoutable, AlignItems? value)
26 | {
27 | if (layoutable is null)
28 | {
29 | throw new ArgumentNullException(nameof(layoutable));
30 | }
31 |
32 | layoutable.SetValue(AlignSelfProperty, value);
33 | }
34 |
35 | public static int GetOrder(Layoutable layoutable)
36 | {
37 | if (layoutable is null)
38 | {
39 | throw new ArgumentNullException(nameof(layoutable));
40 | }
41 |
42 | return layoutable.GetValue(OrderProperty);
43 | }
44 |
45 | public static void SetOrder(Layoutable layoutable, int value)
46 | {
47 | if (layoutable is null)
48 | {
49 | throw new ArgumentNullException(nameof(layoutable));
50 | }
51 |
52 | layoutable.SetValue(OrderProperty, value);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/FlexDirection.cs:
--------------------------------------------------------------------------------
1 | namespace Avalonia.Flexbox
2 | {
3 | public enum FlexDirection
4 | {
5 | Row,
6 | RowReverse,
7 | Column,
8 | ColumnReverse
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/FlexLayout.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using Avalonia.Layout;
6 | using Avalonia.Reactive;
7 |
8 | namespace Avalonia.Flexbox
9 | {
10 | public sealed partial class FlexLayout : NonVirtualizingLayout, IFlexLayout
11 | {
12 | public static readonly StyledProperty DirectionProperty =
13 | AvaloniaProperty.Register(nameof(Direction));
14 |
15 | public static readonly StyledProperty JustifyContentProperty =
16 | AvaloniaProperty.Register(nameof(JustifyContent));
17 |
18 | public static readonly StyledProperty AlignItemsProperty =
19 | AvaloniaProperty.Register(nameof(AlignItems));
20 |
21 | public static readonly StyledProperty AlignContentProperty =
22 | AvaloniaProperty.Register(nameof(AlignContent));
23 |
24 | public static readonly StyledProperty WrapProperty =
25 | AvaloniaProperty.Register(nameof(Wrap), FlexWrap.Wrap);
26 |
27 | public static readonly StyledProperty ColumnSpacingProperty =
28 | AvaloniaProperty.Register(nameof(ColumnSpacing));
29 |
30 | public static readonly StyledProperty RowSpacingProperty =
31 | AvaloniaProperty.Register(nameof(RowSpacing));
32 |
33 | private static readonly AttachedProperty OwnerFlexLayoutProperty =
34 | AvaloniaProperty.RegisterAttached("OwnerFlexLayout");
35 |
36 | private static readonly Func s_GetOrder = x => x is Layoutable y ? Flex.GetOrder(y) : 0;
37 | private static readonly Func s_IsVisible = x => x.IsVisible;
38 |
39 | static FlexLayout()
40 | {
41 | AffectsMeasure(
42 | DirectionProperty,
43 | JustifyContentProperty,
44 | WrapProperty,
45 | ColumnSpacingProperty,
46 | RowSpacingProperty);
47 |
48 | AffectsArrange(
49 | AlignItemsProperty,
50 | AlignContentProperty);
51 |
52 | AffectsLayoutMeasure(Flex.OrderProperty);
53 |
54 | AffectsLayoutArrange(Flex.AlignSelfProperty);
55 | }
56 |
57 | public FlexDirection Direction
58 | {
59 | get => GetValue(DirectionProperty);
60 | set => SetValue(DirectionProperty, value);
61 | }
62 |
63 | public JustifyContent JustifyContent
64 | {
65 | get => GetValue(JustifyContentProperty);
66 | set => SetValue(JustifyContentProperty, value);
67 | }
68 |
69 | public AlignItems AlignItems
70 | {
71 | get => GetValue(AlignItemsProperty);
72 | set => SetValue(AlignItemsProperty, value);
73 | }
74 |
75 | public AlignContent AlignContent
76 | {
77 | get => GetValue(AlignContentProperty);
78 | set => SetValue(AlignContentProperty, value);
79 | }
80 |
81 | public FlexWrap Wrap
82 | {
83 | get => GetValue(WrapProperty);
84 | set => SetValue(WrapProperty, value);
85 | }
86 |
87 | public double ColumnSpacing
88 | {
89 | get => GetValue(ColumnSpacingProperty);
90 | set => SetValue(ColumnSpacingProperty, value);
91 | }
92 |
93 | public double RowSpacing
94 | {
95 | get => GetValue(RowSpacingProperty);
96 | set => SetValue(RowSpacingProperty, value);
97 | }
98 |
99 | protected override Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize)
100 | {
101 | if (context is null)
102 | {
103 | throw new ArgumentNullException(nameof(context));
104 | }
105 |
106 | return Measure(this, context, availableSize);
107 | }
108 |
109 | internal static Size Measure(IFlexLayout layout, NonVirtualizingLayoutContext context, Size availableSize)
110 | {
111 | var isColumn = layout.Direction == FlexDirection.Column || layout.Direction == FlexDirection.ColumnReverse;
112 | var even = layout.JustifyContent == JustifyContent.SpaceEvenly ? 2 : 0;
113 |
114 | var max = Uv.FromSize(availableSize, isColumn);
115 | var spacing = Uv.FromSize(layout.ColumnSpacing, layout.RowSpacing, isColumn);
116 |
117 | var u = 0.0;
118 | var m = 0;
119 |
120 | var v = 0.0;
121 | var maxV = 0.0;
122 | var n = 0;
123 |
124 | var sections = new List();
125 | var first = 0;
126 |
127 | var i = 0;
128 |
129 | var children = context.Children.Where(s_IsVisible).OrderBy(s_GetOrder).ToArray();
130 |
131 | foreach (var element in children)
132 | {
133 | if (element is AvaloniaObject obj)
134 | {
135 | var owner = obj.GetValue(OwnerFlexLayoutProperty);
136 |
137 | if (owner is null)
138 | {
139 | obj.SetValue(OwnerFlexLayoutProperty, layout);
140 | }
141 | else if (owner != layout)
142 | {
143 | throw new InvalidOperationException();
144 | }
145 | }
146 |
147 | element.Measure(availableSize);
148 |
149 | var size = Uv.FromSize(element.DesiredSize, isColumn);
150 |
151 | if (layout.Wrap != FlexWrap.NoWrap && u + size.U + (m + even) * spacing.U > max.U)
152 | {
153 | sections.Add(new Section(first, i - 1, u, maxV));
154 |
155 | u = 0.0;
156 | m = 0;
157 |
158 | v += maxV;
159 | maxV = 0.0;
160 | n++;
161 |
162 | first = i;
163 | }
164 |
165 | if (size.V > maxV)
166 | {
167 | maxV = size.V;
168 | }
169 |
170 | u += size.U;
171 | m++;
172 |
173 | i++;
174 | }
175 |
176 | if (m != 0)
177 | {
178 | sections.Add(new Section(first, first + m - 1, u, maxV));
179 | }
180 |
181 | if (layout.Wrap == FlexWrap.WrapReverse)
182 | {
183 | sections.Reverse();
184 | }
185 |
186 | context.LayoutState = new FlexLayoutState(children, sections);
187 |
188 | if (sections.Count == 0)
189 | {
190 | return default;
191 | }
192 |
193 | return Uv.ToSize(new Uv(sections.Max(s => s.U + (s.Last - s.First + even) * spacing.U), v + maxV + (sections.Count - 1) * spacing.V), isColumn);
194 | }
195 |
196 | protected override Size ArrangeOverride(NonVirtualizingLayoutContext context, Size finalSize)
197 | {
198 | if (context is null)
199 | {
200 | throw new ArgumentNullException(nameof(context));
201 | }
202 |
203 | return Arrange(this, context, finalSize);
204 | }
205 |
206 | internal static Size Arrange(IFlexLayout layout, NonVirtualizingLayoutContext context, Size finalSize)
207 | {
208 | var isColumn = layout.Direction == FlexDirection.Column || layout.Direction == FlexDirection.ColumnReverse;
209 | var isReverse = layout.Direction == FlexDirection.RowReverse || layout.Direction == FlexDirection.ColumnReverse;
210 |
211 | var state = (FlexLayoutState)context.LayoutState;
212 | var n = state.Sections.Count;
213 |
214 | var size = Uv.FromSize(finalSize, isColumn);
215 | var spacing = Uv.FromSize(layout.ColumnSpacing, layout.RowSpacing, isColumn);
216 |
217 | var totalSectionV = 0.0;
218 |
219 | foreach (var section in state.Sections)
220 | {
221 | totalSectionV += section.V;
222 | }
223 |
224 | var totalSpacingV = (n - 1) * spacing.V;
225 |
226 | var totalV = totalSectionV + totalSpacingV;
227 |
228 | var spacingV = layout.AlignContent switch
229 | {
230 | AlignContent.FlexStart => spacing.V,
231 | AlignContent.FlexEnd => spacing.V,
232 | AlignContent.Center => spacing.V,
233 | AlignContent.Stretch => spacing.V,
234 | AlignContent.SpaceBetween => spacing.V + (size.V - totalV) / (n - 1),
235 | AlignContent.SpaceAround => (size.V - totalSectionV) / n,
236 | AlignContent.SpaceEvenly => (size.V - totalSectionV) / (n + 1),
237 | _ => throw new NotImplementedException()
238 | };
239 |
240 | var scaleV = layout.AlignContent == AlignContent.Stretch ? ((size.V - totalSpacingV) / totalSectionV) : 1.0;
241 |
242 | var v = layout.AlignContent switch
243 | {
244 | AlignContent.FlexStart => 0.0,
245 | AlignContent.FlexEnd => size.V - totalV,
246 | AlignContent.Center => (size.V - totalV) / 2,
247 | AlignContent.Stretch => 0,
248 | AlignContent.SpaceBetween => 0.0,
249 | AlignContent.SpaceAround => spacingV / 2,
250 | AlignContent.SpaceEvenly => spacingV,
251 | _ => throw new NotImplementedException()
252 | };
253 |
254 | foreach (var section in state.Sections)
255 | {
256 | var sectionV = scaleV * section.V;
257 |
258 | var (spacingU, u) = layout.JustifyContent switch
259 | {
260 | JustifyContent.FlexStart => (spacing.U, 0.0),
261 | JustifyContent.FlexEnd => (spacing.U, size.U - section.U - (section.Last - section.First) * spacing.U),
262 | JustifyContent.Center => (spacing.U, (size.U - section.U - (section.Last - section.First) * spacing.U) / 2),
263 | JustifyContent.SpaceBetween => ((size.U - section.U) / (section.Last - section.First), 0.0),
264 | JustifyContent.SpaceAround => (spacing.U, (size.U - section.U - (section.Last - section.First) * spacing.U) / 2),
265 | JustifyContent.SpaceEvenly => ((size.U - section.U) / (section.Last - section.First + 2), (size.U - section.U) / (section.Last - section.First + 2)),
266 | _ => throw new NotImplementedException()
267 | };
268 |
269 | for (int i = section.First; i <= section.Last; i++)
270 | {
271 | var element = state.Children[i];
272 | var elementSize = Uv.FromSize(element.DesiredSize, isColumn);
273 |
274 | var align = layout.AlignItems;
275 |
276 | if (element is Layoutable layoutable)
277 | {
278 | align = Flex.GetAlignSelf(layoutable) ?? align;
279 | }
280 |
281 | double finalV = align switch
282 | {
283 | AlignItems.FlexStart => v,
284 | AlignItems.FlexEnd => v + sectionV - elementSize.V,
285 | AlignItems.Center => v + (sectionV - elementSize.V) / 2,
286 | AlignItems.Stretch => v,
287 | _ => throw new NotImplementedException()
288 | };
289 |
290 | if (align == AlignItems.Stretch)
291 | {
292 | elementSize = new Uv(elementSize.U, sectionV);
293 | }
294 |
295 | var position = new Uv(isReverse ? (size.U - elementSize.U - u) : u, finalV);
296 |
297 | element.Arrange(new Rect(Uv.ToPoint(position, isColumn), Uv.ToSize(elementSize, isColumn)));
298 |
299 | u += elementSize.U + spacingU;
300 | }
301 |
302 | v += sectionV + spacingV;
303 | }
304 |
305 | return finalSize;
306 | }
307 |
308 | // Adapted from Avalonia: https://github.com/AvaloniaUI/Avalonia/blob/92fdbb5152f11f707b3f46cda25472525c70c3a3/src/Avalonia.Base/Layout/Layoutable.cs
309 | // - AffectsMeasure
310 | // - AffectsArrange
311 | private static void AffectsMeasure(params AvaloniaProperty[] properties)
312 | {
313 | var invalidateObserver = new AnonymousObserver(
314 | static e => (e.Sender as FlexLayout)?.InvalidateMeasure());
315 |
316 | foreach (var property in properties)
317 | {
318 | property.Changed.Subscribe(invalidateObserver);
319 | }
320 | }
321 |
322 | private static void AffectsArrange(params AvaloniaProperty[] properties)
323 | {
324 | var invalidateObserver = new AnonymousObserver(
325 | static e => (e.Sender as FlexLayout)?.InvalidateArrange());
326 |
327 | foreach (var property in properties)
328 | {
329 | property.Changed.Subscribe(invalidateObserver);
330 | }
331 | }
332 |
333 | // Adapted from Avalonia: https://github.com/AvaloniaUI/Avalonia/blob/92fdbb5152f11f707b3f46cda25472525c70c3a3/src/Avalonia.Controls/Panel.cs
334 | // - AffectsParentMeasure
335 | // - AffectsParentArrange
336 | // - AffectsParentMeasureInvalidate
337 | // - AffectsParentArrangeInvalidate
338 | private static void AffectsLayoutMeasure(params AvaloniaProperty[] properties)
339 | {
340 | var invalidateObserver = new AnonymousObserver(
341 | AffectsLayoutMeasureInvalidate);
342 |
343 | foreach (var property in properties)
344 | {
345 | property.Changed.Subscribe(invalidateObserver);
346 | }
347 | }
348 |
349 | private static void AffectsLayoutArrange(params AvaloniaProperty[] properties)
350 | {
351 | var invalidateObserver = new AnonymousObserver(
352 | AffectsLayoutArrangeInvalidate);
353 |
354 | foreach (var property in properties)
355 | {
356 | property.Changed.Subscribe(invalidateObserver);
357 | }
358 | }
359 |
360 | private static void AffectsLayoutMeasureInvalidate(AvaloniaPropertyChangedEventArgs e)
361 | {
362 | var control = e.Sender;
363 | var owner = control.GetValue(OwnerFlexLayoutProperty);
364 |
365 | if (owner is FlexLayout layout)
366 | {
367 | layout.InvalidateMeasure();
368 | }
369 | }
370 |
371 | private static void AffectsLayoutArrangeInvalidate(AvaloniaPropertyChangedEventArgs e)
372 | {
373 | var control = e.Sender;
374 | var owner = control.GetValue(OwnerFlexLayoutProperty);
375 |
376 | if (owner is FlexLayout layout)
377 | {
378 | layout.InvalidateArrange();
379 | }
380 | }
381 |
382 | private struct FlexLayoutState
383 | {
384 | public FlexLayoutState(IReadOnlyList children, IReadOnlyList sections)
385 | {
386 | Children = children;
387 | Sections = sections;
388 | }
389 |
390 | public IReadOnlyList Children { get; }
391 |
392 | public IReadOnlyList Sections { get; }
393 | }
394 |
395 | private struct Section
396 | {
397 | public Section(int first, int last, double u, double v)
398 | {
399 | First = first;
400 | Last = last;
401 | U = u;
402 | V = v;
403 | }
404 |
405 | public int First { get; }
406 |
407 | public int Last { get; }
408 |
409 | public double U { get; }
410 |
411 | public double V { get; }
412 | }
413 | }
414 | }
415 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/FlexPanel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Layout;
3 |
4 | namespace Avalonia.Flexbox
5 | {
6 | public sealed class FlexPanel : Panel, IFlexLayout
7 | {
8 | public static readonly StyledProperty DirectionProperty =
9 | AvaloniaProperty.Register(nameof(Direction));
10 |
11 | public static readonly StyledProperty JustifyContentProperty =
12 | AvaloniaProperty.Register(nameof(JustifyContent));
13 |
14 | public static readonly StyledProperty AlignItemsProperty =
15 | AvaloniaProperty.Register(nameof(AlignItems));
16 |
17 | public static readonly StyledProperty AlignContentProperty =
18 | AvaloniaProperty.Register(nameof(AlignContent));
19 |
20 | public static readonly StyledProperty WrapProperty =
21 | AvaloniaProperty.Register(nameof(Wrap), FlexWrap.Wrap);
22 |
23 | public static readonly StyledProperty ColumnSpacingProperty =
24 | AvaloniaProperty.Register(nameof(ColumnSpacing));
25 |
26 | public static readonly StyledProperty RowSpacingProperty =
27 | AvaloniaProperty.Register(nameof(RowSpacing));
28 |
29 | private readonly NonVirtualizingLayoutContext _layoutContext;
30 |
31 | static FlexPanel()
32 | {
33 | AffectsMeasure(
34 | DirectionProperty,
35 | JustifyContentProperty,
36 | WrapProperty,
37 | ColumnSpacingProperty,
38 | RowSpacingProperty);
39 |
40 | AffectsArrange(
41 | AlignItemsProperty,
42 | AlignContentProperty);
43 |
44 | AffectsParentMeasure(Flex.OrderProperty);
45 |
46 | AffectsParentArrange(Flex.AlignSelfProperty);
47 | }
48 |
49 | public FlexPanel()
50 | {
51 | _layoutContext = new PanelNonVirtualizingLayoutContext(this);
52 | }
53 |
54 | public FlexDirection Direction
55 | {
56 | get => GetValue(DirectionProperty);
57 | set => SetValue(DirectionProperty, value);
58 | }
59 |
60 | public JustifyContent JustifyContent
61 | {
62 | get => GetValue(JustifyContentProperty);
63 | set => SetValue(JustifyContentProperty, value);
64 | }
65 |
66 | public AlignItems AlignItems
67 | {
68 | get => GetValue(AlignItemsProperty);
69 | set => SetValue(AlignItemsProperty, value);
70 | }
71 |
72 | public AlignContent AlignContent
73 | {
74 | get => GetValue(AlignContentProperty);
75 | set => SetValue(AlignContentProperty, value);
76 | }
77 |
78 | public FlexWrap Wrap
79 | {
80 | get => GetValue(WrapProperty);
81 | set => SetValue(WrapProperty, value);
82 | }
83 |
84 | public double ColumnSpacing
85 | {
86 | get => GetValue(ColumnSpacingProperty);
87 | set => SetValue(ColumnSpacingProperty, value);
88 | }
89 |
90 | public double RowSpacing
91 | {
92 | get => GetValue(RowSpacingProperty);
93 | set => SetValue(RowSpacingProperty, value);
94 | }
95 |
96 | protected override Size MeasureOverride(Size availableSize) => FlexLayout.Measure(this, _layoutContext, availableSize);
97 |
98 | protected override Size ArrangeOverride(Size finalSize) => FlexLayout.Arrange(this, _layoutContext, finalSize);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/FlexWrap.cs:
--------------------------------------------------------------------------------
1 | namespace Avalonia.Flexbox
2 | {
3 | public enum FlexWrap
4 | {
5 | NoWrap,
6 | Wrap,
7 | WrapReverse
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/IFlexLayout.cs:
--------------------------------------------------------------------------------
1 | namespace Avalonia.Flexbox
2 | {
3 | internal interface IFlexLayout
4 | {
5 | public FlexDirection Direction { get; }
6 |
7 | public JustifyContent JustifyContent { get; }
8 |
9 | public AlignItems AlignItems { get; }
10 |
11 | public AlignContent AlignContent { get; }
12 |
13 | public FlexWrap Wrap { get; }
14 |
15 | public double ColumnSpacing { get; }
16 |
17 | public double RowSpacing { get; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/JustifyContent.cs:
--------------------------------------------------------------------------------
1 | namespace Avalonia.Flexbox
2 | {
3 | public enum JustifyContent
4 | {
5 | FlexStart,
6 | FlexEnd,
7 | Center,
8 | SpaceBetween,
9 | SpaceAround,
10 | SpaceEvenly
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/PanelNonVirtualizingLayoutContext.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | using Avalonia.Controls;
4 | using Avalonia.Layout;
5 |
6 | namespace Avalonia.Flexbox
7 | {
8 | internal sealed class PanelNonVirtualizingLayoutContext : NonVirtualizingLayoutContext
9 | {
10 | private readonly Panel _panel;
11 |
12 | public PanelNonVirtualizingLayoutContext(Panel panel)
13 | {
14 | _panel = panel;
15 | }
16 |
17 | protected override IReadOnlyList ChildrenCore =>
18 | _panel.Children;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Avalonia.Flexbox/Uv.cs:
--------------------------------------------------------------------------------
1 | namespace Avalonia.Flexbox
2 | {
3 | internal struct Uv
4 | {
5 | public Uv(double u, double v)
6 | {
7 | U = u;
8 | V = v;
9 | }
10 |
11 | public double U { get; }
12 |
13 | public double V { get; }
14 |
15 | public static Uv FromSize(double width, double height, bool swap) => new Uv(swap ? height : width, swap ? width : height);
16 |
17 | public static Uv FromSize(Size size, bool swap) => FromSize(size.Width, size.Height, swap);
18 |
19 | public static Point ToPoint(Uv uv, bool swap) => new Point(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
20 |
21 | public static Size ToSize(Uv uv, bool swap) => new Size(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------