ViewLayout.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text;
  8. namespace Terminal.Gui {
  9. /// <summary>
  10. /// Determines the LayoutStyle for a <see cref="View"/>, if Absolute, during <see cref="View.LayoutSubviews"/>, the
  11. /// value from the <see cref="View.Frame"/> will be used, if the value is Computed, then <see cref="View.Frame"/>
  12. /// will be updated from the X, Y <see cref="Pos"/> objects and the Width and Height <see cref="Dim"/> objects.
  13. /// </summary>
  14. public enum LayoutStyle {
  15. /// <summary>
  16. /// The position and size of the view are based <see cref="View.Frame"/>.
  17. /// </summary>
  18. Absolute,
  19. /// <summary>
  20. /// The position and size of the view will be computed based on
  21. /// <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/>. <see cref="View.Frame"/> will
  22. /// provide the absolute computed values.
  23. /// </summary>
  24. Computed
  25. }
  26. public partial class View {
  27. // The frame for the object. Superview relative.
  28. Rect _frame;
  29. /// <summary>
  30. /// Gets or sets the frame for the view. The frame is relative to the view's container (<see cref="SuperView"/>).
  31. /// </summary>
  32. /// <value>The frame.</value>
  33. /// <remarks>
  34. /// <para>
  35. /// Change the Frame when using the <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout style to move or resize views.
  36. /// </para>
  37. /// <para>
  38. /// Altering the Frame of a view will trigger the redrawing of the
  39. /// view as well as the redrawing of the affected regions of the <see cref="SuperView"/>.
  40. /// </para>
  41. /// </remarks>
  42. public virtual Rect Frame {
  43. get => _frame;
  44. set {
  45. _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
  46. if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
  47. LayoutFrames ();
  48. TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
  49. SetNeedsLayout ();
  50. SetNeedsDisplay ();
  51. }
  52. }
  53. }
  54. /// <summary>
  55. /// The Thickness that separates a View from other SubViews of the same SuperView.
  56. /// The Margin is not part of the View's content and is not clipped by the View's Clip Area.
  57. /// </summary>
  58. public Frame Margin { get; private set; }
  59. /// <summary>
  60. /// Thickness where a visual border (drawn using line-drawing glyphs) and the Title are drawn.
  61. /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and
  62. /// title will take up the first row and the second row will be filled with spaces.
  63. /// The Border is not part of the View's content and is not clipped by the View's `ClipArea`.
  64. /// </summary>
  65. /// <remarks>
  66. /// <see cref="BorderStyle"/> provides a simple helper for turning a simple border frame on or off.
  67. /// </remarks>
  68. public Frame Border { get; private set; }
  69. /// <summary>
  70. /// Gets or sets whether the view has a one row/col thick border.
  71. /// </summary>
  72. /// <remarks>
  73. /// <para>
  74. /// This is a helper for manipulating the view's <see cref="Border"/>. Setting this property to any value other than
  75. /// <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Frame.Thickness"/>
  76. /// to `1` and <see cref="BorderStyle"/> to the value.
  77. /// </para>
  78. /// <para>
  79. /// Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Frame.Thickness"/>
  80. /// to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>.
  81. /// </para>
  82. /// <para>
  83. /// For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.
  84. /// </para>
  85. /// </remarks>
  86. public LineStyle BorderStyle {
  87. get {
  88. return Border?.BorderStyle ?? LineStyle.None;
  89. }
  90. set {
  91. if (Border == null) {
  92. throw new InvalidOperationException ("Border is null; this is likely a bug.");
  93. }
  94. if (value != LineStyle.None) {
  95. Border.Thickness = new Thickness (1);
  96. } else {
  97. Border.Thickness = new Thickness (0);
  98. }
  99. Border.BorderStyle = value;
  100. LayoutFrames ();
  101. SetNeedsLayout ();
  102. }
  103. }
  104. /// <summary>
  105. /// Means the Thickness inside of an element that offsets the `Content` from the Border.
  106. /// Padding is `{0, 0, 0, 0}` by default. Padding is not part of the View's content and is not clipped by the View's `ClipArea`.
  107. /// </summary>
  108. /// <remarks>
  109. /// (NOTE: in v1 `Padding` is OUTSIDE of the `Border`).
  110. /// </remarks>
  111. public Frame Padding { get; private set; }
  112. /// <summary>
  113. /// Helper to get the total thickness of the <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>.
  114. /// </summary>
  115. /// <returns>A thickness that describes the sum of the Frames' thicknesses.</returns>
  116. public Thickness GetFramesThickness ()
  117. {
  118. var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
  119. var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
  120. var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
  121. var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
  122. return new Thickness (left, top, right, bottom);
  123. }
  124. /// <summary>
  125. /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
  126. /// <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
  127. /// </summary>
  128. public Point GetBoundsOffset () => new Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
  129. /// <summary>
  130. /// Creates the view's <see cref="Frame"/> objects. This internal method is overridden by Frame to do nothing
  131. /// to prevent recursion during View construction.
  132. /// </summary>
  133. internal virtual void CreateFrames ()
  134. {
  135. void ThicknessChangedHandler (object sender, EventArgs e)
  136. {
  137. LayoutFrames ();
  138. SetNeedsLayout ();
  139. SetNeedsDisplay ();
  140. }
  141. if (Margin != null) {
  142. Margin.ThicknessChanged -= ThicknessChangedHandler;
  143. Margin.Dispose ();
  144. }
  145. Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) };
  146. Margin.ThicknessChanged += ThicknessChangedHandler;
  147. Margin.Parent = this;
  148. if (Border != null) {
  149. Border.ThicknessChanged -= ThicknessChangedHandler;
  150. Border.Dispose ();
  151. }
  152. Border = new Frame () { Id = "Border", Thickness = new Thickness (0) };
  153. Border.ThicknessChanged += ThicknessChangedHandler;
  154. Border.Parent = this;
  155. // TODO: Create View.AddAdornment
  156. if (Padding != null) {
  157. Padding.ThicknessChanged -= ThicknessChangedHandler;
  158. Padding.Dispose ();
  159. }
  160. Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) };
  161. Padding.ThicknessChanged += ThicknessChangedHandler;
  162. Padding.Parent = this;
  163. }
  164. LayoutStyle _layoutStyle;
  165. /// <summary>
  166. /// Controls how the View's <see cref="Frame"/> is computed during the LayoutSubviews method, if the style is set to
  167. /// <see cref="Terminal.Gui.LayoutStyle.Absolute"/>,
  168. /// LayoutSubviews does not change the <see cref="Frame"/>. If the style is <see cref="Terminal.Gui.LayoutStyle.Computed"/>
  169. /// the <see cref="Frame"/> is updated using
  170. /// the <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties.
  171. /// </summary>
  172. /// <value>The layout style.</value>
  173. public LayoutStyle LayoutStyle {
  174. get => _layoutStyle;
  175. set {
  176. _layoutStyle = value;
  177. SetNeedsLayout ();
  178. }
  179. }
  180. /// <summary>
  181. /// The View-relative rectangle where View content is displayed. SubViews are positioned relative to
  182. /// Bounds.<see cref="Rect.Location">Location</see> (which is always (0, 0)) and <see cref="Draw()"/> clips drawing to
  183. /// Bounds.<see cref="Rect.Size">Size</see>.
  184. /// </summary>
  185. /// <value>The bounds.</value>
  186. /// <remarks>
  187. /// <para>
  188. /// The <see cref="Rect.Location"/> of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use
  189. /// <see cref="GetBoundsOffset"/>.
  190. /// </para>
  191. /// </remarks>
  192. public virtual Rect Bounds {
  193. get {
  194. #if DEBUG
  195. if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
  196. Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug. View: {this}");
  197. Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design.");
  198. }
  199. #endif // DEBUG
  200. //var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size);
  201. var frameRelativeBounds = FrameGetInsideBounds ();
  202. return new Rect (default, frameRelativeBounds.Size);
  203. }
  204. set {
  205. // BUGBUG: Margin etc.. can be null (if typeof(Frame))
  206. Frame = new Rect (Frame.Location,
  207. new Size (
  208. value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
  209. value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical
  210. )
  211. );
  212. }
  213. }
  214. private Rect FrameGetInsideBounds ()
  215. {
  216. if (Margin == null || Border == null || Padding == null) {
  217. return new Rect (default, Frame.Size);
  218. }
  219. var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
  220. var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
  221. return new Rect (Point.Empty, new Size (width, height));
  222. }
  223. // Diagnostics to highlight when X or Y is read before the view has been initialized
  224. private Pos VerifyIsIntialized (Pos pos)
  225. {
  226. #if DEBUG
  227. if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) {
  228. Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug.");
  229. }
  230. #endif // DEBUG
  231. return pos;
  232. }
  233. // Diagnostics to highlight when Width or Height is read before the view has been initialized
  234. private Dim VerifyIsIntialized (Dim dim)
  235. {
  236. #if DEBUG
  237. if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) {
  238. Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug.");
  239. }
  240. #endif // DEBUG
  241. return dim;
  242. }
  243. Pos _x, _y;
  244. /// <summary>
  245. /// Gets or sets the X position for the view (the column). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
  246. /// </summary>
  247. /// <value>The X Position.</value>
  248. /// <remarks>
  249. /// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
  250. /// </remarks>
  251. public Pos X {
  252. get => VerifyIsIntialized (_x);
  253. set {
  254. if (ForceValidatePosDim && !ValidatePosDim (_x, value)) {
  255. throw new ArgumentException ();
  256. }
  257. _x = value;
  258. OnResizeNeeded ();
  259. }
  260. }
  261. /// <summary>
  262. /// Gets or sets the Y position for the view (the row). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
  263. /// </summary>
  264. /// <value>The y position (line).</value>
  265. /// <remarks>
  266. /// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
  267. /// </remarks>
  268. public Pos Y {
  269. get => VerifyIsIntialized (_y);
  270. set {
  271. if (ForceValidatePosDim && !ValidatePosDim (_y, value)) {
  272. throw new ArgumentException ();
  273. }
  274. _y = value;
  275. OnResizeNeeded ();
  276. }
  277. }
  278. Dim _width, _height;
  279. /// <summary>
  280. /// Gets or sets the width of the view. Only used the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
  281. /// </summary>
  282. /// <value>The width.</value>
  283. /// <remarks>
  284. /// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
  285. /// </remarks>
  286. public Dim Width {
  287. get => VerifyIsIntialized (_width);
  288. set {
  289. if (ForceValidatePosDim && !ValidatePosDim (_width, value)) {
  290. throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Width));
  291. }
  292. _width = value;
  293. if (ForceValidatePosDim) {
  294. var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
  295. if (IsAdded && AutoSize && !isValidNewAutSize) {
  296. throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
  297. }
  298. }
  299. OnResizeNeeded ();
  300. }
  301. }
  302. /// <summary>
  303. /// Gets or sets the height of the view. Only used the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
  304. /// </summary>
  305. /// <value>The height.</value>
  306. /// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
  307. public Dim Height {
  308. get => VerifyIsIntialized (_height);
  309. set {
  310. if (ForceValidatePosDim && !ValidatePosDim (_height, value)) {
  311. throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Height));
  312. }
  313. _height = value;
  314. if (ForceValidatePosDim) {
  315. var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
  316. if (IsAdded && AutoSize && !isValidNewAutSize) {
  317. throw new InvalidOperationException ("Must set AutoSize to false before set the Height.");
  318. }
  319. }
  320. OnResizeNeeded ();
  321. }
  322. }
  323. /// <summary>
  324. /// Forces validation with <see cref="Terminal.Gui.LayoutStyle.Computed"/> layout
  325. /// to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
  326. /// </summary>
  327. public bool ForceValidatePosDim { get; set; }
  328. bool ValidatePosDim (object oldValue, object newValue)
  329. {
  330. if (!IsInitialized || _layoutStyle == LayoutStyle.Absolute || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) {
  331. return true;
  332. }
  333. if (_layoutStyle == LayoutStyle.Computed) {
  334. if (oldValue.GetType () != newValue.GetType () && !(newValue is Pos.PosAbsolute || newValue is Dim.DimAbsolute)) {
  335. return true;
  336. }
  337. }
  338. return false;
  339. }
  340. // BUGBUG: This API is broken - It should be renamed to `GetMinimumBoundsForFrame and
  341. // should not assume Frame.Height == Bounds.Height
  342. /// <summary>
  343. /// Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
  344. /// </summary>
  345. /// <param name="size">The minimum dimensions required.</param>
  346. /// <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</returns>
  347. /// <remarks>
  348. /// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
  349. /// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
  350. /// Does not take into account word wrapping.
  351. /// </remarks>
  352. public bool GetMinimumBounds (out Size size)
  353. {
  354. if (!IsInitialized) {
  355. size = new Size (0, 0);
  356. return false;
  357. }
  358. size = Bounds.Size;
  359. if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
  360. switch (TextFormatter.IsVerticalDirection (TextDirection)) {
  361. case true:
  362. var colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
  363. // TODO: v2 - This uses frame.Width; it should only use Bounds
  364. if (_frame.Width < colWidth &&
  365. (Width == null ||
  366. (Bounds.Width >= 0 &&
  367. Width is Dim.DimAbsolute &&
  368. Width.Anchor (0) >= 0 &&
  369. Width.Anchor (0) < colWidth))) {
  370. size = new Size (colWidth, Bounds.Height);
  371. return true;
  372. }
  373. break;
  374. default:
  375. if (_frame.Height < 1 &&
  376. (Height == null ||
  377. (Height is Dim.DimAbsolute &&
  378. Height.Anchor (0) == 0))) {
  379. size = new Size (Bounds.Width, 1);
  380. return true;
  381. }
  382. break;
  383. }
  384. }
  385. return false;
  386. }
  387. // BUGBUG - v2 - Should be renamed "SetBoundsToFitFrame"
  388. /// <summary>
  389. /// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/> (see <see cref="GetMinimumBounds(out Size)"/>.
  390. /// </summary>
  391. /// <returns><see langword="true"/> if the size was changed, <see langword="false"/> if <see cref="Text"/>
  392. /// will not fit.</returns>
  393. public bool SetMinWidthHeight ()
  394. {
  395. if (GetMinimumBounds (out Size size)) {
  396. _frame = new Rect (_frame.Location, size);
  397. return true;
  398. }
  399. return false;
  400. }
  401. /// <summary>
  402. /// Called whenever the view needs to be resized. Sets <see cref="Frame"/> and
  403. /// triggers a <see cref="LayoutSubviews()"/> call.
  404. /// </summary>
  405. /// <remarks>
  406. /// Can be overridden if the view resize behavior is different than the default.
  407. /// </remarks>
  408. protected virtual void OnResizeNeeded ()
  409. {
  410. var actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X;
  411. var actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y;
  412. if (AutoSize) {
  413. //if (TextAlignment == TextAlignment.Justified) {
  414. // throw new InvalidOperationException ("TextAlignment.Justified cannot be used with AutoSize");
  415. //}
  416. var s = GetAutoSize ();
  417. var w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width;
  418. var h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height;
  419. _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame!
  420. } else {
  421. var w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width;
  422. var h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height;
  423. // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm...
  424. // This is needed for DimAbsolute values by setting the frame before LayoutSubViews.
  425. _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame!
  426. }
  427. //// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case
  428. if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
  429. SetMinWidthHeight ();
  430. LayoutFrames ();
  431. TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
  432. SetNeedsLayout ();
  433. SetNeedsDisplay ();
  434. }
  435. }
  436. internal bool LayoutNeeded { get; private set; } = true;
  437. internal void SetNeedsLayout ()
  438. {
  439. if (LayoutNeeded) {
  440. return;
  441. }
  442. LayoutNeeded = true;
  443. foreach (var view in Subviews) {
  444. view.SetNeedsLayout ();
  445. }
  446. TextFormatter.NeedsFormat = true;
  447. SuperView?.SetNeedsLayout ();
  448. }
  449. /// <summary>
  450. /// Removes the <see cref="SetNeedsLayout"/> setting on this view.
  451. /// </summary>
  452. protected void ClearLayoutNeeded ()
  453. {
  454. LayoutNeeded = false;
  455. }
  456. /// <summary>
  457. /// Converts a point from screen-relative coordinates to view-relative coordinates.
  458. /// </summary>
  459. /// <returns>The mapped point.</returns>
  460. /// <param name="x">X screen-coordinate point.</param>
  461. /// <param name="y">Y screen-coordinate point.</param>
  462. public Point ScreenToView (int x, int y)
  463. {
  464. Point boundsOffset = SuperView == null ? Point.Empty : SuperView.GetBoundsOffset ();
  465. if (SuperView == null) {
  466. return new Point (x - Frame.X + boundsOffset.X, y - Frame.Y + boundsOffset.Y);
  467. } else {
  468. var parent = SuperView.ScreenToView (x - boundsOffset.X, y - boundsOffset.Y);
  469. return new Point (parent.X - Frame.X, parent.Y - Frame.Y);
  470. }
  471. }
  472. /// <summary>
  473. /// Converts a view-relative location to a screen-relative location (col,row). The output is optionally clamped to the screen dimensions.
  474. /// </summary>
  475. /// <param name="col">View-relative column.</param>
  476. /// <param name="row">View-relative row.</param>
  477. /// <param name="rcol">Absolute column; screen-relative.</param>
  478. /// <param name="rrow">Absolute row; screen-relative.</param>
  479. /// <param name="clamped">If <see langword="true"/>, <paramref name="rcol"/> and <paramref name="rrow"/> will be clamped to the
  480. /// screen dimensions (they never be negative and will always be less than to <see cref="ConsoleDriver.Cols"/> and
  481. /// <see cref="ConsoleDriver.Rows"/>, respectively.</param>
  482. public virtual void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clamped = true)
  483. {
  484. var boundsOffset = GetBoundsOffset ();
  485. rcol = col + Frame.X + boundsOffset.X;
  486. rrow = row + Frame.Y + boundsOffset.Y;
  487. var super = SuperView;
  488. while (super != null) {
  489. boundsOffset = super.GetBoundsOffset ();
  490. rcol += super.Frame.X + boundsOffset.X;
  491. rrow += super.Frame.Y + boundsOffset.Y;
  492. super = super.SuperView;
  493. }
  494. // The following ensures that the cursor is always in the screen boundaries.
  495. if (clamped) {
  496. rrow = Math.Min (rrow, Driver.Rows - 1);
  497. rcol = Math.Min (rcol, Driver.Cols - 1);
  498. }
  499. }
  500. /// <summary>
  501. /// Converts a region in view-relative coordinates to screen-relative coordinates.
  502. /// </summary>
  503. public Rect ViewToScreen (Rect region)
  504. {
  505. ViewToScreen (region.X, region.Y, out var x, out var y, clamped: false);
  506. return new Rect (x, y, region.Width, region.Height);
  507. }
  508. /// <summary>
  509. /// Sets the View's <see cref="Frame"/> to the frame-relative coordinates if its container. The
  510. /// container size and location are specified by <paramref name="superviewFrame"/> and are relative to the
  511. /// View's superview.
  512. /// </summary>
  513. /// <param name="superviewFrame">The supserview-relative rectangle describing View's container (nominally the
  514. /// same as <c>this.SuperView.Frame</c>).</param>
  515. internal void SetRelativeLayout (Rect superviewFrame)
  516. {
  517. int newX, newW, newY, newH;
  518. var autosize = Size.Empty;
  519. if (AutoSize) {
  520. // Note this is global to this function and used as such within the local functions defined
  521. // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function.
  522. autosize = GetAutoSize ();
  523. }
  524. // Returns the new dimension (width or height) and location (x or y) for the View given
  525. // the superview's Frame.X or Frame.Y
  526. // the superview's width or height
  527. // the current Pos (View.X or View.Y)
  528. // the current Dim (View.Width or View.Height)
  529. (int newLocation, int newDimension) GetNewLocationAndDimension (int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension)
  530. {
  531. int newDimension, newLocation;
  532. switch (pos) {
  533. case Pos.PosCenter:
  534. if (dim == null) {
  535. newDimension = AutoSize ? autosizeDimension : superviewDimension;
  536. } else {
  537. newDimension = dim.Anchor (superviewDimension);
  538. newDimension = AutoSize && autosizeDimension > newDimension ? autosizeDimension : newDimension;
  539. }
  540. newLocation = pos.Anchor (superviewDimension - newDimension);
  541. break;
  542. case Pos.PosCombine combine:
  543. int left, right;
  544. (left, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.left, dim, autosizeDimension);
  545. (right, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.right, dim, autosizeDimension);
  546. if (combine.add) {
  547. newLocation = left + right;
  548. } else {
  549. newLocation = left - right;
  550. }
  551. newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
  552. break;
  553. case Pos.PosAbsolute:
  554. case Pos.PosAnchorEnd:
  555. case Pos.PosFactor:
  556. case Pos.PosFunc:
  557. case Pos.PosView:
  558. default:
  559. newLocation = pos?.Anchor (superviewDimension) ?? 0;
  560. newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
  561. break;
  562. }
  563. return (newLocation, newDimension);
  564. }
  565. // Recursively calculates the new dimension (width or height) of the given Dim given:
  566. // the current location (x or y)
  567. // the current dimension (width or height)
  568. int CalculateNewDimension (Dim d, int location, int dimension, int autosize)
  569. {
  570. int newDimension;
  571. switch (d) {
  572. case null:
  573. newDimension = AutoSize ? autosize : dimension;
  574. break;
  575. case Dim.DimCombine combine:
  576. int leftNewDim = CalculateNewDimension (combine.left, location, dimension, autosize);
  577. int rightNewDim = CalculateNewDimension (combine.right, location, dimension, autosize);
  578. if (combine.add) {
  579. newDimension = leftNewDim + rightNewDim;
  580. } else {
  581. newDimension = leftNewDim - rightNewDim;
  582. }
  583. newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
  584. break;
  585. case Dim.DimFactor factor when !factor.IsFromRemaining ():
  586. newDimension = d.Anchor (dimension);
  587. newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
  588. break;
  589. case Dim.DimFill:
  590. default:
  591. newDimension = Math.Max (d.Anchor (dimension - location), 0);
  592. newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
  593. break;
  594. }
  595. return newDimension;
  596. }
  597. // horizontal
  598. (newX, newW) = GetNewLocationAndDimension (superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width);
  599. // vertical
  600. (newY, newH) = GetNewLocationAndDimension (superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height);
  601. var r = new Rect (newX, newY, newW, newH);
  602. if (Frame != r) {
  603. Frame = r;
  604. // BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
  605. if (!SetMinWidthHeight ()) {
  606. TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
  607. }
  608. }
  609. }
  610. /// <summary>
  611. /// Fired after the View's <see cref="LayoutSubviews"/> method has completed.
  612. /// </summary>
  613. /// <remarks>
  614. /// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise changed.
  615. /// </remarks>
  616. public event EventHandler<LayoutEventArgs> LayoutStarted;
  617. /// <summary>
  618. /// Raises the <see cref="LayoutStarted"/> event. Called from <see cref="LayoutSubviews"/> before any subviews have been laid out.
  619. /// </summary>
  620. internal virtual void OnLayoutStarted (LayoutEventArgs args)
  621. {
  622. LayoutStarted?.Invoke (this, args);
  623. }
  624. /// <summary>
  625. /// Fired after the View's <see cref="LayoutSubviews"/> method has completed.
  626. /// </summary>
  627. /// <remarks>
  628. /// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise changed.
  629. /// </remarks>
  630. public event EventHandler<LayoutEventArgs> LayoutComplete;
  631. /// <summary>
  632. /// Event called only once when the <see cref="View"/> is being initialized for the first time.
  633. /// Allows configurations and assignments to be performed before the <see cref="View"/> being shown.
  634. /// This derived from <see cref="ISupportInitializeNotification"/> to allow notify all the views that are being initialized.
  635. /// </summary>
  636. public event EventHandler Initialized;
  637. /// <summary>
  638. /// Raises the <see cref="LayoutComplete"/> event. Called from <see cref="LayoutSubviews"/> before all sub-views have been laid out.
  639. /// </summary>
  640. internal virtual void OnLayoutComplete (LayoutEventArgs args)
  641. {
  642. LayoutComplete?.Invoke (this, args);
  643. }
  644. internal void CollectPos (Pos pos, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
  645. {
  646. switch (pos) {
  647. case Pos.PosView pv:
  648. // See #2461
  649. //if (!from.InternalSubviews.Contains (pv.Target)) {
  650. // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}");
  651. //}
  652. if (pv.Target != this) {
  653. nEdges.Add ((pv.Target, from));
  654. }
  655. return;
  656. case Pos.PosCombine pc:
  657. CollectPos (pc.left, from, ref nNodes, ref nEdges);
  658. CollectPos (pc.right, from, ref nNodes, ref nEdges);
  659. break;
  660. }
  661. }
  662. internal void CollectDim (Dim dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
  663. {
  664. switch (dim) {
  665. case Dim.DimView dv:
  666. // See #2461
  667. //if (!from.InternalSubviews.Contains (dv.Target)) {
  668. // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}");
  669. //}
  670. if (dv.Target != this) {
  671. nEdges.Add ((dv.Target, from));
  672. }
  673. return;
  674. case Dim.DimCombine dc:
  675. CollectDim (dc.left, from, ref nNodes, ref nEdges);
  676. CollectDim (dc.right, from, ref nNodes, ref nEdges);
  677. break;
  678. }
  679. }
  680. internal void CollectAll (View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
  681. {
  682. foreach (var v in from.InternalSubviews) {
  683. nNodes.Add (v);
  684. if (v._layoutStyle != LayoutStyle.Computed) {
  685. continue;
  686. }
  687. CollectPos (v.X, v, ref nNodes, ref nEdges);
  688. CollectPos (v.Y, v, ref nNodes, ref nEdges);
  689. CollectDim (v.Width, v, ref nNodes, ref nEdges);
  690. CollectDim (v.Height, v, ref nNodes, ref nEdges);
  691. }
  692. }
  693. // https://en.wikipedia.org/wiki/Topological_sorting
  694. internal static List<View> TopologicalSort (View superView, IEnumerable<View> nodes, ICollection<(View From, View To)> edges)
  695. {
  696. var result = new List<View> ();
  697. // Set of all nodes with no incoming edges
  698. var noEdgeNodes = new HashSet<View> (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
  699. while (noEdgeNodes.Any ()) {
  700. // remove a node n from S
  701. var n = noEdgeNodes.First ();
  702. noEdgeNodes.Remove (n);
  703. // add n to tail of L
  704. if (n != superView)
  705. result.Add (n);
  706. // for each node m with an edge e from n to m do
  707. foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
  708. var m = e.To;
  709. // remove edge e from the graph
  710. edges.Remove (e);
  711. // if m has no other incoming edges then
  712. if (edges.All (me => !me.To.Equals (m)) && m != superView) {
  713. // insert m into S
  714. noEdgeNodes.Add (m);
  715. }
  716. }
  717. }
  718. if (edges.Any ()) {
  719. foreach ((var from, var to) in edges) {
  720. if (from == to) {
  721. // if not yet added to the result, add it and remove from edge
  722. if (result.Find (v => v == from) == null) {
  723. result.Add (from);
  724. }
  725. edges.Remove ((from, to));
  726. } else if (from.SuperView == to.SuperView) {
  727. // if 'from' is not yet added to the result, add it
  728. if (result.Find (v => v == from) == null) {
  729. result.Add (from);
  730. }
  731. // if 'to' is not yet added to the result, add it
  732. if (result.Find (v => v == to) == null) {
  733. result.Add (to);
  734. }
  735. // remove from edge
  736. edges.Remove ((from, to));
  737. } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) {
  738. if (ReferenceEquals (from.SuperView, to)) {
  739. throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\").");
  740. } else {
  741. throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?");
  742. }
  743. }
  744. }
  745. }
  746. // return L (a topologically sorted order)
  747. return result;
  748. } // TopologicalSort
  749. /// <summary>
  750. /// Overriden by <see cref="Frame"/> to do nothing, as the <see cref="Frame"/> does not have frames.
  751. /// </summary>
  752. internal virtual void LayoutFrames ()
  753. {
  754. if (Margin == null) return; // CreateFrames() has not been called yet
  755. if (Margin.Frame.Size != Frame.Size) {
  756. Margin._frame = new Rect (Point.Empty, Frame.Size);
  757. Margin.X = 0;
  758. Margin.Y = 0;
  759. Margin.Width = Frame.Size.Width;
  760. Margin.Height = Frame.Size.Height;
  761. Margin.SetNeedsLayout ();
  762. Margin.LayoutSubviews ();
  763. Margin.SetNeedsDisplay ();
  764. }
  765. var border = Margin.Thickness.GetInside (Margin.Frame);
  766. if (border != Border.Frame) {
  767. Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size);
  768. Border.X = border.Location.X;
  769. Border.Y = border.Location.Y;
  770. Border.Width = border.Size.Width;
  771. Border.Height = border.Size.Height;
  772. Border.SetNeedsLayout ();
  773. Border.LayoutSubviews ();
  774. Border.SetNeedsDisplay ();
  775. }
  776. var padding = Border.Thickness.GetInside (Border.Frame);
  777. if (padding != Padding.Frame) {
  778. Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size);
  779. Padding.X = padding.Location.X;
  780. Padding.Y = padding.Location.Y;
  781. Padding.Width = padding.Size.Width;
  782. Padding.Height = padding.Size.Height;
  783. Padding.SetNeedsLayout ();
  784. Padding.LayoutSubviews ();
  785. Padding.SetNeedsDisplay ();
  786. }
  787. }
  788. /// <summary>
  789. /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in
  790. /// response to the container view or terminal resizing.
  791. /// </summary>
  792. /// <remarks>
  793. /// Calls <see cref="OnLayoutComplete"/> (which raises the <see cref="LayoutComplete"/> event) before it returns.
  794. /// </remarks>
  795. public virtual void LayoutSubviews ()
  796. {
  797. if (!LayoutNeeded) {
  798. return;
  799. }
  800. LayoutFrames ();
  801. var oldBounds = Bounds;
  802. OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
  803. TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
  804. // Sort out the dependencies of the X, Y, Width, Height properties
  805. var nodes = new HashSet<View> ();
  806. var edges = new HashSet<(View, View)> ();
  807. CollectAll (this, ref nodes, ref edges);
  808. var ordered = View.TopologicalSort (SuperView, nodes, edges);
  809. foreach (var v in ordered) {
  810. LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
  811. }
  812. // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
  813. // Use LayoutSubview with the Frame of the 'from'
  814. if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) {
  815. foreach ((var from, var to) in edges) {
  816. LayoutSubview (to, from.Frame);
  817. }
  818. }
  819. LayoutNeeded = false;
  820. OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
  821. }
  822. private void LayoutSubview (View v, Rect contentArea)
  823. {
  824. if (v.LayoutStyle == LayoutStyle.Computed) {
  825. v.SetRelativeLayout (contentArea);
  826. }
  827. v.LayoutSubviews ();
  828. v.LayoutNeeded = false;
  829. }
  830. bool _autoSize;
  831. /// <summary>
  832. /// Gets or sets a flag that determines whether the View will be automatically resized to fit the <see cref="Text"/>
  833. /// within <see cref="Bounds"/>
  834. /// <para>
  835. /// The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If <see langword="true"/> then
  836. /// <see cref="Width"/> and <see cref="Height"/> will be used if <see cref="Text"/> can fit;
  837. /// if <see cref="Text"/> won't fit the view will be resized as needed.
  838. /// </para>
  839. /// <para>
  840. /// In addition, if <see cref="ForceValidatePosDim"/> is <see langword="true"/> the new values of <see cref="Width"/> and
  841. /// <see cref="Height"/> must be of the same types of the existing one to avoid breaking the <see cref="Dim"/> settings.
  842. /// </para>
  843. /// </summary>
  844. public virtual bool AutoSize {
  845. get => _autoSize;
  846. set {
  847. var v = ResizeView (value);
  848. TextFormatter.AutoSize = v;
  849. if (_autoSize != v) {
  850. _autoSize = v;
  851. TextFormatter.NeedsFormat = true;
  852. UpdateTextFormatterText ();
  853. OnResizeNeeded ();
  854. }
  855. }
  856. }
  857. bool ResizeView (bool autoSize)
  858. {
  859. if (!autoSize) {
  860. return false;
  861. }
  862. var aSize = true;
  863. var nBoundsSize = GetAutoSize ();
  864. if (IsInitialized && nBoundsSize != Bounds.Size) {
  865. if (ForceValidatePosDim) {
  866. aSize = SetWidthHeight (nBoundsSize);
  867. } else {
  868. Height = nBoundsSize.Height;
  869. Width = nBoundsSize.Width; // = new Rect (Bounds.X, Bounds.Y, nBoundsSize.Width, nBoundsSize.Height);
  870. }
  871. }
  872. // BUGBUG: This call may be redundant
  873. TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
  874. return aSize;
  875. }
  876. /// <summary>
  877. /// Resizes the View to fit the specified <see cref="Bounds"/> size.
  878. /// </summary>
  879. /// <param name="nBounds"></param>
  880. /// <returns></returns>
  881. bool SetWidthHeight (Size nBounds)
  882. {
  883. var aSize = false;
  884. var canSizeW = TrySetWidth (nBounds.Width - GetHotKeySpecifierLength (), out var rW);
  885. var canSizeH = TrySetHeight (nBounds.Height - GetHotKeySpecifierLength (false), out var rH);
  886. if (canSizeW) {
  887. aSize = true;
  888. _width = rW;
  889. }
  890. if (canSizeH) {
  891. aSize = true;
  892. _height = rH;
  893. }
  894. if (aSize) {
  895. Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
  896. }
  897. return aSize;
  898. }
  899. /// <summary>
  900. /// Gets the Frame dimensions required to fit <see cref="Text"/> using the text <see cref="Direction"/> specified by the
  901. /// <see cref="TextFormatter"/> property and accounting for any <see cref="HotKeySpecifier"/> characters.
  902. /// </summary>
  903. /// <returns>The <see cref="Size"/> required to fit the text.</returns>
  904. public Size GetAutoSize ()
  905. {
  906. int x = 0;
  907. int y = 0;
  908. if (IsInitialized) {
  909. x = Bounds.X;
  910. y = Bounds.Y;
  911. }
  912. var rect = TextFormatter.CalcRect (x, y,TextFormatter.Text, TextFormatter.Direction);
  913. var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal;
  914. var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical;
  915. return new Size (newWidth, newHeight);
  916. }
  917. bool IsValidAutoSize (out Size autoSize)
  918. {
  919. var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
  920. autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (),
  921. rect.Size.Height - GetHotKeySpecifierLength (false));
  922. return !(ForceValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute))
  923. || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength ()
  924. || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false));
  925. }
  926. bool IsValidAutoSizeWidth (Dim width)
  927. {
  928. var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
  929. var dimValue = width.Anchor (0);
  930. return !(ForceValidatePosDim && (!(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width
  931. - GetHotKeySpecifierLength ());
  932. }
  933. bool IsValidAutoSizeHeight (Dim height)
  934. {
  935. var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
  936. var dimValue = height.Anchor (0);
  937. return !(ForceValidatePosDim && (!(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height
  938. - GetHotKeySpecifierLength (false));
  939. }
  940. /// <summary>
  941. /// Determines if the View's <see cref="Width"/> can be set to a new value.
  942. /// </summary>
  943. /// <param name="desiredWidth"></param>
  944. /// <param name="resultWidth">Contains the width that would result if <see cref="Width"/> were set to <paramref name="desiredWidth"/>"/> </param>
  945. /// <returns><see langword="true"/> if the View's <see cref="Width"/> can be changed to the specified value. False otherwise.</returns>
  946. internal bool TrySetWidth (int desiredWidth, out int resultWidth)
  947. {
  948. var w = desiredWidth;
  949. bool canSetWidth;
  950. switch (Width) {
  951. case Dim.DimCombine _:
  952. case Dim.DimView _:
  953. case Dim.DimFill _:
  954. // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored.
  955. w = Width.Anchor (w);
  956. canSetWidth = !ForceValidatePosDim;
  957. break;
  958. case Dim.DimFactor factor:
  959. // Tries to get the SuperView Width otherwise the view Width.
  960. var sw = SuperView != null ? SuperView.Frame.Width : w;
  961. if (factor.IsFromRemaining ()) {
  962. sw -= Frame.X;
  963. }
  964. w = Width.Anchor (sw);
  965. canSetWidth = !ForceValidatePosDim;
  966. break;
  967. default:
  968. canSetWidth = true;
  969. break;
  970. }
  971. resultWidth = w;
  972. return canSetWidth;
  973. }
  974. /// <summary>
  975. /// Determines if the View's <see cref="Height"/> can be set to a new value.
  976. /// </summary>
  977. /// <param name="desiredHeight"></param>
  978. /// <param name="resultHeight">Contains the width that would result if <see cref="Height"/> were set to <paramref name="desiredHeight"/>"/> </param>
  979. /// <returns><see langword="true"/> if the View's <see cref="Height"/> can be changed to the specified value. False otherwise.</returns>
  980. internal bool TrySetHeight (int desiredHeight, out int resultHeight)
  981. {
  982. var h = desiredHeight;
  983. bool canSetHeight;
  984. switch (Height) {
  985. case Dim.DimCombine _:
  986. case Dim.DimView _:
  987. case Dim.DimFill _:
  988. // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
  989. h = Height.Anchor (h);
  990. canSetHeight = !ForceValidatePosDim;
  991. break;
  992. case Dim.DimFactor factor:
  993. // Tries to get the SuperView height otherwise the view height.
  994. var sh = SuperView != null ? SuperView.Frame.Height : h;
  995. if (factor.IsFromRemaining ()) {
  996. sh -= Frame.Y;
  997. }
  998. h = Height.Anchor (sh);
  999. canSetHeight = !ForceValidatePosDim;
  1000. break;
  1001. default:
  1002. canSetHeight = true;
  1003. break;
  1004. }
  1005. resultHeight = h;
  1006. return canSetHeight;
  1007. }
  1008. /// <summary>
  1009. /// Finds which view that belong to the <paramref name="start"/> superview at the provided location.
  1010. /// </summary>
  1011. /// <param name="start">The superview where to look for.</param>
  1012. /// <param name="x">The column location in the superview.</param>
  1013. /// <param name="y">The row location in the superview.</param>
  1014. /// <param name="resx">The found view screen relative column location.</param>
  1015. /// <param name="resy">The found view screen relative row location.</param>
  1016. /// <returns>
  1017. /// The view that was found at the <praramref name="x"/> and <praramref name="y"/> coordinates.
  1018. /// <see langword="null"/> if no view was found.
  1019. /// </returns>
  1020. public static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
  1021. {
  1022. var startFrame = start.Frame;
  1023. if (!startFrame.Contains (x, y)) {
  1024. resx = 0;
  1025. resy = 0;
  1026. return null;
  1027. }
  1028. if (start.InternalSubviews != null) {
  1029. int count = start.InternalSubviews.Count;
  1030. if (count > 0) {
  1031. var boundsOffset = start.GetBoundsOffset ();
  1032. var rx = x - (startFrame.X + boundsOffset.X);
  1033. var ry = y - (startFrame.Y + boundsOffset.Y);
  1034. for (int i = count - 1; i >= 0; i--) {
  1035. View v = start.InternalSubviews [i];
  1036. if (v.Visible && v.Frame.Contains (rx, ry)) {
  1037. var deep = FindDeepestView (v, rx, ry, out resx, out resy);
  1038. if (deep == null)
  1039. return v;
  1040. return deep;
  1041. }
  1042. }
  1043. }
  1044. }
  1045. resx = x - startFrame.X;
  1046. resy = y - startFrame.Y;
  1047. return start;
  1048. }
  1049. }
  1050. }