UniformSpacingPanel.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. using System;
  2. using System.ComponentModel;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using HandyControl.Data;
  6. using HandyControl.Expression.Drawing;
  7. namespace HandyControl.Controls;
  8. public class UniformSpacingPanel : Panel
  9. {
  10. private Orientation _orientation;
  11. public UniformSpacingPanel()
  12. {
  13. _orientation = Orientation.Horizontal;
  14. }
  15. public static readonly DependencyProperty OrientationProperty =
  16. StackPanel.OrientationProperty.AddOwner(typeof(UniformSpacingPanel),
  17. new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure, OnOrientationChanged));
  18. private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  19. {
  20. var p = (UniformSpacingPanel) d;
  21. p._orientation = (Orientation) e.NewValue;
  22. }
  23. public Orientation Orientation
  24. {
  25. get => (Orientation) GetValue(OrientationProperty);
  26. set => SetValue(OrientationProperty, value);
  27. }
  28. public static readonly DependencyProperty ChildWrappingProperty = DependencyProperty.Register(
  29. nameof(ChildWrapping), typeof(VisualWrapping), typeof(UniformSpacingPanel),
  30. new FrameworkPropertyMetadata(default(VisualWrapping), FrameworkPropertyMetadataOptions.AffectsMeasure));
  31. public VisualWrapping ChildWrapping
  32. {
  33. get => (VisualWrapping) GetValue(ChildWrappingProperty);
  34. set => SetValue(ChildWrappingProperty, value);
  35. }
  36. public static readonly DependencyProperty SpacingProperty = DependencyProperty.Register(
  37. nameof(Spacing), typeof(double), typeof(UniformSpacingPanel),
  38. new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure), IsSpacingValid);
  39. public double Spacing
  40. {
  41. get => (double) GetValue(SpacingProperty);
  42. set => SetValue(SpacingProperty, value);
  43. }
  44. public static readonly DependencyProperty HorizontalSpacingProperty = DependencyProperty.Register(
  45. nameof(HorizontalSpacing), typeof(double), typeof(UniformSpacingPanel),
  46. new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure), IsSpacingValid);
  47. public double HorizontalSpacing
  48. {
  49. get => (double) GetValue(HorizontalSpacingProperty);
  50. set => SetValue(HorizontalSpacingProperty, value);
  51. }
  52. public static readonly DependencyProperty VerticalSpacingProperty = DependencyProperty.Register(
  53. nameof(VerticalSpacing), typeof(double), typeof(UniformSpacingPanel),
  54. new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure), IsSpacingValid);
  55. public double VerticalSpacing
  56. {
  57. get => (double) GetValue(VerticalSpacingProperty);
  58. set => SetValue(VerticalSpacingProperty, value);
  59. }
  60. public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
  61. nameof(ItemWidth), typeof(double), typeof(UniformSpacingPanel),
  62. new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
  63. IsWidthHeightValid);
  64. [TypeConverter(typeof(LengthConverter))]
  65. public double ItemWidth
  66. {
  67. get => (double) GetValue(ItemWidthProperty);
  68. set => SetValue(ItemWidthProperty, value);
  69. }
  70. public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
  71. nameof(ItemHeight), typeof(double), typeof(UniformSpacingPanel),
  72. new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
  73. IsWidthHeightValid);
  74. [TypeConverter(typeof(LengthConverter))]
  75. public double ItemHeight
  76. {
  77. get => (double) GetValue(ItemHeightProperty);
  78. set => SetValue(ItemHeightProperty, value);
  79. }
  80. public static readonly DependencyProperty ItemHorizontalAlignmentProperty = DependencyProperty.Register(
  81. nameof(ItemHorizontalAlignment), typeof(HorizontalAlignment?), typeof(UniformSpacingPanel),
  82. new FrameworkPropertyMetadata(HorizontalAlignment.Stretch, FrameworkPropertyMetadataOptions.AffectsMeasure));
  83. public HorizontalAlignment? ItemHorizontalAlignment
  84. {
  85. get => (HorizontalAlignment?) GetValue(ItemHorizontalAlignmentProperty);
  86. set => SetValue(ItemHorizontalAlignmentProperty, value);
  87. }
  88. public static readonly DependencyProperty ItemVerticalAlignmentProperty = DependencyProperty.Register(
  89. nameof(ItemVerticalAlignment), typeof(VerticalAlignment?), typeof(UniformSpacingPanel),
  90. new FrameworkPropertyMetadata(VerticalAlignment.Stretch, FrameworkPropertyMetadataOptions.AffectsMeasure));
  91. public VerticalAlignment? ItemVerticalAlignment
  92. {
  93. get => (VerticalAlignment?) GetValue(ItemVerticalAlignmentProperty);
  94. set => SetValue(ItemVerticalAlignmentProperty, value);
  95. }
  96. private static bool IsWidthHeightValid(object value)
  97. {
  98. var v = (double) value;
  99. return double.IsNaN(v) || v >= 0.0d && !double.IsPositiveInfinity(v);
  100. }
  101. private static bool IsSpacingValid(object value)
  102. {
  103. if (value is double spacing)
  104. {
  105. return double.IsNaN(spacing) || spacing > 0;
  106. }
  107. return false;
  108. }
  109. private void ArrangeWrapLine(double v, double lineV, int start, int end, bool useItemU, double itemU, double spacing)
  110. {
  111. double u = 0;
  112. var isHorizontal = _orientation == Orientation.Horizontal;
  113. var children = InternalChildren;
  114. for (var i = start; i < end; i++)
  115. {
  116. var child = children[i];
  117. if (child == null) continue;
  118. var childSize = new PanelUvSize(_orientation, child.DesiredSize);
  119. var layoutSlotU = useItemU ? itemU : childSize.U;
  120. child.Arrange(isHorizontal ? new Rect(u, v, layoutSlotU, lineV) : new Rect(v, u, lineV, layoutSlotU));
  121. if (layoutSlotU > 0)
  122. {
  123. u += layoutSlotU + spacing;
  124. }
  125. }
  126. }
  127. private void ArrangeLine(double lineV, bool useItemU, double itemU, double spacing)
  128. {
  129. double u = 0;
  130. var isHorizontal = _orientation == Orientation.Horizontal;
  131. var children = InternalChildren;
  132. for (var i = 0; i < children.Count; i++)
  133. {
  134. var child = children[i];
  135. if (child == null) continue;
  136. var childSize = new PanelUvSize(_orientation, child.DesiredSize);
  137. var layoutSlotU = useItemU ? itemU : childSize.U;
  138. child.Arrange(isHorizontal ? new Rect(u, 0, layoutSlotU, lineV) : new Rect(0, u, lineV, layoutSlotU));
  139. if (layoutSlotU > 0)
  140. {
  141. u += layoutSlotU + spacing;
  142. }
  143. }
  144. }
  145. protected override Size MeasureOverride(Size constraint)
  146. {
  147. var curLineSize = new PanelUvSize(_orientation);
  148. var panelSize = new PanelUvSize(_orientation);
  149. var uvConstraint = new PanelUvSize(_orientation, constraint);
  150. var itemWidth = ItemWidth;
  151. var itemHeight = ItemHeight;
  152. var itemWidthSet = !double.IsNaN(itemWidth);
  153. var itemHeightSet = !double.IsNaN(itemHeight);
  154. var childWrapping = ChildWrapping;
  155. var itemHorizontalAlignment = ItemHorizontalAlignment;
  156. var itemVerticalAlignment = ItemVerticalAlignment;
  157. var itemHorizontalAlignmentSet = itemHorizontalAlignment != null;
  158. var itemVerticalAlignmentSet = ItemVerticalAlignment != null;
  159. var spacingSize = GetSpacingSize();
  160. var childConstraint = new Size(
  161. itemWidthSet ? itemWidth : constraint.Width,
  162. itemHeightSet ? itemHeight : constraint.Height);
  163. var children = InternalChildren;
  164. var isFirst = true;
  165. if (childWrapping == VisualWrapping.Wrap)
  166. {
  167. for (int i = 0, count = children.Count; i < count; i++)
  168. {
  169. var child = children[i];
  170. if (child == null) continue;
  171. if (itemHorizontalAlignmentSet)
  172. {
  173. child.SetCurrentValue(HorizontalAlignmentProperty, itemHorizontalAlignment);
  174. }
  175. if (itemVerticalAlignmentSet)
  176. {
  177. child.SetCurrentValue(VerticalAlignmentProperty, itemVerticalAlignment);
  178. }
  179. child.Measure(childConstraint);
  180. var sz = new PanelUvSize(
  181. _orientation,
  182. itemWidthSet ? itemWidth : child.DesiredSize.Width,
  183. itemHeightSet ? itemHeight : child.DesiredSize.Height);
  184. if (MathHelper.GreaterThan(curLineSize.U + sz.U + spacingSize.U, uvConstraint.U))
  185. {
  186. panelSize.U = Math.Max(curLineSize.U, panelSize.U);
  187. panelSize.V += curLineSize.V + spacingSize.V;
  188. curLineSize = sz;
  189. if (MathHelper.GreaterThan(sz.U, uvConstraint.U))
  190. {
  191. panelSize.U = Math.Max(sz.U, panelSize.U);
  192. panelSize.V += sz.V + spacingSize.V;
  193. curLineSize = new PanelUvSize(_orientation);
  194. }
  195. }
  196. else
  197. {
  198. curLineSize.U += isFirst ? sz.U : sz.U + spacingSize.U;
  199. curLineSize.V = Math.Max(sz.V, curLineSize.V);
  200. isFirst = false;
  201. }
  202. }
  203. }
  204. else
  205. {
  206. var layoutSlotSize = constraint;
  207. if (_orientation == Orientation.Horizontal)
  208. {
  209. layoutSlotSize.Width = double.PositiveInfinity;
  210. }
  211. else
  212. {
  213. layoutSlotSize.Height = double.PositiveInfinity;
  214. }
  215. for (int i = 0, count = children.Count; i < count; ++i)
  216. {
  217. var child = children[i];
  218. if (child == null) continue;
  219. if (itemHorizontalAlignmentSet)
  220. {
  221. child.SetCurrentValue(HorizontalAlignmentProperty, itemHorizontalAlignment);
  222. }
  223. if (itemVerticalAlignmentSet)
  224. {
  225. child.SetCurrentValue(VerticalAlignmentProperty, itemVerticalAlignment);
  226. }
  227. child.Measure(layoutSlotSize);
  228. var sz = new PanelUvSize(
  229. _orientation,
  230. itemWidthSet ? itemWidth : child.DesiredSize.Width,
  231. itemHeightSet ? itemHeight : child.DesiredSize.Height);
  232. curLineSize.U += isFirst ? sz.U : sz.U + spacingSize.U;
  233. curLineSize.V = Math.Max(sz.V, curLineSize.V);
  234. isFirst = false;
  235. }
  236. }
  237. panelSize.U = Math.Max(curLineSize.U, panelSize.U);
  238. panelSize.V += curLineSize.V;
  239. return new Size(panelSize.Width, panelSize.Height);
  240. }
  241. private PanelUvSize GetSpacingSize()
  242. {
  243. var spacing = Spacing;
  244. if (!double.IsNaN(spacing))
  245. {
  246. return new PanelUvSize(_orientation, spacing, spacing);
  247. }
  248. var horizontalSpacing = HorizontalSpacing;
  249. if (double.IsNaN(horizontalSpacing))
  250. {
  251. horizontalSpacing = 0;
  252. }
  253. var verticalSpacing = VerticalSpacing;
  254. if (double.IsNaN(verticalSpacing))
  255. {
  256. verticalSpacing = 0;
  257. }
  258. return new PanelUvSize(_orientation, horizontalSpacing, verticalSpacing);
  259. }
  260. protected override Size ArrangeOverride(Size finalSize)
  261. {
  262. var firstInLine = 0;
  263. var itemWidth = ItemWidth;
  264. var itemHeight = ItemHeight;
  265. double accumulatedV = 0;
  266. var itemU = _orientation == Orientation.Horizontal ? itemWidth : itemHeight;
  267. var curLineSize = new PanelUvSize(_orientation);
  268. var uvFinalSize = new PanelUvSize(_orientation, finalSize);
  269. var itemWidthSet = !double.IsNaN(itemWidth);
  270. var itemHeightSet = !double.IsNaN(itemHeight);
  271. var useItemU = _orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet;
  272. var childWrapping = ChildWrapping;
  273. var spacingSize = GetSpacingSize();
  274. var children = InternalChildren;
  275. var isFirst = true;
  276. if (childWrapping == VisualWrapping.Wrap)
  277. {
  278. for (int i = 0, count = children.Count; i < count; i++)
  279. {
  280. var child = children[i];
  281. if (child == null) continue;
  282. var sz = new PanelUvSize(
  283. _orientation,
  284. itemWidthSet ? itemWidth : child.DesiredSize.Width,
  285. itemHeightSet ? itemHeight : child.DesiredSize.Height);
  286. if (MathHelper.GreaterThan(curLineSize.U + (isFirst ? sz.U : sz.U + spacingSize.U), uvFinalSize.U))
  287. {
  288. ArrangeWrapLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU, spacingSize.U);
  289. accumulatedV += curLineSize.V + spacingSize.V;
  290. curLineSize = sz;
  291. firstInLine = i;
  292. }
  293. else
  294. {
  295. curLineSize.U += isFirst ? sz.U : sz.U + spacingSize.U;
  296. curLineSize.V = Math.Max(sz.V, curLineSize.V);
  297. }
  298. isFirst = false;
  299. }
  300. if (firstInLine < children.Count)
  301. {
  302. ArrangeWrapLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU, spacingSize.U);
  303. }
  304. }
  305. else
  306. {
  307. ArrangeLine(uvFinalSize.V, useItemU, itemU, spacingSize.U);
  308. }
  309. return finalSize;
  310. }
  311. }