public void GetDescendantsDepthFirst() { var controlA = new UIControl(); var controlB = new UIControl(); var controlC = new UIControl(); var controlD = new UIControl(); var controlE = new UIControl(); var controlF = new UIControl(); var controlG = new UIControl(); controlA.VisualChildren.Add(controlB); controlA.VisualChildren.Add(controlC); controlA.VisualChildren.Add(controlD); controlD.VisualChildren.Add(controlE); controlD.VisualChildren.Add(controlF); controlE.VisualChildren.Add(controlG); Assert.That(() => UIHelper.GetDescendants(null), Throws.TypeOf<ArgumentNullException>()); var descendants = controlA.GetDescendants().ToArray(); Assert.AreEqual(6, descendants.Length); Assert.AreEqual(controlB, descendants[0]); Assert.AreEqual(controlC, descendants[1]); Assert.AreEqual(controlD, descendants[2]); Assert.AreEqual(controlE, descendants[3]); Assert.AreEqual(controlG, descendants[4]); Assert.AreEqual(controlF, descendants[5]); }
public void RenderTreeViewItem(UIControl control, UIRenderContext context) { var treeViewItem = control as TreeViewItem; if (treeViewItem != null && treeViewItem.IsSelected && treeViewItem.Header != null) { // Draw a blue rectangle behind selected tree view items. context.RenderTransform.Draw( SpriteBatch, WhiteTexture, treeViewItem.Header.ActualBounds, null, Color.CornflowerBlue); } // Call the default render callback to draw all the rest. RenderCallbacks["UIControl"](control, context); }
/// <summary> /// Called when the <see cref="Content"/> was exchanged. /// </summary> /// <param name="newContent">The new content.</param> /// <param name="oldContent">The old content.</param> protected virtual void OnContentChanged(UIControl newContent, UIControl oldContent) { if (oldContent != null) { VisualChildren.Remove(oldContent); } if (newContent != null) { // Apply ContentStyle to Content. if (IsLoaded && !string.IsNullOrEmpty(ContentStyle)) { newContent.Style = ContentStyle; } VisualChildren.Add(newContent); } InvalidateMeasure(); }
public void GetAncestors() { var controlA = new UIControl(); var controlB = new UIControl(); var controlC = new UIControl(); var controlD = new UIControl(); var controlE = new UIControl(); var controlF = new UIControl(); var controlG = new UIControl(); controlA.VisualChildren.Add(controlB); controlA.VisualChildren.Add(controlC); controlA.VisualChildren.Add(controlD); controlD.VisualChildren.Add(controlE); controlD.VisualChildren.Add(controlF); controlE.VisualChildren.Add(controlG); Assert.That(() => UIHelper.GetAncestors(null), Throws.TypeOf<ArgumentNullException>()); var ancestors = controlE.GetAncestors().ToArray(); Assert.AreEqual(2, ancestors.Length); Assert.AreEqual(controlD, ancestors[0]); Assert.AreEqual(controlA, ancestors[1]); }
/// <summary> /// Opens this <see cref="ContextMenu"/> (adds it to the <see cref="UIScreen"/>). /// </summary> /// <param name="owner">The control that opened this context menu.</param> /// <param name="position"> /// The position of the mouse cursor - or where the context menu should be opened. /// (<see cref="Offset"/> will be applied to this position.) /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="owner"/> is <see langword="null"/>. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="owner"/> is not loaded. The owner needs to be a visible control. /// </exception> public void Open(UIControl owner, Vector2F position) { if (owner == null) { throw new ArgumentNullException("owner"); } var screen = owner.Screen; if (screen == null) { throw new ArgumentException("Invalid owner. The owner must be loaded.", "owner"); } if (!IsEnabled) { return; } // Close if already opened. Close(); Owner = owner; // Make visible and add to screen. IsVisible = true; screen.Children.Add(this); // Choose position near the given position. // The context menu is positioned so that it fits onto the screen. Measure(new Vector2F(float.PositiveInfinity)); float x = position.X; if (x + DesiredWidth > screen.ActualWidth) { // Show context menu on the left. x = x - DesiredWidth; } float offset = Offset; float y = position.Y + offset; if (y + DesiredHeight > screen.ActualHeight) { // Show context menu on top. // (We use 0.5 * offset if context menu is above cursor. This assumes that the cursor // is similar to the typical arrow where the hot spot is at the top. If the cursor is a // different shape we might need to adjust the offset.) y = y - DesiredHeight - 1.5f * offset; } X = x; Y = y; #if WP7 || PORTABLE #if PORTABLE if (GlobalSettings.PlatformID == PlatformID.WindowsPhone8) #endif { // Imitate position of Silverlight WP7 context menus. if (screen.ActualHeight >= screen.ActualWidth) { // Portrait mode. X = 0; HorizontalAlignment = HorizontalAlignment.Stretch; VerticalAlignment = VerticalAlignment.Top; } else { // Landscape mode. Y = 0; Width = screen.ActualWidth / 2; HorizontalAlignment = HorizontalAlignment.Left; VerticalAlignment = VerticalAlignment.Stretch; if (position.X <= screen.ActualWidth / 2) { // Show context menu on right half of the screen. X = screen.ActualWidth / 2; } else { // Show context menu on the left half of the screen. X = 0; } } } #endif screen.FocusManager.Focus(this); IsOpen = true; }
/// <summary> /// Opens this <see cref="ContextMenu"/> (adds it to the <see cref="UIScreen"/>). /// </summary> /// <param name="owner">The control that opened this context menu.</param> /// <param name="position"> /// The position of the mouse cursor - or where the context menu should be opened. /// (<see cref="Offset"/> will be applied to this position.) /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="owner"/> is <see langword="null"/>. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="owner"/> is not loaded. The owner needs to be a visible control. /// </exception> public void Open(UIControl owner, Vector2F position) { if (owner == null) throw new ArgumentNullException("owner"); var screen = owner.Screen; if (screen == null) throw new ArgumentException("Invalid owner. The owner must be loaded.", "owner"); if (!IsEnabled) return; // Close if already opened. Close(); Owner = owner; // Make visible and add to screen. IsVisible = true; screen.Children.Add(this); // Choose position near the given position. // The context menu is positioned so that it fits onto the screen. Measure(new Vector2F(float.PositiveInfinity)); float x = position.X; if (x + DesiredWidth > screen.ActualWidth) { // Show context menu on the left. x = x - DesiredWidth; } float offset = Offset; float y = position.Y + offset; if (y + DesiredHeight > screen.ActualHeight) { // Show context menu on top. // (We use 0.5 * offset if context menu is above cursor. This assumes that the cursor // is similar to the typical arrow where the hot spot is at the top. If the cursor is a // different shape we might need to adjust the offset.) y = y - DesiredHeight - 1.5f * offset; } X = x; Y = y; #if WP7 || PORTABLE #if PORTABLE if (GlobalSettings.PlatformID == PlatformID.WindowsPhone8) #endif { // Imitate position of Silverlight WP7 context menus. if (screen.ActualHeight >= screen.ActualWidth) { // Portrait mode. X = 0; HorizontalAlignment = HorizontalAlignment.Stretch; VerticalAlignment = VerticalAlignment.Top; } else { // Landscape mode. Y = 0; Width = screen.ActualWidth / 2; HorizontalAlignment = HorizontalAlignment.Left; VerticalAlignment = VerticalAlignment.Stretch; if (position.X <= screen.ActualWidth / 2) { // Show context menu on right half of the screen. X = screen.ActualWidth / 2; } else { // Show context menu on the left half of the screen. X = 0; } } } #endif screen.FocusManager.Focus(this); IsOpen = true; }
/// <summary> /// Moves focus to a control. /// </summary> /// <param name="control"> /// The control that should get the focus. (If <paramref name="control"/> is /// <see langword="null"/>, this method does nothing. Use <see cref="ClearFocus"/> to remove the /// focus from a control.) /// </param> /// <returns> /// <see langword="true"/> if the focus was moved; otherwise <see langword="false"/>. /// </returns> /// <remarks> /// Disabled or invisible controls cannot be focused. If the <paramref name="control"/> is not /// itself <see cref="UIControl.Focusable"/>, the method tries to focus a child control. /// </remarks> public bool Focus(UIControl control) { if (control == null) return false; // Cannot focus a disabled or invisible control. if (!control.ActualIsEnabled || !control.ActualIsVisible) return false; if (!control.Focusable) { if (control.IsFocusScope) { // Try to move focus to the last focused control. var lastFocusedControl = control.GetValue<UIControl>(LastFocusedControlPropertyId); if (Focus(lastFocusedControl) && control.IsFocusWithin) return true; } // Control is not focusable. --> Try to focus a child. foreach (var child in control.VisualChildren) { if (Focus(child)) return true; } return false; } #if !WP7 #if PORTABLE if (GlobalSettings.PlatformID != PlatformID.WindowsPhone8) #endif { // Move control to visible area. On phone this is typically not done to avoid non-smooth // scrolling. control.BringIntoView(); } #endif // Abort if control is already focused. // (Note: We do the check here and not at the beginning of the method because we still want // to do the things above to bring the control into the view, even if the control already has // focus.) if (control.IsFocused) return true; // First, clear the current IsFocused and IsFocusWithin flags. ClearFocus(); // Set IsFocused and IsFocusWithin flags in FocusedControl and all ancestors. FocusedControl = control; FocusedControl.IsFocused = true; FocusScope = null; while (control != null) { if (control.IsFocusScope) { // Remember the first focus scope that we find. if (FocusScope == null) FocusScope = control; // Focus scope remembers last focused control. control.SetValue(LastFocusedControlPropertyId, FocusedControl); } control.IsFocusWithin = true; control = control.VisualParent; } return true; }
/// <inheritdoc/> protected override bool HitTest(UIControl control, Vector2F position) { // If control is the child and ClipContent is enabled, then the mouse must be within the // ContentBounds. (The child part that is outside the ContentBounds is invisible and cannot // be clicked.) if (control != null && control == Content && ClipContent) { return ContentBounds.Contains(position); } return base.HitTest(control, position); }
//-------------------------------------------------------------- private void OnScreenInputProcessed(object sender, InputEventArgs eventArgs) { // Automatically hide the ToolTip if the Screen does not process input (because then // we would not see mouse movement, so we close it immediately). // Or close it if another UIControl was added on top that could hide the ToolTip. if (IsToolTipOpen) { if (!Screen.InputEnabled // Screen does not handle input. || Screen.Children[Screen.Children.Count - 1] != ToolTipControl) // Another window was opened. { // ----- Screen was disabled, or another control was shown on top. CloseToolTip(); return; } } var context = eventArgs.Context; if (context.MousePositionDelta == Vector2F.Zero) { // ----- No mouse movement --> Increase counter. _noMouseMoveDuration += context.DeltaTime; } else { // ----- Mouse moved --> Close tool tip if mouse is no longer over control. Reset counter. if (_control == null || !_control.IsMouseOver) CloseToolTip(); _noMouseMoveDuration = TimeSpan.Zero; return; } if (_noMouseMoveDuration >= ToolTipDelay) { // ----- Mouse was not moving for ToolTipDelay seconds --> Show tool tip. // Get control under the mouse cursor. Search up the control hierarchy until we // find a control with a tool tip string. var control = Screen.ControlUnderMouse; while (control != null && control.ToolTip == null) control = control.VisualParent; if (control != null) { // Show or update tool tip. ShowToolTip(control.ToolTip, context.MousePosition); _control = control; } // We do not want to check on the same position again in the next frame, so we set // an extreme value that will be automatically reset when the mouse moves in the future. _noMouseMoveDuration = TimeSpan.MinValue; } }
/// <summary> /// Hides the tool tip or does nothing if no tool tip is visible. /// </summary> public void CloseToolTip() { ToolTipControl.Content = null; ToolTipControl.IsVisible = false; _control = null; }
/// <summary> /// Opens a window and returns without waiting for the newly opened window to close. /// </summary> /// <param name="owner"> /// The owner of this window. If this window is closed, the focus moves back to the owner. Must /// not be <see langword="null"/>. /// </param> /// <remarks> /// The window is added to the <see cref="UIScreen"/> of the <paramref name="owner"/> /// (unless it was already added to a screen) and activated (see <see cref="Activate"/>). /// <see cref="DialogResult"/> is reset to <see langword="null"/>. /// </remarks> /// <exception cref="ArgumentNullException"> /// <paramref name="owner"/> is <see langword="null"/>. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="owner"/> is not loaded. The owner needs to be a visible control. /// </exception> public void Show(UIControl owner) { if (owner == null) throw new ArgumentNullException("owner"); var screen = owner.Screen; if (screen == null) throw new ArgumentException("Invalid owner. Owner must be loaded.", "owner"); Owner = owner; // Make visible and add to screen. IsVisible = true; if (VisualParent == null) screen.Children.Add(this); Activate(); #if !WINDOWS_UWP DialogResult = null; #else DialogResult = false; #endif }
/// <summary> /// Gets the window that contains the given <paramref name="control"/>. /// </summary> /// <param name="control">The control.</param> /// <returns> /// The window that contains the <paramref name="control"/>, or <see langword="null"/> if /// the control is not part of a window (controls can be direct children of the screen, no /// intermediate window is required). /// </returns> public static Window GetWindow(UIControl control) { while (control != null) { var window = control as Window; if (window != null) return window; control = control.VisualParent; } return null; }
/// <summary> /// Recursively collects all focusable controls of the same focus scope. /// </summary> /// <param name="control">The control.</param> /// <remarks> /// The focusable controls are stored in _focusableControls. <see cref="FocusedControl"/> will /// be excluded from the list. /// </remarks> private void GetFocusableControls(UIControl control) { if (control == FocusedControl) return; if (control.Focusable && control.IsEnabled && control.IsVisible) { _focusableControls.Add(control); } else { foreach (var child in control.VisualChildren) if (child.IsEnabled && child.IsVisible) GetFocusableControls(child); } }
internal void MoveFocus(UIControl control, LogicalPlayerIndex allowedPlayer) { // Called by the UIControl after UIControl.OnHandleInput. if (control == null) return; var inputService = InputService; // Handle AutoUnfocus. if (control.AutoUnfocus && FocusedControl != null) { if (inputService.IsPressed(MouseButtons.Left, false) || inputService.IsPressed(MouseButtons.Right, false)) { if (control == FocusScope && !FocusedControl.IsMouseOver // E.g. a window and not the focused control is clicked. || control.IsMouseDirectlyOver && !inputService.IsMouseOrTouchHandled) // E.g. a screen and no child UIControl is clicked. { ClearFocus(); } } } // Only focus scopes handle focus movement. // And only the current focus scope (which means the first focus scope in the hierarchy). if (!control.IsFocusScope || FocusScope != null && control != FocusScope) return; bool moveLeft = false; bool moveRight = false; bool moveUp = false; bool moveDown = false; // Check arrow keys. if (!inputService.IsKeyboardHandled) { // This focus scope "absorbs" arrow keys. if (inputService.IsDown(Keys.Left) || inputService.IsDown(Keys.Right) || inputService.IsDown(Keys.Up) || inputService.IsDown(Keys.Down)) { inputService.IsKeyboardHandled = true; if (inputService.IsPressed(Keys.Left, true)) moveLeft = true; else if (inputService.IsPressed(Keys.Right, true)) moveRight = true; else if (inputService.IsPressed(Keys.Up, true)) moveUp = true; else if (inputService.IsPressed(Keys.Down, true)) moveDown = true; } } #if !SILVERLIGHT // Check left thumb stick and d-pad. if (!moveLeft && !moveRight && !moveUp && !moveDown && !inputService.IsGamePadHandled(allowedPlayer)) { if (inputService.IsDown(Buttons.LeftThumbstickLeft, allowedPlayer) || inputService.IsDown(Buttons.LeftThumbstickRight, allowedPlayer) || inputService.IsDown(Buttons.LeftThumbstickUp, allowedPlayer) || inputService.IsDown(Buttons.LeftThumbstickDown, allowedPlayer) || inputService.IsDown(Buttons.DPadLeft, allowedPlayer) || inputService.IsDown(Buttons.DPadRight, allowedPlayer) || inputService.IsDown(Buttons.DPadUp, allowedPlayer) || inputService.IsDown(Buttons.DPadDown, allowedPlayer)) { inputService.SetGamePadHandled(allowedPlayer, true); if (inputService.IsPressed(Buttons.LeftThumbstickLeft, true, allowedPlayer) || inputService.IsPressed(Buttons.DPadLeft, true, allowedPlayer)) moveLeft = true; else if (inputService.IsPressed(Buttons.LeftThumbstickRight, true, allowedPlayer) || inputService.IsPressed(Buttons.DPadRight, true, allowedPlayer)) moveRight = true; else if (inputService.IsPressed(Buttons.LeftThumbstickUp, true, allowedPlayer) || inputService.IsPressed(Buttons.DPadUp, true, allowedPlayer)) moveUp = true; else if (inputService.IsPressed(Buttons.LeftThumbstickDown, true, allowedPlayer) || inputService.IsPressed(Buttons.DPadDown, true, allowedPlayer)) moveDown = true; } } #endif // Now, we know in which direction the focus should move. if (!moveLeft && !moveRight && !moveUp && !moveDown) return; // Collect all focusable controls. _focusableControls.Clear(); GetFocusableControls(control); if (_focusableControls.Count == 0) return; // Call virtual method that does the job. var target = OnMoveFocus(moveLeft, moveRight, moveUp, moveDown, _focusableControls); Focus(target); _focusableControls.Clear(); }
/// <summary> /// Brings a control to the front of the z-order. /// </summary> /// <param name="control">The control.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="control"/> is <see langword="null"/>. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="control"/> is not a child of this UI screen. /// </exception> public void BringToFront(UIControl control) { if (control == null) throw new ArgumentNullException("control"); int index = Children.IndexOf(control); if (index == -1) throw new ArgumentException("control is not a child of this UI screen."); Children.Move(index, Children.Count - 1); }
/// <summary> /// Called when the <see cref="Content"/> was exchanged. /// </summary> /// <param name="newContent">The new content.</param> /// <param name="oldContent">The old content.</param> protected virtual void OnContentChanged(UIControl newContent, UIControl oldContent) { if (oldContent != null) VisualChildren.Remove(oldContent); if (newContent != null) { // Apply ContentStyle to Content. if (IsLoaded && !string.IsNullOrEmpty(ContentStyle)) newContent.Style = ContentStyle; VisualChildren.Add(newContent); } InvalidateMeasure(); }
/// <summary> /// Changes <see cref="HorizontalOffset"/> and <see cref="VerticalOffset"/> such that the /// <paramref name="control"/> is visible in the viewport. /// </summary> /// <param name="control">The control.</param> internal void BringIntoView(UIControl control) { if (!control.IsArrangeValid) control.UpdateLayout(); BringIntoView(control.ActualBounds); }
public void GetRoot() { var controlA = new UIControl(); var controlB = new UIControl(); var controlC = new UIControl(); var controlD = new UIControl(); var controlE = new UIControl(); var controlF = new UIControl(); var controlG = new UIControl(); controlA.VisualChildren.Add(controlB); controlA.VisualChildren.Add(controlC); controlA.VisualChildren.Add(controlD); controlD.VisualChildren.Add(controlE); controlD.VisualChildren.Add(controlF); controlE.VisualChildren.Add(controlG); Assert.That(() => UIHelper.GetRoot(null), Throws.TypeOf<ArgumentNullException>()); Assert.AreEqual(controlA, controlA.GetRoot()); Assert.AreEqual(controlA, controlG.GetRoot()); }
/// <summary> /// Arranges the specified control considering the horizontal and vertical alignment of the /// given control. /// </summary> /// <param name="control">The control.</param> /// <param name="position">The position.</param> /// <param name="constraintSize">The constraint size.</param> internal static void Arrange(UIControl control, Vector2F position, Vector2F constraintSize) { if (control == null) return; Vector2F childPosition = position; Vector2F childSize = constraintSize; switch (control.HorizontalAlignment) { case HorizontalAlignment.Left: childPosition.X = position.X + control.X; childSize.X = control.DesiredWidth; break; case HorizontalAlignment.Center: childPosition.X = position.X + constraintSize.X / 2 - control.DesiredWidth / 2; childSize.X = control.DesiredWidth; break; case HorizontalAlignment.Right: childPosition.X = position.X + constraintSize.X - control.DesiredWidth; childSize.X = control.DesiredWidth; break; default: // HorizontalAlignment.Stretch break; } switch (control.VerticalAlignment) { case VerticalAlignment.Top: childPosition.Y = position.Y + control.Y; childSize.Y = control.DesiredHeight; break; case VerticalAlignment.Center: childPosition.Y = position.Y + constraintSize.Y / 2 - control.DesiredHeight / 2; childSize.Y = control.DesiredHeight; break; case VerticalAlignment.Bottom: childPosition.Y = position.Y + constraintSize.Y - control.DesiredHeight; childSize.Y = control.DesiredHeight; break; default: // VerticalAlignment.Stretch break; } control.Arrange(childPosition, childSize); }