├── .editorconfig
├── .github
├── stale.yml
└── workflows
│ └── build.yml
├── .gitignore
├── Avalonia.Controls.VariableSizedWrapGrid
├── Avalonia.Controls.VariableSizedWrapGrid.csproj
└── VariableSizedWrapGrid.cs
├── AvaloniaSample
├── App.axaml
├── App.axaml.cs
├── AvaloniaSample.csproj
├── MainWindow.axaml
├── MainWindow.axaml.cs
├── Program.cs
├── SamplesView.axaml
├── SamplesView.axaml.cs
└── ViewModels
│ ├── TilePanelViewModel.cs
│ ├── TileViewModel.cs
│ └── ViewModelBase.cs
├── LICENSE
├── NuGet.Config
├── README.md
├── VariableSizedWrapGrid.sln
├── WpfApplication1
├── App.config
├── App.xaml
├── App.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── MyGridView.cs
├── VariableSizedWrapGrid.cs
└── WpfApplication1.csproj
└── global.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Default settings:
7 | # A newline ending every file
8 | # Use 4 spaces as indentation
9 | [*]
10 | insert_final_newline = true
11 | indent_style = space
12 | indent_size = 4
13 |
14 | # C# files
15 | [*.cs]
16 | # New line preferences
17 | csharp_new_line_before_open_brace = all
18 | csharp_new_line_before_else = true
19 | csharp_new_line_before_catch = true
20 | csharp_new_line_before_finally = true
21 | csharp_new_line_before_members_in_object_initializers = true
22 | csharp_new_line_before_members_in_anonymous_types = true
23 | csharp_new_line_between_query_expression_clauses = true
24 |
25 | # Indentation preferences
26 | csharp_indent_block_contents = true
27 | csharp_indent_braces = false
28 | csharp_indent_case_contents = true
29 | csharp_indent_switch_labels = true
30 | csharp_indent_labels = one_less_than_current
31 |
32 | # avoid this. unless absolutely necessary
33 | dotnet_style_qualification_for_field = false:suggestion
34 | dotnet_style_qualification_for_property = false:suggestion
35 | dotnet_style_qualification_for_method = false:suggestion
36 | dotnet_style_qualification_for_event = false:suggestion
37 |
38 | # prefer var
39 | csharp_style_var_for_built_in_types = true
40 | csharp_style_var_when_type_is_apparent = true
41 | csharp_style_var_elsewhere = true:suggestion
42 |
43 | # use language keywords instead of BCL types
44 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
45 | dotnet_style_predefined_type_for_member_access = true:suggestion
46 |
47 | # name all constant fields using PascalCase
48 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
49 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
50 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
51 |
52 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
53 | dotnet_naming_symbols.constant_fields.required_modifiers = const
54 |
55 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
56 |
57 | # static fields should have s_ prefix
58 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
59 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
60 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
61 |
62 | dotnet_naming_symbols.static_fields.applicable_kinds = field
63 | dotnet_naming_symbols.static_fields.required_modifiers = static
64 |
65 | dotnet_naming_style.static_prefix_style.required_prefix = s_
66 | dotnet_naming_style.static_prefix_style.capitalization = camel_case
67 |
68 | # internal and private fields should be _camelCase
69 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
70 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
71 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
72 |
73 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
74 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
75 |
76 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
77 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
78 |
79 | # use accessibility modifiers
80 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
81 |
82 | # Code style defaults
83 | dotnet_sort_system_directives_first = true
84 | csharp_preserve_single_line_blocks = true
85 | csharp_preserve_single_line_statements = false
86 |
87 | # Expression-level preferences
88 | dotnet_style_object_initializer = true:suggestion
89 | dotnet_style_collection_initializer = true:suggestion
90 | dotnet_style_explicit_tuple_names = true:suggestion
91 | dotnet_style_coalesce_expression = true:suggestion
92 | dotnet_style_null_propagation = true:suggestion
93 |
94 | # Expression-bodied members
95 | csharp_style_expression_bodied_methods = false:none
96 | csharp_style_expression_bodied_constructors = false:none
97 | csharp_style_expression_bodied_operators = false:none
98 | csharp_style_expression_bodied_properties = true:none
99 | csharp_style_expression_bodied_indexers = true:none
100 | csharp_style_expression_bodied_accessors = true:none
101 |
102 | # Pattern matching
103 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
104 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
105 | csharp_style_inlined_variable_declaration = true:suggestion
106 |
107 | # Null checking preferences
108 | csharp_style_throw_expression = true:suggestion
109 | csharp_style_conditional_delegate_call = true:suggestion
110 |
111 | # Space preferences
112 | csharp_space_after_cast = false
113 | csharp_space_after_colon_in_inheritance_clause = true
114 | csharp_space_after_comma = true
115 | csharp_space_after_dot = false
116 | csharp_space_after_keywords_in_control_flow_statements = true
117 | csharp_space_after_semicolon_in_for_statement = true
118 | csharp_space_around_binary_operators = before_and_after
119 | csharp_space_around_declaration_statements = do_not_ignore
120 | csharp_space_before_colon_in_inheritance_clause = true
121 | csharp_space_before_comma = false
122 | csharp_space_before_dot = false
123 | csharp_space_before_open_square_brackets = false
124 | csharp_space_before_semicolon_in_for_statement = false
125 | csharp_space_between_empty_square_brackets = false
126 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
127 | csharp_space_between_method_call_name_and_opening_parenthesis = false
128 | csharp_space_between_method_call_parameter_list_parentheses = false
129 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
130 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
131 | csharp_space_between_method_declaration_parameter_list_parentheses = false
132 | csharp_space_between_parentheses = false
133 | csharp_space_between_square_brackets = false
134 |
135 | # Xaml files
136 | [*.{xaml,axaml}]
137 | indent_style = space
138 | indent_size = 2
139 |
140 | # Xml project files
141 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
142 | indent_size = 2
143 |
144 | # Xml build files
145 | [*.builds]
146 | indent_size = 2
147 |
148 | # Xml files
149 | [*.{xml,stylecop,resx,ruleset}]
150 | indent_size = 2
151 |
152 | # Xml config files
153 | [*.{props,targets,config,nuspec}]
154 | indent_size = 2
155 |
156 | # Shell scripts
157 | [*.sh]
158 | end_of_line = lf
159 | [*.{cmd, bat}]
160 | end_of_line = crlf
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # https://probot.github.io/apps/stale/
2 |
3 | # Number of days of inactivity before an issue becomes stale
4 | daysUntilStale: 60
5 |
6 | # Number of days of inactivity before a stale issue is closed
7 | daysUntilClose: 7
8 |
9 | # Issues with these labels will never be considered stale
10 | exemptLabels:
11 | - pinned
12 | - security
13 |
14 | # Label to use when marking an issue as stale
15 | staleLabel: stale
16 |
17 | # Comment to post when marking an issue as stale. Set to `false` to disable
18 | markComment: >
19 | This issue has been automatically marked as stale because it has not had
20 | recent activity. It will be closed if no further activity occurs. Thank you
21 | for your contributions.
22 |
23 | # Comment to post when closing a stale issue. Set to `false` to disable
24 | closeComment: true
25 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: windows-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Setup .NET Core
17 | uses: actions/setup-dotnet@v1
18 | - name: Install dependencies
19 | run: dotnet restore
20 | - name: Build
21 | run: dotnet build --configuration Release --no-restore
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
--------------------------------------------------------------------------------
/Avalonia.Controls.VariableSizedWrapGrid/Avalonia.Controls.VariableSizedWrapGrid.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net461
5 | preview
6 | enable
7 | True
8 | latest
9 | latest
10 | 11.0.0
11 |
12 |
13 |
14 | 11.0.0
15 |
16 | Wiesław Šoltés
17 | Wiesław Šoltés
18 | Copyright © Wiesław Šoltés 2023
19 | MIT
20 | https://github.com/wieslawsoltes/VariableSizedWrapGrid
21 | Provides a grid-style layout panel where each tile/cell can be variable size based on content.
22 | Avalonia.Controls.VariableSizedWrapGrid
23 | avalonia;avaloniaui;controls;variablesizedwrapgrid
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Avalonia.Controls.VariableSizedWrapGrid/VariableSizedWrapGrid.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Avalonia.Controls.Primitives;
5 | using Avalonia.Input;
6 | using Avalonia.Layout;
7 |
8 | namespace Avalonia.Controls.VariableSizedWrapGrid
9 | {
10 | public class VariableSizedWrapGrid : Panel, ILogicalScrollable
11 | {
12 | public static readonly StyledProperty HorizontalChildrenAlignmentProperty =
13 | AvaloniaProperty.Register(nameof(HorizontalChildrenAlignment), HorizontalAlignment.Left);
14 |
15 | public static readonly StyledProperty ItemHeightProperty =
16 | AvaloniaProperty.Register(nameof(ItemHeight), double.NaN);
17 |
18 | public static readonly StyledProperty ItemWidthProperty =
19 | AvaloniaProperty.Register(nameof(ItemWidth), double.NaN);
20 |
21 | public static readonly StyledProperty LatchItemSizeProperty =
22 | AvaloniaProperty.Register(nameof(LatchItemSize), true);
23 |
24 | public static readonly StyledProperty MaximumRowsOrColumnsProperty =
25 | AvaloniaProperty.Register(nameof(MaximumRowsOrColumns), -1);
26 |
27 | public static readonly StyledProperty OrientationProperty =
28 | AvaloniaProperty.Register(nameof(Orientation), Orientation.Vertical);
29 |
30 | public static readonly StyledProperty StrictItemOrderProperty =
31 | AvaloniaProperty.Register(nameof(StrictItemOrder), true);
32 |
33 | public static readonly StyledProperty VerticalChildrenAlignmentProperty =
34 | AvaloniaProperty.Register(nameof(VerticalChildrenAlignment), VerticalAlignment.Top);
35 |
36 | public static readonly AttachedProperty ColumnSpanProperty =
37 | AvaloniaProperty.RegisterAttached("ColumnSpan", 1);
38 |
39 | public static readonly AttachedProperty RowSpanProperty =
40 | AvaloniaProperty.RegisterAttached("RowSpan", 1);
41 |
42 | public static int GetColumnSpan(Control element)
43 | {
44 | return element!.GetValue(ColumnSpanProperty);
45 | }
46 |
47 | public static void SetColumnSpan(Control element, int value)
48 | {
49 | element!.SetValue(ColumnSpanProperty, value);
50 | }
51 |
52 | public static int GetRowSpan(Control element)
53 | {
54 | return element!.GetValue(RowSpanProperty);
55 | }
56 |
57 | public static void SetRowSpan(Control element, int value)
58 | {
59 | element!.SetValue(RowSpanProperty, value);
60 | }
61 |
62 | static VariableSizedWrapGrid()
63 | {
64 | AffectsArrange(
65 | HorizontalChildrenAlignmentProperty,
66 | ItemHeightProperty,
67 | ItemWidthProperty,
68 | LatchItemSizeProperty,
69 | MaximumRowsOrColumnsProperty,
70 | OrientationProperty,
71 | StrictItemOrderProperty,
72 | VerticalChildrenAlignmentProperty);
73 |
74 | AffectsMeasure(
75 | ItemHeightProperty,
76 | ItemWidthProperty,
77 | LatchItemSizeProperty,
78 | MaximumRowsOrColumnsProperty,
79 | OrientationProperty,
80 | StrictItemOrderProperty);
81 |
82 | AffectsParentArrange(ColumnSpanProperty, RowSpanProperty);
83 |
84 | AffectsParentMeasure(ColumnSpanProperty, RowSpanProperty);
85 | }
86 |
87 | private double _itemHeight;
88 | private double _itemWidth;
89 | private Size _extent = new Size();
90 | private Size _viewport = new Size();
91 | private Vector _offset = new Vector();
92 | private bool _canHorizontallyScroll = false;
93 | private bool _canVerticallyScroll = false;
94 | private EventHandler? _scrollInvalidated;
95 | private IList? _finalRects;
96 |
97 | public HorizontalAlignment HorizontalChildrenAlignment
98 | {
99 | get => GetValue(HorizontalChildrenAlignmentProperty);
100 | set => SetValue(HorizontalChildrenAlignmentProperty, value);
101 | }
102 |
103 | public double ItemHeight
104 | {
105 | get => GetValue(ItemHeightProperty);
106 | set => SetValue(ItemHeightProperty, value);
107 | }
108 |
109 | public double ItemWidth
110 | {
111 | get => GetValue(ItemWidthProperty);
112 | set => SetValue(ItemWidthProperty, value);
113 | }
114 |
115 | public bool LatchItemSize
116 | {
117 | get => GetValue(LatchItemSizeProperty);
118 | set => SetValue(LatchItemSizeProperty, value);
119 | }
120 |
121 | public int MaximumRowsOrColumns
122 | {
123 | get => GetValue(MaximumRowsOrColumnsProperty);
124 | set => SetValue(MaximumRowsOrColumnsProperty, value);
125 | }
126 |
127 | public Orientation Orientation
128 | {
129 | get => GetValue(OrientationProperty);
130 | set => SetValue(OrientationProperty, value);
131 | }
132 |
133 | public bool StrictItemOrder
134 | {
135 | get => GetValue(StrictItemOrderProperty);
136 | set => SetValue(StrictItemOrderProperty, value);
137 | }
138 |
139 | public VerticalAlignment VerticalChildrenAlignment
140 | {
141 | get => GetValue(VerticalChildrenAlignmentProperty);
142 | set => SetValue(VerticalChildrenAlignmentProperty, value);
143 | }
144 |
145 | private class PlotSorterVertical : IComparer
146 | {
147 | public int Compare(Rect x, Rect y)
148 | {
149 | if (x.Left < y.Left)
150 | return -1;
151 | if (x.Left > y.Left)
152 | return 1;
153 | if (x.Top < y.Top)
154 | return -1;
155 | if (x.Top > y.Top)
156 | return 1;
157 | return 0;
158 | }
159 | }
160 |
161 | private class PlotSorterHorizontal : IComparer
162 | {
163 | public int Compare(Rect x, Rect y)
164 | {
165 | if (x.Top < y.Top)
166 | return -1;
167 | if (x.Top > y.Top)
168 | return 1;
169 | if (x.Left < y.Left)
170 | return -1;
171 | if (x.Left > y.Left)
172 | return 1;
173 | return 0;
174 | }
175 | }
176 |
177 | private IEnumerable ReserveAcreage(Rect acreage, Rect plot)
178 | {
179 | if(acreage.Intersects(plot))
180 | {
181 | // Above?
182 | if(plot.Top < acreage.Top)
183 | {
184 | var rest = new Rect(plot.Position, new Size(plot.Width, acreage.Top - plot.Top));
185 | yield return rest;
186 | }
187 |
188 | // Below?
189 | if(plot.Bottom > acreage.Bottom)
190 | {
191 | var rest = new Rect(new Point(plot.Left, acreage.Bottom), new Size(plot.Width, plot.Bottom - acreage.Bottom));
192 | yield return rest;
193 | }
194 |
195 | // Left?
196 | if (plot.Left < acreage.Left)
197 | {
198 | var rest = new Rect(plot.Position, new Size(acreage.Left - plot.Left, plot.Height));
199 | yield return rest;
200 | }
201 |
202 | // Right?
203 | if (plot.Right > acreage.Right)
204 | {
205 | var rest = new Rect(new Point(acreage.Right, plot.Top), new Size(plot.Right - acreage.Right, plot.Height));
206 | yield return rest;
207 | }
208 | }
209 | else
210 | {
211 | yield return plot;
212 | }
213 | }
214 |
215 | private Point PlaceElement(Size requiredSize, ref List plots, double itemWidth, double itemHeight)
216 | {
217 | var location = new Point();
218 |
219 | foreach (var plot in plots)
220 | {
221 | if ((plot.Height >= requiredSize.Height) && (plot.Width >= requiredSize.Width))
222 | {
223 | var acreage = new Rect(plot.Position, requiredSize);
224 |
225 | Rect innerRect;
226 | Rect outerRect;
227 | IComparer plotSorter;
228 |
229 | if(Orientation == Orientation.Vertical)
230 | {
231 | innerRect = new Rect(0, 0, acreage.X + itemWidth, acreage.Y);
232 | outerRect = new Rect(0, 0, acreage.X, double.MaxValue);
233 | plotSorter = new PlotSorterVertical();
234 | }
235 | else
236 | {
237 | innerRect = new Rect(0, 0, acreage.X, acreage.Y + itemHeight);
238 | outerRect = new Rect(0, 0, double.MaxValue, acreage.Y);
239 | plotSorter = new PlotSorterHorizontal();
240 | }
241 |
242 | List localPlots;
243 |
244 | if (StrictItemOrder)
245 | {
246 | localPlots = plots.SelectMany(p => ReserveAcreage(acreage, p))
247 | .SelectMany(p => ReserveAcreage(outerRect, p))
248 | .SelectMany(p => ReserveAcreage(innerRect, p)).Distinct().ToList();
249 | }
250 | else
251 | {
252 | localPlots = plots.SelectMany(p => ReserveAcreage(acreage, p)).Distinct().ToList();
253 | }
254 |
255 | localPlots.RemoveAll(x => localPlots.Any(y => y.Contains(x) && !y.Equals(x)));
256 | localPlots.Sort(plotSorter);
257 | plots = localPlots;
258 |
259 | location = acreage.Position;
260 | break;
261 | }
262 | }
263 |
264 | return location;
265 | }
266 |
267 | private Rect ArrangeElement(Rect acreage, Size desiredSize, Vector offset)
268 | {
269 | var rect = acreage;
270 |
271 | // Adjust horizontal location and size for alignment
272 | switch (HorizontalChildrenAlignment)
273 | {
274 | case HorizontalAlignment.Center:
275 | rect = rect.WithX(rect.X + Math.Max(0, (acreage.Width - desiredSize.Width) / 2));
276 | rect = rect.WithWidth(desiredSize.Width);
277 | break;
278 | case HorizontalAlignment.Left:
279 | rect = rect.WithWidth(desiredSize.Width);
280 | break;
281 | case HorizontalAlignment.Right:
282 | rect = rect.WithX(rect.X + Math.Max(0, acreage.Width - desiredSize.Width));
283 | rect = rect.WithWidth(desiredSize.Width);
284 | break;
285 | case HorizontalAlignment.Stretch:
286 | default:
287 | break;
288 | }
289 |
290 | // Adjust vertical location and size for alignment
291 | switch (VerticalChildrenAlignment)
292 | {
293 | case VerticalAlignment.Bottom:
294 | rect = rect.WithY(rect.Y + Math.Max(0, acreage.Height - desiredSize.Height));
295 | rect = rect.WithHeight(desiredSize.Height);
296 | break;
297 | case VerticalAlignment.Center:
298 | rect = rect.WithY(rect.Y + Math.Max(0, (acreage.Height - desiredSize.Height) / 2));
299 | rect = rect.WithHeight(desiredSize.Height);
300 | break;
301 | case VerticalAlignment.Top:
302 | rect = rect.WithHeight(desiredSize.Height);
303 | break;
304 | case VerticalAlignment.Stretch:
305 | default:
306 | break;
307 | }
308 |
309 | // Adjust location for scrolling offset
310 | var position = rect.Position - offset;
311 | rect = new Rect(position.X, position.Y, rect.Width, rect.Height);
312 |
313 | return rect;
314 | }
315 |
316 | private void SetViewport(Size size)
317 | {
318 | if (_viewport != size)
319 | {
320 | _viewport = size;
321 | if (this is ILogicalScrollable logicalScrollable)
322 | {
323 | logicalScrollable.RaiseScrollInvalidated(EventArgs.Empty);
324 | }
325 | }
326 | }
327 |
328 | private void SetExtent(Size size)
329 | {
330 | if (_extent != size)
331 | {
332 | _extent = size;
333 | if (this is ILogicalScrollable logicalScrollable)
334 | {
335 | logicalScrollable.RaiseScrollInvalidated(EventArgs.Empty);
336 | }
337 | }
338 | }
339 |
340 | private void SetHorizontalOffset(double offset)
341 | {
342 | offset = Math.Max(0, Math.Min(offset, _extent.Width - _viewport.Width));
343 | if (offset != _offset.X)
344 | {
345 | _offset = _offset.WithX(offset);
346 | if (this is ILogicalScrollable logicalScrollable)
347 | {
348 | logicalScrollable.RaiseScrollInvalidated(EventArgs.Empty);
349 | }
350 | InvalidateArrange();
351 | }
352 | }
353 |
354 | private void SetVerticalOffset(double offset)
355 | {
356 | offset = Math.Max(0, Math.Min(offset, _extent.Height - _viewport.Height));
357 | if (offset != _offset.Y)
358 | {
359 | _offset = _offset.WithY(offset);
360 | if (this is ILogicalScrollable logicalScrollable)
361 | {
362 | logicalScrollable.RaiseScrollInvalidated(EventArgs.Empty);
363 | }
364 | InvalidateArrange();
365 | }
366 | }
367 |
368 | protected override Size MeasureOverride(Size availableSize)
369 | {
370 | var desiredSizeMin = new Size();
371 | var elementSizes = new List(Children.Count);
372 |
373 | _itemHeight = ItemHeight;
374 | _itemWidth = ItemWidth;
375 |
376 | foreach (var element in Children)
377 | {
378 | Size elementSize = LatchItemSize ?
379 | new Size(double.IsNaN(_itemWidth) ? double.MaxValue : _itemWidth * GetColumnSpan((Control)element), double.IsNaN(_itemHeight) ? double.MaxValue : _itemHeight * GetRowSpan((Control)element)) :
380 | new Size(double.IsNaN(ItemWidth) ? double.MaxValue : _itemWidth * GetColumnSpan((Control)element), double.IsNaN(ItemHeight) ? double.MaxValue : _itemHeight * GetRowSpan((Control)element));
381 |
382 | // Measure each element providing allocated plot size.
383 | element.Measure(elementSize);
384 |
385 | // Use the elements desired size as item size in the undefined dimension(s)
386 | if (double.IsNaN(_itemHeight) || (!LatchItemSize && double.IsNaN(ItemHeight)))
387 | {
388 | elementSize = elementSize.WithHeight(element.DesiredSize.Height);
389 | }
390 |
391 | if (double.IsNaN(_itemWidth) || (!LatchItemSize && double.IsNaN(ItemWidth)))
392 | {
393 | elementSize = elementSize.WithWidth(element.DesiredSize.Width);
394 | }
395 |
396 | if (double.IsNaN(_itemHeight))
397 | {
398 | _itemHeight = element.DesiredSize.Height / GetRowSpan((Control)element);
399 | }
400 |
401 | if (double.IsNaN(_itemWidth))
402 | {
403 | _itemWidth = element.DesiredSize.Width / GetColumnSpan((Control)element);
404 | }
405 |
406 | // The minimum size of the panel is equal to the largest element in each dimension.
407 | desiredSizeMin = new Size(
408 | Math.Max(desiredSizeMin.Width, elementSize.Width),
409 | Math.Max(desiredSizeMin.Height, elementSize.Height));
410 |
411 | elementSizes.Add(elementSize);
412 | }
413 |
414 | // Always use at least the available size for the panel unless infinite.
415 | var desiredSize = new Size(
416 | double.IsPositiveInfinity(availableSize.Width) ? 0 : availableSize.Width,
417 | double.IsPositiveInfinity(availableSize.Height) ? 0 : availableSize.Height);
418 |
419 | // Available plots on the panel real estate
420 | var plots = new List();
421 |
422 | // Calculate maximum size
423 | var maxSize = (MaximumRowsOrColumns > 0) ?
424 | new Size(_itemWidth * MaximumRowsOrColumns, _itemHeight * MaximumRowsOrColumns) :
425 | new Size(double.MaxValue, double.MaxValue);
426 |
427 | // Add the first plot covering the entire estate.
428 | var bigPlot = new Rect(new Point(0, 0), (Orientation == Orientation.Vertical) ?
429 | new Size(double.MaxValue, Math.Max(Math.Min(availableSize.Height, maxSize.Height), desiredSizeMin.Height)) :
430 | new Size(Math.Max(Math.Min(availableSize.Width, maxSize.Width), desiredSizeMin.Width), double.MaxValue));
431 |
432 | plots.Add(bigPlot);
433 |
434 | _finalRects = new List(Children.Count);
435 |
436 | using (var sizeEnumerator = elementSizes.GetEnumerator())
437 | {
438 | foreach (var element in Children)
439 | {
440 | sizeEnumerator.MoveNext();
441 | var elementSize = sizeEnumerator.Current;
442 |
443 | // Find a plot able to hold this element.
444 | var acreage = new Rect(
445 | PlaceElement(elementSize, ref plots, _itemWidth, _itemHeight), elementSize);
446 |
447 | _finalRects.Add(acreage);
448 |
449 | // Keep track of panel size...
450 | desiredSize = new Size(
451 | Math.Max(desiredSize.Width, acreage.Right),
452 | Math.Max(desiredSize.Height, acreage.Bottom));
453 | }
454 | }
455 |
456 | SetViewport(availableSize);
457 | SetExtent(desiredSize);
458 |
459 | return desiredSize;
460 | }
461 |
462 | protected override Size ArrangeOverride(Size finalSize)
463 | {
464 | if (_finalRects is null)
465 | {
466 | throw new NullReferenceException(nameof(_finalRects));
467 | }
468 |
469 | var actualSize = new Size(
470 | double.IsPositiveInfinity(finalSize.Width) ? 0 : finalSize.Width,
471 | double.IsPositiveInfinity(finalSize.Height) ? 0 : finalSize.Height);
472 |
473 | using (var rectEnumerator = _finalRects.GetEnumerator())
474 | {
475 | foreach (var element in Children)
476 | {
477 | rectEnumerator.MoveNext();
478 | var acreage = rectEnumerator.Current;
479 |
480 | // Keep track of panel size...
481 | actualSize = new Size(
482 | Math.Max(actualSize.Width, acreage.Right),
483 | Math.Max(actualSize.Height, acreage.Bottom));
484 |
485 | // Arrange each element using allocated plot location and size.
486 | element.Arrange(ArrangeElement(acreage, element.DesiredSize, _offset));
487 | }
488 | }
489 |
490 | // Adjust offset when the viewport size changes
491 | SetHorizontalOffset(Math.Max(0, Math.Min(_offset.X, _extent.Width - _viewport.Width)));
492 | SetVerticalOffset(Math.Max(0, Math.Min(_offset.Y, _extent.Height - _viewport.Height)));
493 |
494 | return actualSize;
495 | }
496 |
497 | Size IScrollable.Extent => _extent;
498 |
499 | Vector IScrollable.Offset
500 | {
501 | get => _offset;
502 | set => _offset = value;
503 | }
504 |
505 | Size IScrollable.Viewport => _viewport;
506 |
507 | bool ILogicalScrollable.CanHorizontallyScroll
508 | {
509 | get => _canHorizontallyScroll;
510 | set
511 | {
512 | _canHorizontallyScroll = value;
513 | InvalidateMeasure();
514 | }
515 | }
516 |
517 | bool ILogicalScrollable.CanVerticallyScroll
518 | {
519 | get => _canVerticallyScroll;
520 | set
521 | {
522 | _canVerticallyScroll = value;
523 | InvalidateMeasure();
524 | }
525 | }
526 |
527 | bool ILogicalScrollable.IsLogicalScrollEnabled => true;
528 |
529 | event EventHandler? ILogicalScrollable.ScrollInvalidated
530 | {
531 | add => _scrollInvalidated += value;
532 | remove => _scrollInvalidated -= value;
533 | }
534 |
535 | Size ILogicalScrollable.ScrollSize => new Size(16, 1);
536 |
537 | Size ILogicalScrollable.PageScrollSize => new Size(16, 16);
538 |
539 | bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect)
540 | {
541 | if (targetRect == default)
542 | {
543 | return false;
544 | }
545 |
546 | targetRect = targetRect.TransformToAABB(target.TransformToVisual(this)!.Value);
547 |
548 | Rect viewRect = new Rect(_offset.X, _offset.Y, _viewport.Width, _viewport.Height);
549 |
550 | // Horizontal
551 | if (targetRect.Right + _offset.X > viewRect.Right)
552 | {
553 | viewRect = viewRect.WithX(viewRect.X + targetRect.Right + _offset.X - viewRect.Right);
554 | }
555 | if(targetRect.Left + _offset.X < viewRect.Left)
556 | {
557 | viewRect = viewRect.WithX(viewRect.X - (viewRect.Left - (targetRect.Left + _offset.X)));
558 | }
559 |
560 | // Vertical
561 | if(targetRect.Bottom + _offset.Y > viewRect.Bottom)
562 | {
563 | viewRect = viewRect.WithY(viewRect.Y + targetRect.Bottom + _offset.Y- viewRect.Bottom);
564 | }
565 | if(targetRect.Top + _offset.Y < viewRect.Top)
566 | {
567 | viewRect = viewRect.WithY(viewRect.Y - (viewRect.Top - (targetRect.Top + _offset.Y)));
568 | }
569 |
570 | SetHorizontalOffset(viewRect.X);
571 | SetVerticalOffset(viewRect.Y);
572 |
573 | targetRect.Intersect(viewRect);
574 |
575 | return targetRect != default;
576 | }
577 |
578 | Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from)
579 | {
580 | return null;
581 | }
582 |
583 | void ILogicalScrollable.RaiseScrollInvalidated(EventArgs e)
584 | {
585 | _scrollInvalidated?.Invoke(this, e);
586 | }
587 | }
588 | }
589 |
--------------------------------------------------------------------------------
/AvaloniaSample/App.axaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/AvaloniaSample/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace AvaloniaSample
6 | {
7 | public class App : Application
8 | {
9 | public override void Initialize()
10 | {
11 | AvaloniaXamlLoader.Load(this);
12 | }
13 |
14 | public override void OnFrameworkInitializationCompleted()
15 | {
16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
17 | {
18 | desktop.MainWindow = new MainWindow();
19 | }
20 |
21 | base.OnFrameworkInitializationCompleted();
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/AvaloniaSample/AvaloniaSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net7.0
6 | preview
7 | enable
8 | False
9 | 11.0.0
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/AvaloniaSample/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
13 |
17 |
18 |
22 |
23 |
24 |
25 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/AvaloniaSample/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using Avalonia;
6 | using Avalonia.Controls;
7 | using Avalonia.Markup.Xaml;
8 | using Avalonia.Media;
9 | using AvaloniaSample.ViewModels;
10 |
11 | namespace AvaloniaSample
12 | {
13 | public partial class MainWindow : Window
14 | {
15 | public MainWindow()
16 | {
17 | InitializeComponent();
18 | this.AttachDevTools();
19 |
20 | var tilePanel = new TilePanelViewModel()
21 | {
22 | ItemHeight = 130,
23 | ItemWidth = 230,
24 | MaximumRowsOrColumns = 3,
25 | Tiles = new ObservableCollection()
26 | {
27 | new TileViewModel()
28 | {
29 | ColumnSpan = 1,
30 | RowSpan = 1,
31 | ColumnSpans = new List() { 1, 1, 1 },
32 | RowSpans = new List() { 1, 1, 1 },
33 | Background = SolidColorBrush.Parse("Red")
34 | },
35 | new TileViewModel()
36 | {
37 | ColumnSpan = 1,
38 | RowSpan = 1,
39 | ColumnSpans = new List() { 1, 1, 1 },
40 | RowSpans = new List() { 1, 1, 1 },
41 | Background = SolidColorBrush.Parse("Green")
42 | },
43 | new TileViewModel()
44 | {
45 | ColumnSpan = 1,
46 | RowSpan = 1,
47 | ColumnSpans = new List() { 1, 1, 1 },
48 | RowSpans = new List() { 1, 1, 1 },
49 | Background = SolidColorBrush.Parse("Blue")
50 | },
51 | new TileViewModel()
52 | {
53 | ColumnSpan = 1,
54 | RowSpan = 2,
55 | ColumnSpans = new List() { 1, 1, 1 },
56 | RowSpans = new List() { 1, 2, 2 },
57 | Background = SolidColorBrush.Parse("Yellow")
58 | },
59 | new TileViewModel()
60 | {
61 | ColumnSpan = 2,
62 | RowSpan = 2,
63 | ColumnSpans = new List() { 2, 2, 2 },
64 | RowSpans = new List() { 2, 2, 2 },
65 | Background = SolidColorBrush.Parse("Black")
66 | },
67 | }
68 | };
69 |
70 | double tileAspectRation = 0.5;
71 |
72 | ItemsControl.GetObservable(BoundsProperty).Subscribe(x =>
73 | {
74 | var width = x.Width;
75 | var itemWidth = width / tilePanel.MaximumRowsOrColumns;
76 | var itemHeight = itemWidth * tileAspectRation;
77 |
78 | tilePanel.ItemHeight = itemHeight;
79 | tilePanel.ItemWidth = itemWidth;
80 | });
81 |
82 | DataContext = tilePanel;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/AvaloniaSample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia;
3 | using Avalonia.ReactiveUI;
4 |
5 | namespace AvaloniaSample
6 | {
7 | class Program
8 | {
9 | public static void Main(string[] args)
10 | {
11 | try
12 | {
13 | BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
14 | }
15 | catch (Exception ex)
16 | {
17 | Console.WriteLine(ex.Message);
18 | Console.WriteLine(ex.StackTrace);
19 | }
20 | }
21 |
22 | public static AppBuilder BuildAvaloniaApp()
23 | => AppBuilder.Configure()
24 | .UsePlatformDetect()
25 | .UseReactiveUI()
26 | .LogToTrace();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/AvaloniaSample/SamplesView.axaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
--------------------------------------------------------------------------------
/AvaloniaSample/SamplesView.axaml.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 | using Avalonia.Media;
5 |
6 | namespace AvaloniaSample
7 | {
8 | public partial class SamplesView : UserControl
9 | {
10 | public SamplesView()
11 | {
12 | InitializeComponent();
13 |
14 | InitColors();
15 | }
16 |
17 | private void InitColors()
18 | {
19 | var colors = typeof(Colors)
20 | .GetProperties()
21 | .Select((c, i) => new
22 | {
23 | Color = (Color) c.GetValue(null)!,
24 | Name = c.Name,
25 | Index = i,
26 | ColSpan = ColSpan(i),
27 | RowSpan = RowSpan(i)
28 | });
29 |
30 | DataContext = colors;
31 |
32 | int RowSpan(int i)
33 | {
34 | if (i == 0)
35 | return 2;
36 | if (i == 2)
37 | return 3;
38 | if (i == 7)
39 | return 2;
40 | if (i == 14)
41 | return 2;
42 | return 1;
43 | }
44 |
45 | int ColSpan(int i)
46 | {
47 | if (i == 0)
48 | return 2;
49 | if (i == 6)
50 | return 3;
51 | if (i == 14)
52 | return 2;
53 | return 1;
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/AvaloniaSample/ViewModels/TilePanelViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using ReactiveUI;
4 |
5 | namespace AvaloniaSample.ViewModels
6 | {
7 | public class TilePanelViewModel : ViewModelBase
8 | {
9 | private double _itemHeight;
10 | private double _itemWidth;
11 | private int _maximumRowsOrColumns;
12 | private double _panelHeight;
13 | private double _panelWidth;
14 | private ObservableCollection _tiles;
15 |
16 | public double ItemHeight
17 | {
18 | get => _itemHeight;
19 | set => this.RaiseAndSetIfChanged(ref _itemHeight, value);
20 | }
21 |
22 | public double ItemWidth
23 | {
24 | get => _itemWidth;
25 | set => this.RaiseAndSetIfChanged(ref _itemWidth, value);
26 | }
27 |
28 | public int MaximumRowsOrColumns
29 | {
30 | get => _maximumRowsOrColumns;
31 | set => this.RaiseAndSetIfChanged(ref _maximumRowsOrColumns, value);
32 | }
33 |
34 | public double PanelHeight
35 | {
36 | get => _panelHeight;
37 | set => this.RaiseAndSetIfChanged(ref _panelHeight, value);
38 | }
39 |
40 | public double PanelWidth
41 | {
42 | get => _panelWidth;
43 | set => this.RaiseAndSetIfChanged(ref _panelWidth, value);
44 | }
45 |
46 | public ObservableCollection Tiles
47 | {
48 | get => _tiles;
49 | set => this.RaiseAndSetIfChanged(ref _tiles, value);
50 | }
51 |
52 | public TilePanelViewModel()
53 | {
54 | this.WhenAnyValue(x => x.PanelHeight)
55 | .Subscribe(x =>
56 | {
57 | // TODO: Update ItemHeight
58 | });
59 |
60 | this.WhenAnyValue(x => x.PanelWidth)
61 | .Subscribe(x =>
62 | {
63 | // TODO: Update ItemWidth
64 | });
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/AvaloniaSample/ViewModels/TileViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Avalonia.Media;
3 | using ReactiveUI;
4 |
5 | namespace AvaloniaSample.ViewModels
6 | {
7 | public class TileViewModel : ViewModelBase
8 | {
9 | private int _columnSpan;
10 | private int _rowSpan;
11 | private List _columnSpans;
12 | private List _rowSpans;
13 | private IBrush _background;
14 |
15 | public int ColumnSpan
16 | {
17 | get => _columnSpan;
18 | set => this.RaiseAndSetIfChanged(ref _columnSpan, value);
19 | }
20 |
21 | public int RowSpan
22 | {
23 | get => _rowSpan;
24 | set => this.RaiseAndSetIfChanged(ref _rowSpan, value);
25 | }
26 |
27 | public List ColumnSpans
28 | {
29 | get => _columnSpans;
30 | set => this.RaiseAndSetIfChanged(ref _columnSpans, value);
31 | }
32 |
33 | public List RowSpans
34 | {
35 | get => _rowSpans;
36 | set => this.RaiseAndSetIfChanged(ref _rowSpans, value);
37 | }
38 |
39 | public IBrush Background
40 | {
41 | get => _background;
42 | set => this.RaiseAndSetIfChanged(ref _background, value);
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/AvaloniaSample/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace AvaloniaSample.ViewModels
4 | {
5 | public abstract class ViewModelBase : ReactiveObject
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Wiesław Šoltés
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VariableSizedWrapGrid
2 |
3 | [](https://dev.azure.com/wieslawsoltes/GitHub/_build/latest?definitionId=80)
4 | 
5 |
6 | **VariableSizedWrapGrid** is a port of [Windows UWP](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.variablesizedwrapgrid?view=winrt-19041) version of VariableSizedWrapGrid control for [Avalonia](https://github.com/AvaloniaUI/Avalonia) based on [WPF](https://www.codeproject.com/Articles/995465/VariableSizedWrapGrid-for-WPF) implemantation.
7 |
--------------------------------------------------------------------------------
/VariableSizedWrapGrid.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.VariableSizedWrapGrid", "Avalonia.Controls.VariableSizedWrapGrid\Avalonia.Controls.VariableSizedWrapGrid.csproj", "{7C4270FB-BC0D-4A3F-B82B-AF13319EA925}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvaloniaSample", "AvaloniaSample\AvaloniaSample.csproj", "{B198ADF1-8F05-4C6D-AAE7-9051A63D8191}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApplication1", "WpfApplication1\WpfApplication1.csproj", "{C4F77D8E-EE5E-46CE-97DB-197E8C0BE0A8}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Debug|x64 = Debug|x64
16 | Debug|x86 = Debug|x86
17 | Release|Any CPU = Release|Any CPU
18 | Release|x64 = Release|x64
19 | Release|x86 = Release|x86
20 | EndGlobalSection
21 | GlobalSection(SolutionProperties) = preSolution
22 | HideSolutionNode = FALSE
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Debug|x64.ActiveCfg = Debug|Any CPU
28 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Debug|x64.Build.0 = Debug|Any CPU
29 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Debug|x86.ActiveCfg = Debug|Any CPU
30 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Debug|x86.Build.0 = Debug|Any CPU
31 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Release|x64.ActiveCfg = Release|Any CPU
34 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Release|x64.Build.0 = Release|Any CPU
35 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Release|x86.ActiveCfg = Release|Any CPU
36 | {7C4270FB-BC0D-4A3F-B82B-AF13319EA925}.Release|x86.Build.0 = Release|Any CPU
37 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Debug|x64.Build.0 = Debug|Any CPU
41 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Debug|x86.Build.0 = Debug|Any CPU
43 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Release|x64.ActiveCfg = Release|Any CPU
46 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Release|x64.Build.0 = Release|Any CPU
47 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Release|x86.ActiveCfg = Release|Any CPU
48 | {B198ADF1-8F05-4C6D-AAE7-9051A63D8191}.Release|x86.Build.0 = Release|Any CPU
49 | {C4F77D8E-EE5E-46CE-97DB-197E8C0BE0A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {C4F77D8E-EE5E-46CE-97DB-197E8C0BE0A8}.Debug|x64.ActiveCfg = Debug|Any CPU
51 | {C4F77D8E-EE5E-46CE-97DB-197E8C0BE0A8}.Debug|x86.ActiveCfg = Debug|Any CPU
52 | {C4F77D8E-EE5E-46CE-97DB-197E8C0BE0A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {C4F77D8E-EE5E-46CE-97DB-197E8C0BE0A8}.Release|x64.ActiveCfg = Release|Any CPU
54 | {C4F77D8E-EE5E-46CE-97DB-197E8C0BE0A8}.Release|x86.ActiveCfg = Release|Any CPU
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/WpfApplication1/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/WpfApplication1/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/WpfApplication1/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace WpfApplication1
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/WpfApplication1/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/WpfApplication1/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Windows;
3 | using System.Windows.Media;
4 |
5 | namespace WpfApplication1
6 | {
7 | public partial class MainWindow : Window
8 | {
9 | public MainWindow()
10 | {
11 | InitializeComponent();
12 |
13 | var _Colors = typeof(Colors)
14 | // using System.Reflection;
15 | .GetProperties()
16 | .Select((c, i) => new
17 | {
18 | Color = (Color)c.GetValue(null),
19 | Name = c.Name,
20 | Index = i,
21 | ColSpan = ColSpan(i),
22 | RowSpan = RowSpan(i)
23 | });
24 |
25 | DataContext = _Colors;
26 | }
27 |
28 | private object RowSpan(int i)
29 | {
30 | if (i == 0)
31 | return 2;
32 | if (i == 2)
33 | return 3;
34 | if (i == 7)
35 | return 2;
36 | if (i == 14)
37 | return 2;
38 | return 1;
39 | }
40 |
41 | private object ColSpan(int i)
42 | {
43 | if (i == 0)
44 | return 2;
45 | if (i == 6)
46 | return 3;
47 | if (i == 14)
48 | return 2;
49 | return 1;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/WpfApplication1/MyGridView.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 |
4 | namespace WpfApplication1
5 | {
6 | public class MyGridView : ItemsControl
7 | {
8 | protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
9 | {
10 | dynamic model = item;
11 | try
12 | {
13 | element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, model.ColSpan);
14 | element.SetValue(VariableSizedWrapGrid.RowSpanProperty, model.RowSpan);
15 | }
16 | catch
17 | {
18 | element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
19 | element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
20 | }
21 | finally
22 | {
23 | element.SetValue(VerticalContentAlignmentProperty, VerticalAlignment.Stretch);
24 | element.SetValue(HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch);
25 | base.PrepareContainerForItemOverride(element, item);
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/WpfApplication1/VariableSizedWrapGrid.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Controls.Primitives;
7 | using System.Windows.Media;
8 |
9 | namespace WpfApplication1
10 | {
11 | public class VariableSizedWrapGrid : Panel, IScrollInfo
12 | {
13 | #region HorizontalAlignment HorizontalChildrenAlignment
14 | public HorizontalAlignment HorizontalChildrenAlignment
15 | {
16 | get { return (HorizontalAlignment)GetValue(HorizontalChildrenAlignmentProperty); }
17 | set { SetValue(HorizontalChildrenAlignmentProperty, value); }
18 | }
19 |
20 | public static readonly DependencyProperty HorizontalChildrenAlignmentProperty =
21 | DependencyProperty.Register("HorizontalChildrenAlignment", typeof(HorizontalAlignment), typeof(VariableSizedWrapGrid),
22 | new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
23 | #endregion
24 |
25 | #region double ItemHeight
26 | public double ItemHeight
27 | {
28 | get { return (double)GetValue(ItemHeightProperty); }
29 | set { SetValue(ItemHeightProperty, value); }
30 | }
31 |
32 | public static readonly DependencyProperty ItemHeightProperty =
33 | DependencyProperty.Register("ItemHeight", typeof(double), typeof(VariableSizedWrapGrid),
34 | new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
35 | #endregion
36 |
37 | #region double ItemWidth
38 | public double ItemWidth
39 | {
40 | get { return (double)GetValue(ItemWidthProperty); }
41 | set { SetValue(ItemWidthProperty, value); }
42 | }
43 |
44 | public static readonly DependencyProperty ItemWidthProperty =
45 | DependencyProperty.Register("ItemWidth", typeof(double), typeof(VariableSizedWrapGrid),
46 | new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
47 | #endregion
48 |
49 | #region bool LatchIemSize
50 | public bool LatchItemSize
51 | {
52 | get { return (bool)GetValue(LatchItemSizeProperty); }
53 | set { SetValue(LatchItemSizeProperty, value); }
54 | }
55 |
56 | public static readonly DependencyProperty LatchItemSizeProperty =
57 | DependencyProperty.Register("LatchItemSize", typeof(bool), typeof(VariableSizedWrapGrid),
58 | new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
59 | #endregion
60 |
61 | #region int MaximumRowsOrColumns
62 | public int MaximumRowsOrColumns
63 | {
64 | get { return (int)GetValue(MaximumRowsOrColumnsProperty); }
65 | set { SetValue(MaximumRowsOrColumnsProperty, value); }
66 | }
67 |
68 | public static readonly DependencyProperty MaximumRowsOrColumnsProperty =
69 | DependencyProperty.Register("MaximumRowsOrColumns", typeof(int), typeof(VariableSizedWrapGrid),
70 | new FrameworkPropertyMetadata(-1, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
71 | #endregion
72 |
73 | #region Orientation Orientation
74 | public Orientation Orientation
75 | {
76 | get { return (Orientation)GetValue(OrientationProperty); }
77 | set { SetValue(OrientationProperty, value); }
78 | }
79 |
80 | public static readonly DependencyProperty OrientationProperty =
81 | DependencyProperty.Register("Orientation", typeof(Orientation), typeof(VariableSizedWrapGrid),
82 | new FrameworkPropertyMetadata(Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
83 | #endregion
84 |
85 | #region bool StrictItemOrder
86 | public bool StrictItemOrder
87 | {
88 | get { return (bool)GetValue(StrictItemOrderProperty); }
89 | set { SetValue(StrictItemOrderProperty, value); }
90 | }
91 |
92 | public static readonly DependencyProperty StrictItemOrderProperty =
93 | DependencyProperty.Register("StrictItemOrder", typeof(bool), typeof(VariableSizedWrapGrid),
94 | new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
95 | #endregion
96 |
97 | #region VerticalAlignment VerticalChildrenAlignment
98 | public VerticalAlignment VerticalChildrenAlignment
99 | {
100 | get { return (VerticalAlignment)GetValue(VerticalChildrenAlignmentProperty); }
101 | set { SetValue(VerticalChildrenAlignmentProperty, value); }
102 | }
103 |
104 | public static readonly DependencyProperty VerticalChildrenAlignmentProperty =
105 | DependencyProperty.Register("VerticalChildrenAlignment", typeof(VerticalAlignment), typeof(VariableSizedWrapGrid),
106 | new FrameworkPropertyMetadata(VerticalAlignment.Top, FrameworkPropertyMetadataOptions.AffectsArrange));
107 | #endregion
108 |
109 | #region int ColumnSpan
110 | public static int GetColumnSpan(DependencyObject obj)
111 | {
112 | return (int)obj.GetValue(ColumnSpanProperty);
113 | }
114 |
115 | public static void SetColumnSpan(DependencyObject obj, int value)
116 | {
117 | obj.SetValue(ColumnSpanProperty, value);
118 | }
119 |
120 | public static readonly DependencyProperty ColumnSpanProperty =
121 | DependencyProperty.RegisterAttached("ColumnSpan", typeof(int), typeof(VariableSizedWrapGrid),
122 | new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure));
123 | #endregion
124 |
125 | #region int RowSpan
126 | public static int GetRowSpan(DependencyObject obj)
127 | {
128 | return (int)obj.GetValue(RowSpanProperty);
129 | }
130 |
131 | public static void SetRowSpan(DependencyObject obj, int value)
132 | {
133 | obj.SetValue(RowSpanProperty, value);
134 | }
135 |
136 | public static readonly DependencyProperty RowSpanProperty =
137 | DependencyProperty.RegisterAttached("RowSpan", typeof(int), typeof(VariableSizedWrapGrid),
138 | new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure));
139 | #endregion
140 |
141 | private class PlotSorterVertical : IComparer
142 | {
143 | public int Compare(Rect x, Rect y)
144 | {
145 | if (x.Left < y.Left)
146 | return -1;
147 | if (x.Left > y.Left)
148 | return 1;
149 | if (x.Top < y.Top)
150 | return -1;
151 | if (x.Top > y.Top)
152 | return 1;
153 | return 0;
154 | }
155 | }
156 |
157 | private class PlotSorterHorizontal : IComparer
158 | {
159 | public int Compare(Rect x, Rect y)
160 | {
161 | if (x.Top < y.Top)
162 | return -1;
163 | if (x.Top > y.Top)
164 | return 1;
165 | if (x.Left < y.Left)
166 | return -1;
167 | if (x.Left > y.Left)
168 | return 1;
169 | return 0;
170 | }
171 | }
172 |
173 | private IEnumerable ReserveAcreage(Rect acreage, Rect plot)
174 | {
175 | if(acreage.IntersectsWith(plot))
176 | {
177 | // Above?
178 | if(plot.Top < acreage.Top)
179 | {
180 | var rest = new Rect(plot.Location, new Size(plot.Width, acreage.Top - plot.Top));
181 | yield return rest;
182 | }
183 |
184 | // Below?
185 | if(plot.Bottom > acreage.Bottom)
186 | {
187 | var rest = new Rect(new Point(plot.Left, acreage.Bottom), new Size(plot.Width, plot.Bottom - acreage.Bottom));
188 | yield return rest;
189 | }
190 |
191 | // Left?
192 | if (plot.Left < acreage.Left)
193 | {
194 | var rest = new Rect(plot.Location, new Size(acreage.Left - plot.Left, plot.Height));
195 | yield return rest;
196 | }
197 |
198 | // Right?
199 | if (plot.Right > acreage.Right)
200 | {
201 | var rest = new Rect(new Point(acreage.Right, plot.Top), new Size(plot.Right - acreage.Right, plot.Height));
202 | yield return rest;
203 | }
204 | }
205 | else
206 | {
207 | yield return plot;
208 | }
209 | }
210 |
211 | private Point PlaceElement(Size requiredSize, ref List plots,
212 | double itemWidth, double itemHeight)
213 | {
214 | var location = new Point();
215 |
216 | foreach (var plot in plots)
217 | {
218 | if ((plot.Height >= requiredSize.Height) && (plot.Width >= requiredSize.Width))
219 | {
220 | var acreage = new Rect(plot.Location, requiredSize);
221 |
222 | Rect innerRect;
223 | Rect outerRect;
224 | IComparer plotSorter;
225 |
226 | if(Orientation == Orientation.Vertical)
227 | {
228 | innerRect = new Rect(0, 0, acreage.X + itemWidth, acreage.Y);
229 | outerRect = new Rect(0, 0, acreage.X, double.MaxValue);
230 | plotSorter = new PlotSorterVertical();
231 | }
232 | else
233 | {
234 | innerRect = new Rect(0, 0, acreage.X, acreage.Y + itemHeight);
235 | outerRect = new Rect(0, 0, double.MaxValue, acreage.Y);
236 | plotSorter = new PlotSorterHorizontal();
237 | }
238 |
239 | List localPlots;
240 |
241 | if (StrictItemOrder)
242 | {
243 | localPlots = plots.SelectMany(p => ReserveAcreage(acreage, p))
244 | .SelectMany(p => ReserveAcreage(outerRect, p))
245 | .SelectMany(p => ReserveAcreage(innerRect, p)).Distinct().ToList();
246 | }
247 | else
248 | {
249 | localPlots = plots.SelectMany(p => ReserveAcreage(acreage, p)).Distinct().ToList();
250 | }
251 |
252 | localPlots.RemoveAll(x => localPlots.Any(y => y.Contains(x) && !y.Equals(x)));
253 | localPlots.Sort(plotSorter);
254 | plots = localPlots;
255 |
256 | location = acreage.Location;
257 | break;
258 | }
259 | }
260 |
261 | return location;
262 | }
263 |
264 | private Rect ArrangeElement(Rect acreage, Size desiredSize, Vector offset)
265 | {
266 | var rect = acreage;
267 |
268 | // Adjust horizontal location and size for alignment
269 | switch (HorizontalChildrenAlignment)
270 | {
271 | case HorizontalAlignment.Center:
272 | rect.X = rect.X + Math.Max(0, (acreage.Width - desiredSize.Width) / 2);
273 | rect.Width = desiredSize.Width;
274 | break;
275 | case HorizontalAlignment.Left:
276 | rect.Width = desiredSize.Width;
277 | break;
278 | case HorizontalAlignment.Right:
279 | rect.X = rect.X + Math.Max(0, acreage.Width - desiredSize.Width);
280 | rect.Width = desiredSize.Width;
281 | break;
282 | case HorizontalAlignment.Stretch:
283 | default:
284 | break;
285 | }
286 |
287 | // Adjust vertical location and size for alignment
288 | switch (VerticalChildrenAlignment)
289 | {
290 | case VerticalAlignment.Bottom:
291 | rect.Y = rect.Y + Math.Max(0, acreage.Height - desiredSize.Height);
292 | rect.Height = desiredSize.Height;
293 | break;
294 | case VerticalAlignment.Center:
295 | rect.Y = rect.Y + Math.Max(0, (acreage.Height - desiredSize.Height) / 2);
296 | rect.Height = desiredSize.Height;
297 | break;
298 | case VerticalAlignment.Top:
299 | rect.Height = desiredSize.Height;
300 | break;
301 | case VerticalAlignment.Stretch:
302 | default:
303 | break;
304 | }
305 |
306 | // Adjust location for scrolling offset
307 | rect.Location = rect.Location - offset;
308 |
309 | return rect;
310 | }
311 |
312 | double _itemHeight;
313 | double _itemWidth;
314 |
315 | private ScrollViewer _owner;
316 | private Size _extent = new Size();
317 | private Size _viewport = new Size();
318 | private Vector _offset = new Vector();
319 |
320 | private void SetViewport(Size size)
321 | {
322 | if (_viewport != size)
323 | {
324 | _viewport = size;
325 | if (_owner != null)
326 | {
327 | _owner.InvalidateScrollInfo();
328 | }
329 | }
330 | }
331 |
332 | private void SetExtent(Size size)
333 | {
334 | if (_extent != size)
335 | {
336 | _extent = size;
337 | if (_owner != null)
338 | {
339 | _owner.InvalidateScrollInfo();
340 | }
341 | }
342 | }
343 |
344 | private IList _finalRects;
345 |
346 | protected override Size MeasureOverride(Size availableSize)
347 | {
348 | var desiredSizeMin = new Size();
349 | var elementSizes = new List(InternalChildren.Count);
350 |
351 | _itemHeight = ItemHeight;
352 | _itemWidth = ItemWidth;
353 |
354 | foreach (UIElement element in InternalChildren)
355 | {
356 | Size elementSize = LatchItemSize ?
357 | new Size(double.IsNaN(_itemWidth) ? double.MaxValue : _itemWidth * GetColumnSpan(element), double.IsNaN(_itemHeight) ? double.MaxValue : _itemHeight * GetRowSpan(element)) :
358 | new Size(double.IsNaN(ItemWidth) ? double.MaxValue : _itemWidth * GetColumnSpan(element), double.IsNaN(ItemHeight) ? double.MaxValue : _itemHeight * GetRowSpan(element));
359 |
360 | // Measure each element providing allocated plot size.
361 | element.Measure(elementSize);
362 |
363 | // Use the elements desired size as item size in the undefined dimension(s)
364 | if (double.IsNaN(_itemHeight) || (!LatchItemSize && double.IsNaN(ItemHeight)))
365 | {
366 | elementSize.Height = element.DesiredSize.Height;
367 | }
368 |
369 | if (double.IsNaN(_itemWidth) || (!LatchItemSize && double.IsNaN(ItemWidth)))
370 | {
371 | elementSize.Width = element.DesiredSize.Width;
372 | }
373 |
374 | if (double.IsNaN(_itemHeight))
375 | {
376 | _itemHeight = element.DesiredSize.Height / GetRowSpan(element);
377 | }
378 |
379 | if (double.IsNaN(_itemWidth))
380 | {
381 | _itemWidth = element.DesiredSize.Width / GetColumnSpan(element);
382 | }
383 |
384 | // The minimum size of the panel is equal to the largest element in each dimension.
385 | desiredSizeMin.Height = Math.Max(desiredSizeMin.Height, elementSize.Height);
386 | desiredSizeMin.Width = Math.Max(desiredSizeMin.Width, elementSize.Width);
387 |
388 | elementSizes.Add(elementSize);
389 | }
390 |
391 | // Always use at least the available size for the panel unless infinite.
392 | var desiredSize = new Size();
393 | desiredSize.Height = double.IsPositiveInfinity(availableSize.Height) ? 0 : availableSize.Height;
394 | desiredSize.Width = double.IsPositiveInfinity(availableSize.Width) ? 0 : availableSize.Width;
395 |
396 | // Available plots on the panel real estate
397 | var plots = new List();
398 |
399 | // Calculate maximum size
400 | var maxSize = (MaximumRowsOrColumns > 0) ?
401 | new Size(_itemWidth * MaximumRowsOrColumns, _itemHeight * MaximumRowsOrColumns) :
402 | new Size(double.MaxValue, double.MaxValue);
403 |
404 | // Add the first plot covering the entire estate.
405 | var bigPlot = new Rect(new Point(0, 0), (Orientation == Orientation.Vertical) ?
406 | new Size(double.MaxValue, Math.Max(Math.Min(availableSize.Height, maxSize.Height), desiredSizeMin.Height)) :
407 | new Size(Math.Max(Math.Min(availableSize.Width, maxSize.Width), desiredSizeMin.Width), double.MaxValue));
408 |
409 | plots.Add(bigPlot);
410 |
411 | _finalRects = new List(InternalChildren.Count);
412 |
413 | using (var sizeEnumerator = elementSizes.GetEnumerator())
414 | {
415 | foreach (UIElement element in InternalChildren)
416 | {
417 | sizeEnumerator.MoveNext();
418 | var elementSize = sizeEnumerator.Current;
419 |
420 | // Find a plot able to hold this element.
421 | var acreage = new Rect(
422 | PlaceElement(elementSize, ref plots, _itemWidth, _itemHeight), elementSize);
423 |
424 | _finalRects.Add(acreage);
425 |
426 | // Keep track of panel size...
427 | desiredSize.Height = Math.Max(desiredSize.Height, acreage.Bottom);
428 | desiredSize.Width = Math.Max(desiredSize.Width, acreage.Right);
429 | }
430 | }
431 |
432 | SetViewport(availableSize);
433 | SetExtent(desiredSize);
434 |
435 | return desiredSize;
436 | }
437 |
438 | protected override Size ArrangeOverride(Size finalSize)
439 | {
440 | var actualSize = new Size();
441 | actualSize.Height = double.IsPositiveInfinity(finalSize.Height) ? 0 : finalSize.Height;
442 | actualSize.Width = double.IsPositiveInfinity(finalSize.Width) ? 0 : finalSize.Width;
443 |
444 | using (var rectEnumerator = _finalRects.GetEnumerator())
445 | {
446 | foreach (UIElement element in InternalChildren)
447 | {
448 | rectEnumerator.MoveNext();
449 | var acreage = rectEnumerator.Current;
450 |
451 | // Keep track of panel size...
452 | actualSize.Height = Math.Max(actualSize.Height, acreage.Bottom);
453 | actualSize.Width = Math.Max(actualSize.Width, acreage.Right);
454 |
455 | // Arrange each element using allocated plot location and size.
456 | element.Arrange(ArrangeElement(acreage, element.DesiredSize, _offset));
457 | }
458 | }
459 |
460 | // Adjust offset when the viewport size changes
461 | SetHorizontalOffset(Math.Max(0, Math.Min(HorizontalOffset, ExtentWidth - ViewportWidth)));
462 | SetVerticalOffset(Math.Max(0, Math.Min(VerticalOffset, ExtentHeight - ViewportHeight)));
463 |
464 | return actualSize;
465 | }
466 |
467 | #region IScrollInfo
468 | // This property is not intended for use in your code. It is exposed publicly to fulfill an interface contract (IScrollInfo). Setting this property has no effect.
469 | public bool CanVerticallyScroll
470 | {
471 | get { return false; }
472 | set { }
473 | }
474 |
475 | // This property is not intended for use in your code. It is exposed publicly to fulfill an interface contract (IScrollInfo). Setting this property has no effect.
476 | public bool CanHorizontallyScroll
477 | {
478 | get { return false; }
479 | set { }
480 | }
481 |
482 | public double ExtentWidth
483 | {
484 | get { return _extent.Width; }
485 | }
486 |
487 | public double ExtentHeight
488 | {
489 | get { return _extent.Height; }
490 | }
491 |
492 | public double ViewportWidth
493 | {
494 | get { return _viewport.Width; }
495 | }
496 |
497 | public double ViewportHeight
498 | {
499 | get { return _viewport.Height; }
500 | }
501 |
502 | public double HorizontalOffset
503 | {
504 | get { return _offset.X; }
505 | }
506 |
507 | public double VerticalOffset
508 | {
509 | get { return _offset.Y; }
510 | }
511 |
512 | public ScrollViewer ScrollOwner
513 | {
514 | get { return _owner; }
515 | set { _owner = value; }
516 | }
517 |
518 | public void LineUp()
519 | {
520 | SetVerticalOffset(VerticalOffset - _itemHeight);
521 | }
522 |
523 | public void LineDown()
524 | {
525 | SetVerticalOffset(VerticalOffset + _itemHeight);
526 | }
527 |
528 | public void LineLeft()
529 | {
530 | SetHorizontalOffset(HorizontalOffset - _itemWidth);
531 | }
532 |
533 | public void LineRight()
534 | {
535 | SetHorizontalOffset(HorizontalOffset + _itemWidth);
536 | }
537 |
538 | public void PageUp()
539 | {
540 | SetVerticalOffset(VerticalOffset - Math.Max(_itemHeight, Math.Max(0, _viewport.Height - _itemHeight)));
541 | }
542 |
543 | public void PageDown()
544 | {
545 | SetVerticalOffset(VerticalOffset + Math.Max(_itemHeight, Math.Max(0, _viewport.Height - _itemHeight)));
546 | }
547 |
548 | public void PageLeft()
549 | {
550 | SetHorizontalOffset(HorizontalOffset - Math.Max(_itemWidth, Math.Max(0, _viewport.Width - _itemWidth)));
551 | }
552 |
553 | public void PageRight()
554 | {
555 | SetHorizontalOffset(HorizontalOffset + Math.Max(_itemWidth, Math.Max(0, _viewport.Width - _itemWidth)));
556 | }
557 |
558 | public void MouseWheelUp()
559 | {
560 | LineUp();
561 | }
562 |
563 | public void MouseWheelDown()
564 | {
565 | LineDown();
566 | }
567 |
568 | public void MouseWheelLeft()
569 | {
570 | LineLeft();
571 | }
572 |
573 | public void MouseWheelRight()
574 | {
575 | LineRight();
576 | }
577 |
578 | public void SetHorizontalOffset(double offset)
579 | {
580 | offset = Math.Max(0, Math.Min(offset, ExtentWidth - ViewportWidth));
581 | if (offset != _offset.X)
582 | {
583 | _offset.X = offset;
584 | if (_owner != null)
585 | {
586 | _owner.InvalidateScrollInfo();
587 | }
588 | InvalidateArrange();
589 | }
590 | }
591 |
592 | public void SetVerticalOffset(double offset)
593 | {
594 | offset = Math.Max(0, Math.Min(offset, ExtentHeight - ViewportHeight));
595 | if (offset != _offset.Y)
596 | {
597 | _offset.Y = offset;
598 | if (_owner != null)
599 | {
600 | _owner.InvalidateScrollInfo();
601 | }
602 | InvalidateArrange();
603 | }
604 | }
605 |
606 | public Rect MakeVisible(Visual visual, Rect rectangle)
607 | {
608 | if (rectangle.IsEmpty || visual == null || visual == this || !IsAncestorOf(visual))
609 | {
610 | return Rect.Empty;
611 | }
612 |
613 | rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);
614 |
615 | Rect viewRect = new Rect(HorizontalOffset, VerticalOffset, ViewportWidth, ViewportHeight);
616 |
617 | // Horizontal
618 | if (rectangle.Right + HorizontalOffset > viewRect.Right)
619 | {
620 | viewRect.X = viewRect.X + rectangle.Right + HorizontalOffset - viewRect.Right;
621 | }
622 | if(rectangle.Left + HorizontalOffset < viewRect.Left)
623 | {
624 | viewRect.X = viewRect.X - (viewRect.Left - (rectangle.Left + HorizontalOffset));
625 | }
626 |
627 | // Vertical
628 | if(rectangle.Bottom + VerticalOffset > viewRect.Bottom)
629 | {
630 | viewRect.Y = viewRect.Y + rectangle.Bottom + VerticalOffset- viewRect.Bottom;
631 | }
632 | if(rectangle.Top + VerticalOffset < viewRect.Top)
633 | {
634 | viewRect.Y = viewRect.Y - (viewRect.Top - (rectangle.Top + VerticalOffset));
635 | }
636 |
637 | SetHorizontalOffset(viewRect.X);
638 | SetVerticalOffset(viewRect.Y);
639 |
640 | rectangle.Intersect(viewRect);
641 |
642 | return rectangle;
643 | }
644 | #endregion
645 | }
646 | }
647 |
--------------------------------------------------------------------------------
/WpfApplication1/WpfApplication1.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net5.0-windows
6 | true
7 | False
8 | disable
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "7.0.100",
4 | "rollForward": "latestMinor",
5 | "allowPrerelease": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------