ViewLayout.cs 41 KB

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