SplitContainer.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. using System;
  2. using Terminal.Gui.Graphs;
  3. using static Terminal.Gui.Dim;
  4. namespace Terminal.Gui {
  5. public class SplitContainer : View {
  6. private LineView splitterLine;
  7. private bool panel1Collapsed;
  8. private bool panel2Collapsed;
  9. private Pos splitterDistance = Pos.Percent (50);
  10. private Orientation orientation = Orientation.Vertical;
  11. private int panel1MinSize;
  12. /// <summary>
  13. /// Creates a new instance of the SplitContainer class.
  14. /// </summary>
  15. public SplitContainer ()
  16. {
  17. // Default to a border of 1 so that View looks nice
  18. Border = new Border ();
  19. splitterLine = new SplitContainerLineView (this);
  20. this.Add (splitterLine);
  21. this.Add (Panel1);
  22. this.Add (Panel2);
  23. Setup ();
  24. CanFocus = false;
  25. }
  26. /// <summary>
  27. /// The left or top panel of the <see cref="SplitContainer"/>
  28. /// (depending on <see cref="Orientation"/>). Add panel contents
  29. /// to this <see cref="View"/> using <see cref="View.Add(View)"/>.
  30. /// </summary>
  31. public View Panel1 { get; } = new View ();
  32. /// <summary>
  33. /// The minimum size <see cref="Panel1"/> can be when adjusting
  34. /// <see cref="SplitterDistance"/>.
  35. /// </summary>
  36. public int Panel1MinSize {
  37. get { return panel1MinSize; }
  38. set {
  39. panel1MinSize = value;
  40. Setup();
  41. }
  42. }
  43. /// <summary>
  44. /// This determines if <see cref="Panel1"/> is collapsed.
  45. /// </summary>
  46. public bool Panel1Collapsed {
  47. get { return panel1Collapsed; }
  48. set {
  49. panel1Collapsed = value;
  50. if (value && panel2Collapsed) {
  51. panel2Collapsed = false;
  52. }
  53. Setup ();
  54. }
  55. }
  56. /// <summary>
  57. /// The right or bottom panel of the <see cref="SplitContainer"/>
  58. /// (depending on <see cref="Orientation"/>). Add panel contents
  59. /// to this <see cref="View"/> using <see cref="View.Add(View)"/>
  60. /// </summary>
  61. public View Panel2 { get; } = new View ();
  62. /// <summary>
  63. /// The minimum size <see cref="Panel2"/> can be when adjusting
  64. /// <see cref="SplitterDistance"/>.
  65. /// </summary>
  66. public int Panel2MinSize { get; set; }
  67. /// <summary>
  68. /// This determines if <see cref="Panel2"/> is collapsed.
  69. /// </summary>
  70. public bool Panel2Collapsed {
  71. get { return panel2Collapsed; }
  72. set {
  73. panel2Collapsed = value;
  74. if (value && panel1Collapsed) {
  75. panel1Collapsed = false;
  76. }
  77. Setup ();
  78. }
  79. }
  80. /// <summary>
  81. /// Orientation of the dividing line (Horizontal or Vertical).
  82. /// </summary>
  83. public Orientation Orientation {
  84. get { return orientation; }
  85. set {
  86. orientation = value;
  87. Setup ();
  88. }
  89. }
  90. /// <summary>
  91. /// Distance Horizontally or Vertically to the splitter line when
  92. /// neither panel is collapsed.
  93. /// </summary>
  94. public Pos SplitterDistance {
  95. get { return splitterDistance; }
  96. set {
  97. splitterDistance = value;
  98. Setup ();
  99. }
  100. }
  101. public override bool OnEnter (View view)
  102. {
  103. Driver.SetCursorVisibility (CursorVisibility.Invisible);
  104. return base.OnEnter (view);
  105. }
  106. private void Setup ()
  107. {
  108. splitterLine.Orientation = Orientation;
  109. if (panel1Collapsed || panel2Collapsed) {
  110. SetupForCollapsedPanel ();
  111. } else {
  112. SetupForNormal ();
  113. }
  114. }
  115. private void SetupForNormal ()
  116. {
  117. // Ensure all our component views are here
  118. // (e.g. if we are transitioning from a collapsed state)
  119. if (!this.Subviews.Contains (splitterLine)) {
  120. this.Add (splitterLine);
  121. }
  122. if (!this.Subviews.Contains (Panel1)) {
  123. this.Add (Panel1);
  124. }
  125. if (!this.Subviews.Contains (Panel2)) {
  126. this.Add (Panel2);
  127. }
  128. switch (Orientation) {
  129. case Orientation.Horizontal:
  130. splitterLine.X = 0;
  131. splitterLine.Y = splitterDistance;
  132. splitterLine.Width = Dim.Fill ();
  133. splitterLine.Height = 1;
  134. splitterLine.LineRune = Driver.HLine;
  135. this.Panel1.X = 0;
  136. this.Panel1.Y = 0;
  137. this.Panel1.Width = Dim.Fill ();
  138. this.Panel1.Height = new DimFunc (() =>
  139. splitterDistance.Anchor (Bounds.Height) - 1);
  140. this.Panel2.Y = Pos.Bottom (splitterLine) + 1;
  141. this.Panel2.X = 0;
  142. this.Panel2.Width = Dim.Fill ();
  143. this.Panel2.Height = Dim.Fill ();
  144. break;
  145. case Orientation.Vertical:
  146. splitterLine.X = splitterDistance;
  147. splitterLine.Y = 0;
  148. splitterLine.Width = 1;
  149. splitterLine.Height = Dim.Fill ();
  150. splitterLine.LineRune = Driver.VLine;
  151. this.Panel1.X = 0;
  152. this.Panel1.Y = 0;
  153. this.Panel1.Height = Dim.Fill ();
  154. this.Panel1.Width = new DimFunc (() =>
  155. splitterDistance.Anchor (Bounds.Width) - 1);
  156. this.Panel2.X = Pos.Right (splitterLine) + 1;
  157. this.Panel2.Y = 0;
  158. this.Panel2.Width = Dim.Fill ();
  159. this.Panel2.Height = Dim.Fill ();
  160. break;
  161. default: throw new ArgumentOutOfRangeException (nameof (orientation));
  162. };
  163. }
  164. private void SetupForCollapsedPanel ()
  165. {
  166. View toRemove = panel1Collapsed ? Panel1 : Panel2;
  167. View toFullSize = panel1Collapsed ? Panel2 : Panel1;
  168. if (this.Subviews.Contains (splitterLine)) {
  169. this.Subviews.Remove (splitterLine);
  170. }
  171. if (this.Subviews.Contains (toRemove)) {
  172. this.Subviews.Remove (toRemove);
  173. }
  174. if (!this.Subviews.Contains (toFullSize)) {
  175. this.Add (toFullSize);
  176. }
  177. toFullSize.X = 0;
  178. toFullSize.Y = 0;
  179. toFullSize.Width = Dim.Fill ();
  180. toFullSize.Height = Dim.Fill ();
  181. }
  182. private class SplitContainerLineView : LineView
  183. {
  184. private SplitContainer parent;
  185. Point? dragPosition;
  186. Pos dragOrignalPos;
  187. // TODO: Make focusable and allow moving with keyboard
  188. public SplitContainerLineView(SplitContainer parent)
  189. {
  190. CanFocus = true;
  191. this.parent = parent;
  192. }
  193. ///<inheritdoc/>
  194. public override bool MouseEvent (MouseEvent mouseEvent)
  195. {
  196. if (!CanFocus) {
  197. return true;
  198. }
  199. //System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}");
  200. int nx, ny;
  201. // Start a drag
  202. if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed)) {
  203. SetFocus ();
  204. Application.EnsuresTopOnFront ();
  205. if (mouseEvent.Flags == MouseFlags.Button1Pressed) {
  206. nx = mouseEvent.X - mouseEvent.OfX;
  207. ny = mouseEvent.Y - mouseEvent.OfY;
  208. dragPosition = new Point (nx, ny);
  209. dragOrignalPos = Orientation == Orientation.Horizontal ? Y : X;
  210. Application.GrabMouse (this);
  211. }
  212. System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
  213. return true;
  214. } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
  215. {
  216. if (dragPosition.HasValue) {
  217. // how far has user dragged from original location?
  218. if(Orientation == Orientation.Horizontal)
  219. {
  220. int dy = dragPosition.Value.Y - mouseEvent.Y;
  221. Y = dragOrignalPos + dy;
  222. }
  223. else
  224. {
  225. int dx = dragPosition.Value.X - mouseEvent.X;
  226. X = dragOrignalPos + dx;
  227. }
  228. //System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}");
  229. SetNeedsDisplay ();
  230. parent.Setup();
  231. return true;
  232. }
  233. }
  234. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
  235. Application.UngrabMouse ();
  236. Driver.UncookMouse ();
  237. dragPosition = null;
  238. }
  239. //System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}");
  240. //System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}");
  241. return false;
  242. }
  243. }
  244. }
  245. }