TopLevel.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. // Copyright (c) The Avalonia 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.Reactive.Disposables;
  5. using System.Reactive.Linq;
  6. using Avalonia.Controls.Platform;
  7. using Avalonia.Controls.Primitives;
  8. using Avalonia.Input;
  9. using Avalonia.Input.Raw;
  10. using Avalonia.Layout;
  11. using Avalonia.Logging;
  12. using Avalonia.Platform;
  13. using Avalonia.Rendering;
  14. using Avalonia.Styling;
  15. using Avalonia.VisualTree;
  16. namespace Avalonia.Controls
  17. {
  18. /// <summary>
  19. /// Base class for top-level windows.
  20. /// </summary>
  21. /// <remarks>
  22. /// This class acts as a base for top level windows such as <see cref="Window"/> and
  23. /// <see cref="PopupRoot"/>. It handles scheduling layout, styling and rendering as well as
  24. /// tracking the window <see cref="ClientSize"/> and <see cref="IsActive"/> state.
  25. /// </remarks>
  26. public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleRoot
  27. {
  28. /// <summary>
  29. /// Defines the <see cref="ClientSize"/> property.
  30. /// </summary>
  31. public static readonly DirectProperty<TopLevel, Size> ClientSizeProperty =
  32. AvaloniaProperty.RegisterDirect<TopLevel, Size>(nameof(ClientSize), o => o.ClientSize);
  33. /// <summary>
  34. /// Defines the <see cref="IsActive"/> property.
  35. /// </summary>
  36. public static readonly DirectProperty<TopLevel, bool> IsActiveProperty =
  37. AvaloniaProperty.RegisterDirect<TopLevel, bool>(nameof(IsActive), o => o.IsActive);
  38. /// <summary>
  39. /// Defines the <see cref="IInputRoot.PointerOverElement"/> property.
  40. /// </summary>
  41. public static readonly StyledProperty<IInputElement> PointerOverElementProperty =
  42. AvaloniaProperty.Register<TopLevel, IInputElement>(nameof(IInputRoot.PointerOverElement));
  43. private readonly IInputManager _inputManager;
  44. private readonly IAccessKeyHandler _accessKeyHandler;
  45. private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
  46. private readonly IApplicationLifecycle _applicationLifecycle;
  47. private readonly IPlatformRenderInterface _renderInterface;
  48. private Size _clientSize;
  49. private bool _isActive;
  50. /// <summary>
  51. /// Initializes static members of the <see cref="TopLevel"/> class.
  52. /// </summary>
  53. static TopLevel()
  54. {
  55. AffectsMeasure(ClientSizeProperty);
  56. }
  57. /// <summary>
  58. /// Initializes a new instance of the <see cref="TopLevel"/> class.
  59. /// </summary>
  60. /// <param name="impl">The platform-specific window implementation.</param>
  61. public TopLevel(ITopLevelImpl impl)
  62. : this(impl, AvaloniaLocator.Current)
  63. {
  64. }
  65. /// <summary>
  66. /// Initializes a new instance of the <see cref="TopLevel"/> class.
  67. /// </summary>
  68. /// <param name="impl">The platform-specific window implementation.</param>
  69. /// <param name="dependencyResolver">
  70. /// The dependency resolver to use. If null the default dependency resolver will be used.
  71. /// </param>
  72. public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver dependencyResolver)
  73. {
  74. if (impl == null)
  75. {
  76. throw new InvalidOperationException(
  77. "Could not create window implementation: maybe no windowing subsystem was initialized?");
  78. }
  79. PlatformImpl = impl;
  80. dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current;
  81. var styler = TryGetService<IStyler>(dependencyResolver);
  82. _accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
  83. _inputManager = TryGetService<IInputManager>(dependencyResolver);
  84. _keyboardNavigationHandler = TryGetService<IKeyboardNavigationHandler>(dependencyResolver);
  85. _applicationLifecycle = TryGetService<IApplicationLifecycle>(dependencyResolver);
  86. _renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
  87. var renderLoop = TryGetService<IRenderLoop>(dependencyResolver);
  88. Renderer = TryGetService<IRenderer>(dependencyResolver);
  89. Renderer?.Attach(this, renderLoop);
  90. PlatformImpl.SetInputRoot(this);
  91. PlatformImpl.Activated = HandleActivated;
  92. PlatformImpl.Deactivated = HandleDeactivated;
  93. PlatformImpl.Closed = HandleClosed;
  94. PlatformImpl.Input = HandleInput;
  95. PlatformImpl.Paint = Renderer != null ? (Action<Rect>)Renderer.Render : null;
  96. PlatformImpl.Resized = HandleResized;
  97. PlatformImpl.ScalingChanged = HandleScalingChanged;
  98. PlatformImpl.PositionChanged = HandlePositionChanged;
  99. _keyboardNavigationHandler?.SetOwner(this);
  100. _accessKeyHandler?.SetOwner(this);
  101. styler?.ApplyStyles(this);
  102. ClientSize = PlatformImpl.ClientSize;
  103. this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x);
  104. this.GetObservable(PointerOverElementProperty)
  105. .Select(
  106. x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
  107. .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor));
  108. if (_applicationLifecycle != null)
  109. {
  110. _applicationLifecycle.OnExit += OnApplicationExiting;
  111. }
  112. }
  113. /// <summary>
  114. /// Fired when the window is activated.
  115. /// </summary>
  116. public event EventHandler Activated;
  117. /// <summary>
  118. /// Fired when the window is closed.
  119. /// </summary>
  120. public event EventHandler Closed;
  121. /// <summary>
  122. /// Fired when the window is deactivated.
  123. /// </summary>
  124. public event EventHandler Deactivated;
  125. /// <summary>
  126. /// Fired when the window position is changed.
  127. /// </summary>
  128. public event EventHandler<PointEventArgs> PositionChanged;
  129. /// <summary>
  130. /// Gets or sets the client size of the window.
  131. /// </summary>
  132. public Size ClientSize
  133. {
  134. get { return _clientSize; }
  135. private set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); }
  136. }
  137. /// <summary>
  138. /// Gets a value that indicates whether the window is active.
  139. /// </summary>
  140. public bool IsActive
  141. {
  142. get { return _isActive; }
  143. private set { SetAndRaise(IsActiveProperty, ref _isActive, value); }
  144. }
  145. /// <summary>
  146. /// Gets or sets the window position in screen coordinates.
  147. /// </summary>
  148. public Point Position
  149. {
  150. get { return PlatformImpl.Position; }
  151. set { PlatformImpl.Position = value; }
  152. }
  153. /// <summary>
  154. /// Gets the platform-specific window implementation.
  155. /// </summary>
  156. public ITopLevelImpl PlatformImpl
  157. {
  158. get;
  159. }
  160. /// <summary>
  161. /// Gets the renderer for the window.
  162. /// </summary>
  163. public IRenderer Renderer { get; }
  164. /// <summary>
  165. /// Gets the access key handler for the window.
  166. /// </summary>
  167. IAccessKeyHandler IInputRoot.AccessKeyHandler => _accessKeyHandler;
  168. /// <summary>
  169. /// Gets or sets the keyboard navigation handler for the window.
  170. /// </summary>
  171. IKeyboardNavigationHandler IInputRoot.KeyboardNavigationHandler => _keyboardNavigationHandler;
  172. /// <summary>
  173. /// Gets or sets the input element that the pointer is currently over.
  174. /// </summary>
  175. IInputElement IInputRoot.PointerOverElement
  176. {
  177. get { return GetValue(PointerOverElementProperty); }
  178. set { SetValue(PointerOverElementProperty, value); }
  179. }
  180. /// <summary>
  181. /// Gets or sets a value indicating whether access keys are shown in the window.
  182. /// </summary>
  183. bool IInputRoot.ShowAccessKeys
  184. {
  185. get { return GetValue(AccessText.ShowAccessKeyProperty); }
  186. set { SetValue(AccessText.ShowAccessKeyProperty, value); }
  187. }
  188. /// <inheritdoc/>
  189. Size ILayoutRoot.MaxClientSize => Size.Infinity;
  190. /// <inheritdoc/>
  191. double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling;
  192. IStyleHost IStyleHost.StylingParent
  193. {
  194. get { return AvaloniaLocator.Current.GetService<IGlobalStyles>(); }
  195. }
  196. /// <summary>
  197. /// Whether an auto-size operation is in progress.
  198. /// </summary>
  199. protected bool AutoSizing
  200. {
  201. get;
  202. private set;
  203. }
  204. /// <inheritdoc/>
  205. IRenderTarget IRenderRoot.CreateRenderTarget()
  206. {
  207. return _renderInterface.CreateRenderer(PlatformImpl.Handle);
  208. }
  209. /// <inheritdoc/>
  210. void IRenderRoot.Invalidate(Rect rect)
  211. {
  212. PlatformImpl.Invalidate(rect);
  213. }
  214. /// <inheritdoc/>
  215. Point IRenderRoot.PointToClient(Point p)
  216. {
  217. return PlatformImpl.PointToClient(p);
  218. }
  219. /// <inheritdoc/>
  220. Point IRenderRoot.PointToScreen(Point p)
  221. {
  222. return PlatformImpl.PointToScreen(p);
  223. }
  224. /// <summary>
  225. /// Activates the window.
  226. /// </summary>
  227. public void Activate()
  228. {
  229. PlatformImpl.Activate();
  230. }
  231. /// <summary>
  232. /// Begins an auto-resize operation.
  233. /// </summary>
  234. /// <returns>A disposable used to finish the operation.</returns>
  235. /// <remarks>
  236. /// When an auto-resize operation is in progress any resize events received will not be
  237. /// cause the new size to be written to the <see cref="Layoutable.Width"/> and
  238. /// <see cref="Layoutable.Height"/> properties.
  239. /// </remarks>
  240. protected IDisposable BeginAutoSizing()
  241. {
  242. AutoSizing = true;
  243. return Disposable.Create(() => AutoSizing = false);
  244. }
  245. /// <summary>
  246. /// Carries out the arrange pass of the window.
  247. /// </summary>
  248. /// <param name="finalSize">The final window size.</param>
  249. /// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
  250. protected override Size ArrangeOverride(Size finalSize)
  251. {
  252. using (BeginAutoSizing())
  253. {
  254. PlatformImpl.ClientSize = finalSize;
  255. }
  256. return base.ArrangeOverride(PlatformImpl.ClientSize);
  257. }
  258. /// <summary>
  259. /// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
  260. /// </summary>
  261. /// <param name="clientSize">The new client size.</param>
  262. protected virtual void HandleResized(Size clientSize)
  263. {
  264. if (!AutoSizing)
  265. {
  266. Width = clientSize.Width;
  267. Height = clientSize.Height;
  268. }
  269. ClientSize = clientSize;
  270. LayoutManager.Instance.ExecuteLayoutPass();
  271. PlatformImpl.Invalidate(new Rect(clientSize));
  272. }
  273. /// <summary>
  274. /// Handles a window scaling change notification from
  275. /// <see cref="ITopLevelImpl.ScalingChanged"/>.
  276. /// </summary>
  277. /// <param name="scaling">The window scaling.</param>
  278. protected virtual void HandleScalingChanged(double scaling)
  279. {
  280. foreach (ILayoutable control in this.GetSelfAndVisualDescendents())
  281. {
  282. control.InvalidateMeasure();
  283. }
  284. }
  285. /// <inheritdoc/>
  286. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  287. {
  288. base.OnAttachedToVisualTree(e);
  289. throw new InvalidOperationException(
  290. $"Control '{GetType().Name}' is a top level control and cannot be added as a child.");
  291. }
  292. /// <summary>
  293. /// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a
  294. /// warning if not found.
  295. /// </summary>
  296. /// <typeparam name="T">The service type.</typeparam>
  297. /// <param name="resolver">The resolver.</param>
  298. /// <returns>The service.</returns>
  299. private T TryGetService<T>(IAvaloniaDependencyResolver resolver) where T : class
  300. {
  301. var result = resolver.GetService<T>();
  302. if (result == null)
  303. {
  304. Logger.Warning(
  305. LogArea.Control,
  306. this,
  307. "Could not create {Service} : maybe Application.RegisterServices() wasn't called?",
  308. typeof(T));
  309. }
  310. return result;
  311. }
  312. /// <summary>
  313. /// Handles an activated notification from <see cref="ITopLevelImpl.Activated"/>.
  314. /// </summary>
  315. private void HandleActivated()
  316. {
  317. Activated?.Invoke(this, EventArgs.Empty);
  318. var scope = this as IFocusScope;
  319. if (scope != null)
  320. {
  321. FocusManager.Instance.SetFocusScope(scope);
  322. }
  323. IsActive = true;
  324. }
  325. /// <summary>
  326. /// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>.
  327. /// </summary>
  328. private void HandleClosed()
  329. {
  330. Closed?.Invoke(this, EventArgs.Empty);
  331. _applicationLifecycle.OnExit -= OnApplicationExiting;
  332. }
  333. private void OnApplicationExiting(object sender, EventArgs args)
  334. {
  335. HandleApplicationExiting();
  336. }
  337. /// <summary>
  338. /// Handles the application exiting, either from the last window closing, or a call to <see cref="IApplicationLifecycle.Exit"/>.
  339. /// </summary>
  340. protected virtual void HandleApplicationExiting()
  341. {
  342. }
  343. /// <summary>
  344. /// Handles a deactivated notification from <see cref="ITopLevelImpl.Deactivated"/>.
  345. /// </summary>
  346. private void HandleDeactivated()
  347. {
  348. IsActive = false;
  349. Deactivated?.Invoke(this, EventArgs.Empty);
  350. }
  351. /// <summary>
  352. /// Handles input from <see cref="ITopLevelImpl.Input"/>.
  353. /// </summary>
  354. /// <param name="e">The event args.</param>
  355. private void HandleInput(RawInputEventArgs e)
  356. {
  357. _inputManager.ProcessInput(e);
  358. }
  359. /// <summary>
  360. /// Handles a window position change notification from
  361. /// <see cref="ITopLevelImpl.PositionChanged"/>.
  362. /// </summary>
  363. /// <param name="pos">The window position.</param>
  364. private void HandlePositionChanged(Point pos)
  365. {
  366. PositionChanged?.Invoke(this, new PointEventArgs(pos));
  367. }
  368. /// <summary>
  369. /// Starts moving a window with left button being held. Should be called from left mouse button press event handler
  370. /// </summary>
  371. public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag();
  372. /// <summary>
  373. /// Starts resizing a window. This function is used if an application has window resizing controls.
  374. /// Should be called from left mouse button press event handler
  375. /// </summary>
  376. public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge);
  377. }
  378. }