Visual.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. // Copyright (c) The Perspex Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Collections.Specialized;
  6. using System.Linq;
  7. using System.Reactive.Linq;
  8. using Perspex.Animation;
  9. using Perspex.Collections;
  10. using Perspex.Media;
  11. using Perspex.Platform;
  12. using Perspex.Rendering;
  13. using Perspex.VisualTree;
  14. using Serilog;
  15. using Serilog.Core.Enrichers;
  16. namespace Perspex
  17. {
  18. /// <summary>
  19. /// Base class for controls that provides rendering and related visual properties.
  20. /// </summary>
  21. /// <remarks>
  22. /// The <see cref="Visual"/> class acts as a node in the Perspex scene graph and holds
  23. /// all the information needed for an <see cref="IRenderTarget"/> to render the control.
  24. /// To traverse the scene graph (aka Visual Tree), use the extension methods defined
  25. /// in <see cref="VisualExtensions"/>.
  26. /// </remarks>
  27. public class Visual : Animatable, IVisual
  28. {
  29. /// <summary>
  30. /// Defines the <see cref="Bounds"/> property.
  31. /// </summary>
  32. public static readonly PerspexProperty<Rect> BoundsProperty =
  33. PerspexProperty.RegisterDirect<Visual, Rect>(nameof(Bounds), o => o.Bounds);
  34. /// <summary>
  35. /// Defines the <see cref="ClipToBounds"/> property.
  36. /// </summary>
  37. public static readonly PerspexProperty<bool> ClipToBoundsProperty =
  38. PerspexProperty.Register<Visual, bool>(nameof(ClipToBounds));
  39. /// <summary>
  40. /// Defines the <see cref="IsVisibleProperty"/> property.
  41. /// </summary>
  42. public static readonly PerspexProperty<bool> IsVisibleProperty =
  43. PerspexProperty.Register<Visual, bool>(nameof(IsVisible), true);
  44. /// <summary>
  45. /// Defines the <see cref="Opacity"/> property.
  46. /// </summary>
  47. public static readonly PerspexProperty<double> OpacityProperty =
  48. PerspexProperty.Register<Visual, double>(nameof(Opacity), 1);
  49. /// <summary>
  50. /// Defines the <see cref="RenderTransform"/> property.
  51. /// </summary>
  52. public static readonly PerspexProperty<Transform> RenderTransformProperty =
  53. PerspexProperty.Register<Visual, Transform>(nameof(RenderTransform));
  54. /// <summary>
  55. /// Defines the <see cref="TransformOrigin"/> property.
  56. /// </summary>
  57. public static readonly PerspexProperty<RelativePoint> TransformOriginProperty =
  58. PerspexProperty.Register<Visual, RelativePoint>(nameof(TransformOrigin), defaultValue: RelativePoint.Center);
  59. /// <summary>
  60. /// Defines the <see cref="IVisual.VisualParent"/> property.
  61. /// </summary>
  62. public static readonly PerspexProperty<IVisual> VisualParentProperty =
  63. PerspexProperty.RegisterDirect<Visual, IVisual>("VisualParent", o => o._visualParent);
  64. /// <summary>
  65. /// Defines the <see cref="ZIndex"/> property.
  66. /// </summary>
  67. public static readonly PerspexProperty<int> ZIndexProperty =
  68. PerspexProperty.Register<Visual, int>(nameof(ZIndex));
  69. /// <summary>
  70. /// Holds the children of the visual.
  71. /// </summary>
  72. private readonly PerspexList<IVisual> _visualChildren;
  73. /// <summary>
  74. /// The visual's bounds relative to its parent.
  75. /// </summary>
  76. private Rect _bounds;
  77. /// <summary>
  78. /// Holds the parent of the visual.
  79. /// </summary>
  80. private IVisual _visualParent;
  81. /// <summary>
  82. /// Whether the element is attached to the visual tree.
  83. /// </summary>
  84. private bool _isAttachedToVisualTree;
  85. /// <summary>
  86. /// The logger for visual-level events.
  87. /// </summary>
  88. private readonly ILogger _visualLogger;
  89. /// <summary>
  90. /// Initializes static members of the <see cref="Visual"/> class.
  91. /// </summary>
  92. static Visual()
  93. {
  94. AffectsRender(IsVisibleProperty);
  95. AffectsRender(OpacityProperty);
  96. RenderTransformProperty.Changed.Subscribe(RenderTransformChanged);
  97. }
  98. /// <summary>
  99. /// Initializes a new instance of the <see cref="Visual"/> class.
  100. /// </summary>
  101. public Visual()
  102. {
  103. _visualLogger = Log.ForContext(new[]
  104. {
  105. new PropertyEnricher("Area", "Visual"),
  106. new PropertyEnricher("SourceContext", GetType()),
  107. new PropertyEnricher("Id", GetHashCode()),
  108. });
  109. _visualChildren = new PerspexList<IVisual>();
  110. _visualChildren.ResetBehavior = ResetBehavior.Remove;
  111. _visualChildren.CollectionChanged += VisualChildrenChanged;
  112. }
  113. /// <summary>
  114. /// Gets the bounds of the scene graph node relative to its parent.
  115. /// </summary>
  116. public Rect Bounds
  117. {
  118. get { return _bounds; }
  119. protected set { SetAndRaise(BoundsProperty, ref _bounds, value); }
  120. }
  121. /// <summary>
  122. /// Gets a value indicating whether the scene graph node should be clipped to its bounds.
  123. /// </summary>
  124. public bool ClipToBounds
  125. {
  126. get { return GetValue(ClipToBoundsProperty); }
  127. set { SetValue(ClipToBoundsProperty, value); }
  128. }
  129. /// <summary>
  130. /// Gets a value indicating whether this scene graph node and all its parents are visible.
  131. /// </summary>
  132. public bool IsEffectivelyVisible
  133. {
  134. get { return this.GetSelfAndVisualAncestors().All(x => x.IsVisible); }
  135. }
  136. /// <summary>
  137. /// Gets a value indicating whether this scene graph node is visible.
  138. /// </summary>
  139. public bool IsVisible
  140. {
  141. get { return GetValue(IsVisibleProperty); }
  142. set { SetValue(IsVisibleProperty, value); }
  143. }
  144. /// <summary>
  145. /// Gets the opacity of the scene graph node.
  146. /// </summary>
  147. public double Opacity
  148. {
  149. get { return GetValue(OpacityProperty); }
  150. set { SetValue(OpacityProperty, value); }
  151. }
  152. /// <summary>
  153. /// Gets the render transform of the scene graph node.
  154. /// </summary>
  155. public Transform RenderTransform
  156. {
  157. get { return GetValue(RenderTransformProperty); }
  158. set { SetValue(RenderTransformProperty, value); }
  159. }
  160. /// <summary>
  161. /// Gets the transform origin of the scene graph node.
  162. /// </summary>
  163. public RelativePoint TransformOrigin
  164. {
  165. get { return GetValue(TransformOriginProperty); }
  166. set { SetValue(TransformOriginProperty, value); }
  167. }
  168. /// <summary>
  169. /// Gets the Z index of the node.
  170. /// </summary>
  171. /// <remarks>
  172. /// Controls with a higher <see cref="ZIndex"/> will appear in front of controls with
  173. /// a lower ZIndex. If two controls have the same ZIndex then the control that appears
  174. /// later in the containing element's children collection will appear on top.
  175. /// </remarks>
  176. public int ZIndex
  177. {
  178. get { return GetValue(ZIndexProperty); }
  179. set { SetValue(ZIndexProperty, value); }
  180. }
  181. /// <summary>
  182. /// Gets a value indicating whether this scene graph node is attached to a visual root.
  183. /// </summary>
  184. bool IVisual.IsAttachedToVisualTree => _isAttachedToVisualTree;
  185. /// <summary>
  186. /// Gets the scene graph node's child nodes.
  187. /// </summary>
  188. IPerspexReadOnlyList<IVisual> IVisual.VisualChildren => _visualChildren;
  189. /// <summary>
  190. /// Gets the scene graph node's parent node.
  191. /// </summary>
  192. IVisual IVisual.VisualParent => _visualParent;
  193. /// <summary>
  194. /// Invalidates the visual and queues a repaint.
  195. /// </summary>
  196. public void InvalidateVisual()
  197. {
  198. IRenderRoot root = this.GetSelfAndVisualAncestors()
  199. .OfType<IRenderRoot>()
  200. .FirstOrDefault();
  201. if (root != null && root.RenderQueueManager != null)
  202. {
  203. root.RenderQueueManager.InvalidateRender(this);
  204. }
  205. }
  206. /// <summary>
  207. /// Renders the visual to a <see cref="DrawingContext"/>.
  208. /// </summary>
  209. /// <param name="context">The drawing context.</param>
  210. public virtual void Render(DrawingContext context)
  211. {
  212. Contract.Requires<ArgumentNullException>(context != null);
  213. }
  214. /// <summary>
  215. /// Converts a point from control coordinates to screen coordinates.
  216. /// </summary>
  217. /// <param name="point">The point to convert.</param>
  218. /// <returns>The point in screen coordinates.</returns>
  219. public Point PointToScreen(Point point)
  220. {
  221. var p = GetOffsetFromRoot(this);
  222. return p.Item1.TranslatePointToScreen(point + p.Item2);
  223. }
  224. /// <summary>
  225. /// Returns a transform that transforms the visual's coordinates into the coordinates
  226. /// of the specified <paramref name="visual"/>.
  227. /// </summary>
  228. /// <param name="visual">The visual to translate the coordinates to.</param>
  229. /// <returns>A <see cref="Matrix"/> containing the transform.</returns>
  230. public Matrix TransformToVisual(IVisual visual)
  231. {
  232. var thisOffset = GetOffsetFromRoot(this).Item2;
  233. var thatOffset = GetOffsetFromRoot(visual).Item2;
  234. return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset);
  235. }
  236. /// <summary>
  237. /// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
  238. /// called.
  239. /// </summary>
  240. /// <param name="property">The property.</param>
  241. /// <remarks>
  242. /// This method should be called in a control's static constructor for each property
  243. /// on the control which when changed should cause a redraw. This is similar to WPF's
  244. /// FrameworkPropertyMetadata.AffectsRender flag.
  245. /// </remarks>
  246. protected static void AffectsRender(PerspexProperty property)
  247. {
  248. property.Changed.Subscribe(AffectsRenderInvalidate);
  249. }
  250. /// <summary>
  251. /// Adds a visual child to the control.
  252. /// </summary>
  253. /// <param name="visual">The child to add.</param>
  254. protected void AddVisualChild(IVisual visual)
  255. {
  256. Contract.Requires<ArgumentNullException>(visual != null);
  257. _visualChildren.Add(visual);
  258. }
  259. /// <summary>
  260. /// Adds visual children to the control.
  261. /// </summary>
  262. /// <param name="visuals">The children to add.</param>
  263. protected void AddVisualChildren(IEnumerable<IVisual> visuals)
  264. {
  265. Contract.Requires<ArgumentNullException>(visuals != null);
  266. _visualChildren.AddRange(visuals);
  267. }
  268. /// <summary>
  269. /// Removes all visual children from the control.
  270. /// </summary>
  271. protected void ClearVisualChildren()
  272. {
  273. _visualChildren.Clear();
  274. }
  275. /// <summary>
  276. /// Removes a visual child from the control;
  277. /// </summary>
  278. /// <param name="visual">The child to remove.</param>
  279. protected void RemoveVisualChild(IVisual visual)
  280. {
  281. Contract.Requires<ArgumentNullException>(visual != null);
  282. _visualChildren.Remove(visual);
  283. }
  284. /// <summary>
  285. /// Removes a visual children from the control;
  286. /// </summary>
  287. /// <param name="visuals">The children to remove.</param>
  288. protected void RemoveVisualChildren(IEnumerable<IVisual> visuals)
  289. {
  290. Contract.Requires<ArgumentNullException>(visuals != null);
  291. foreach (var v in visuals)
  292. {
  293. _visualChildren.Remove(v);
  294. }
  295. }
  296. /// <summary>
  297. /// Called when the control is added to a visual tree.
  298. /// </summary>
  299. /// <param name="root">The root of the visual tree.</param>
  300. protected virtual void OnAttachedToVisualTree(IRenderRoot root)
  301. {
  302. }
  303. /// <summary>
  304. /// Called when the control is removed from a visual tree.
  305. /// </summary>
  306. /// <param name="root">The root of the visual tree.</param>
  307. protected virtual void OnDetachedFromVisualTree(IRenderRoot root)
  308. {
  309. }
  310. /// <summary>
  311. /// Called when a property changes that should invalidate the visual.
  312. /// </summary>
  313. /// <param name="e">The event args.</param>
  314. private static void AffectsRenderInvalidate(PerspexPropertyChangedEventArgs e)
  315. {
  316. Visual visual = e.Sender as Visual;
  317. if (visual != null)
  318. {
  319. visual.InvalidateVisual();
  320. }
  321. }
  322. /// <summary>
  323. /// Gets the root of the controls visual tree and the distance from the root.
  324. /// </summary>
  325. /// <param name="v">The visual.</param>
  326. /// <returns>A tuple containing the root and the distance from the root</returns>
  327. private static Tuple<IRenderRoot, Vector> GetOffsetFromRoot(IVisual v)
  328. {
  329. var result = new Vector();
  330. while (!(v is IRenderRoot))
  331. {
  332. result = new Vector(result.X + v.Bounds.X, result.Y + v.Bounds.Y);
  333. v = v.VisualParent;
  334. if (v == null)
  335. {
  336. throw new InvalidOperationException("Control is not attached to visual tree.");
  337. }
  338. }
  339. return Tuple.Create((IRenderRoot)v, result);
  340. }
  341. /// <summary>
  342. /// Called when a visual's <see cref="RenderTransform"/> changes.
  343. /// </summary>
  344. /// <param name="e">The event args.</param>
  345. private static void RenderTransformChanged(PerspexPropertyChangedEventArgs e)
  346. {
  347. var sender = e.Sender as Visual;
  348. if (sender != null)
  349. {
  350. var oldValue = e.OldValue as Transform;
  351. var newValue = e.NewValue as Transform;
  352. if (oldValue != null)
  353. {
  354. oldValue.Changed -= sender.RenderTransformChanged;
  355. }
  356. if (newValue != null)
  357. {
  358. newValue.Changed += sender.RenderTransformChanged;
  359. }
  360. sender.InvalidateVisual();
  361. }
  362. }
  363. /// <summary>
  364. /// Called when the <see cref="RenderTransform"/>'s <see cref="Transform.Changed"/> event
  365. /// is fired.
  366. /// </summary>
  367. /// <param name="sender">The sender.</param>
  368. /// <param name="e">The event args.</param>
  369. private void RenderTransformChanged(object sender, EventArgs e)
  370. {
  371. InvalidateVisual();
  372. }
  373. /// <summary>
  374. /// Sets the visual parent of the Visual.
  375. /// </summary>
  376. /// <param name="value">The visual parent.</param>
  377. private void SetVisualParent(Visual value)
  378. {
  379. if (_visualParent != value)
  380. {
  381. var old = _visualParent;
  382. var oldRoot = this.GetVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
  383. var newRoot = default(IRenderRoot);
  384. if (value != null)
  385. {
  386. newRoot = value.GetSelfAndVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
  387. }
  388. _visualParent = value;
  389. if (oldRoot != null)
  390. {
  391. NotifyDetachedFromVisualTree(oldRoot);
  392. }
  393. if (newRoot != null)
  394. {
  395. NotifyAttachedToVisualTree(newRoot);
  396. }
  397. RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue);
  398. }
  399. }
  400. /// <summary>
  401. /// Called when the <see cref="_visualChildren"/> collection changes.
  402. /// </summary>
  403. /// <param name="sender">The sender.</param>
  404. /// <param name="e">The event args.</param>
  405. private void VisualChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
  406. {
  407. switch (e.Action)
  408. {
  409. case NotifyCollectionChangedAction.Add:
  410. foreach (Visual v in e.NewItems)
  411. {
  412. v.InheritanceParent = this;
  413. v.SetVisualParent(this);
  414. }
  415. break;
  416. case NotifyCollectionChangedAction.Remove:
  417. foreach (Visual v in e.OldItems)
  418. {
  419. v.InheritanceParent = null;
  420. v.SetVisualParent(null);
  421. }
  422. break;
  423. }
  424. }
  425. /// <summary>
  426. /// Calls the <see cref="OnAttachedToVisualTree(IRenderRoot)"/> method for this control
  427. /// and all of its visual descendents.
  428. /// </summary>
  429. /// <param name="root">The root of the visual tree.</param>
  430. private void NotifyAttachedToVisualTree(IRenderRoot root)
  431. {
  432. _visualLogger.Verbose("Attached to visual tree");
  433. _isAttachedToVisualTree = true;
  434. OnAttachedToVisualTree(root);
  435. if (_visualChildren != null)
  436. {
  437. foreach (Visual child in _visualChildren.OfType<Visual>())
  438. {
  439. child.NotifyAttachedToVisualTree(root);
  440. }
  441. }
  442. }
  443. /// <summary>
  444. /// Calls the <see cref="OnDetachedFromVisualTree(IRenderRoot)"/> method for this control
  445. /// and all of its visual descendents.
  446. /// </summary>
  447. /// <param name="root">The root of the visual tree.</param>
  448. private void NotifyDetachedFromVisualTree(IRenderRoot root)
  449. {
  450. _visualLogger.Verbose("Detached from visual tree");
  451. _isAttachedToVisualTree = false;
  452. OnDetachedFromVisualTree(root);
  453. if (_visualChildren != null)
  454. {
  455. foreach (Visual child in _visualChildren.OfType<Visual>())
  456. {
  457. child.NotifyDetachedFromVisualTree(root);
  458. }
  459. }
  460. }
  461. }
  462. }