/// <summary> /// Moves the current focus in the focus chain. /// </summary> /// <param name="direction">The change in position, relative to the current position.</param> /// <param name="resetAnchor">If true, resets the selected text anchor.</param> /// <param name="isMoved">True upon return if the focus was moved.</param> public virtual void MoveFocus(int direction, bool resetAnchor, out bool isMoved) { ulong OldFocusHash = FocusHash; int OldFocusIndex = FocusChain.IndexOf(Focus); Debug.Assert(OldFocusIndex >= 0 && OldFocusIndex < FocusChain.Count); int NewFocusIndex = OldFocusIndex + direction; if (NewFocusIndex < 0) { NewFocusIndex = 0; } else if (NewFocusIndex >= FocusChain.Count) { NewFocusIndex = FocusChain.Count - 1; } Debug.Assert(NewFocusIndex >= 0 && NewFocusIndex < FocusChain.Count); if (OldFocusIndex != NewFocusIndex) { ChangeFocus(direction, OldFocusIndex, NewFocusIndex, resetAnchor, out bool IsRefreshed); isMoved = true; } else { isMoved = false; } Debug.Assert(isMoved || OldFocusHash == FocusHash); }
/// <summary> /// Moves the focus up or down. /// The starting point is the center of the area covered by the current focus. /// </summary> /// <param name="distance">The distance to cross.</param> /// <param name="resetAnchor">If true, resets the selected text anchor.</param> /// <param name="isMoved">True if the focus has changed.</param> public virtual void MoveFocusVertically(double distance, bool resetAnchor, out bool isMoved) { ulong OldFocusHash = FocusHash; int OldFocusIndex = FocusChain.IndexOf(Focus); int NewFocusIndex = -1; double BestVerticalDistance = 0; double BestHorizontalDistance = 0; FindClosestFocusVertical(distance, OldFocusIndex, ref NewFocusIndex, ref BestVerticalDistance, ref BestHorizontalDistance); // Always choose an extremum cell view if all are on the wrong side of the target. if (NewFocusIndex < 0) { NewFocusIndex = (distance < 0) ? 0 : FocusChain.Count - 1; } if (NewFocusIndex != OldFocusIndex) { Debug.Assert(NewFocusIndex >= MinFocusMove + OldFocusIndex && NewFocusIndex <= MaxFocusMove + OldFocusIndex); ChangeFocus(NewFocusIndex - OldFocusIndex, OldFocusIndex, NewFocusIndex, resetAnchor, out bool IsRefreshed); isMoved = true; } else { isMoved = false; } Debug.Assert(isMoved || OldFocusHash == FocusHash); }
/// <summary> /// Force the comment attached to the node with the focus to show, if empty, and move the focus to this comment. /// </summary> public virtual void ForceShowComment(out bool isMoved) { IFocusNodeState State = Focus.CellView.StateView.State; Document Documentation; if (State is IFocusOptionalNodeState AsOptionalNodeState) { Debug.Assert(AsOptionalNodeState.ParentInner.IsAssigned); Documentation = AsOptionalNodeState.Node.Documentation; } else { Documentation = State.Node.Documentation; } isMoved = false; ulong OldFocusHash = FocusHash; if (!(Focus is IFocusCommentFocus)) { string Text = CommentHelper.Get(Documentation); if (Text == null) { IFocusNodeStateView StateView = Focus.CellView.StateView; ForcedCommentStateView = StateView; Refresh(Controller.RootState); Debug.Assert(ForcedCommentStateView == null); for (int i = 0; i < FocusChain.Count; i++) { if (FocusChain[i] is IFocusCommentFocus AsCommentFocus && AsCommentFocus.CellView.StateView == StateView) { int OldFocusIndex = FocusChain.IndexOf(Focus); Debug.Assert(OldFocusIndex >= 0); // The old focus must have been preserved. int NewFocusIndex = i; ChangeFocus(NewFocusIndex - OldFocusIndex, OldFocusIndex, NewFocusIndex, true, out bool IsRefreshed); Debug.Assert(!IsRefreshed); // Refresh must not be done twice. isMoved = true; break; } } Debug.Assert(isMoved); } } if (isMoved) { ResetSelection(); } Debug.Assert(isMoved || OldFocusHash == FocusHash); }
/// <summary> /// Sets the focus to the visible cell view corresponding to a location. /// </summary> /// <param name="x">X-coordinate of the location where to set the focus.</param> /// <param name="y">Y-coordinate of the location where to set the focus.</param> /// <param name="resetAnchor">If true, resets the selected text anchor.</param> /// <param name="isMoved">True if the focus was moved.</param> public virtual void SetFocusToPoint(double x, double y, bool resetAnchor, out bool isMoved) { isMoved = false; ulong OldFocusHash = FocusHash; int OldIndex = FocusChain.IndexOf(Focus); int NewIndex = -1; // Takes page margins and padding into account. DrawContext.ToRelativeLocation(ref x, ref y); for (int i = 0; i < FocusChain.Count; i++) { ILayoutFocus TestFocus = (ILayoutFocus)FocusChain[i]; if (TestFocus.CellView.CellRect.IsPointInRect(x, y)) { NewIndex = i; break; } } if (NewIndex >= 0) { if (NewIndex != OldIndex) { ChangeFocus(NewIndex - OldIndex, OldIndex, NewIndex, resetAnchor, out bool IsRefreshed); } if (Focus is ILayoutTextFocus AsTextFocus) { Point CellOrigin = Focus.CellView.CellOrigin; Size CellSize = Focus.CellView.CellSize; double XRelativeToCell = x - CellOrigin.X.Draw; double YRelativeToCell = y - CellOrigin.Y.Draw; Debug.Assert(XRelativeToCell >= 0 && XRelativeToCell < CellSize.Width.Draw); Debug.Assert(YRelativeToCell >= 0 && YRelativeToCell < CellSize.Height.Draw); string Text = GetFocusedText(AsTextFocus); int NewCaretPosition = DrawContext.GetCaretPositionInText(XRelativeToCell, Text, FocusedTextStyle, CaretMode, Measure.Floating); Debug.Assert(NewCaretPosition >= 0 && NewCaretPosition <= MaxCaretPosition); SetTextCaretPosition(Text, NewCaretPosition, resetAnchor, out isMoved); } isMoved = true; } Debug.Assert(isMoved || OldFocusHash == FocusHash); }
/// <summary> /// Moves the focus to the beginning or end of a line. /// The starting point is the center of the area covered by the current focus. /// </summary> /// <param name="direction">-1 for the beginning of the line, +1 for the end.</param> /// <param name="resetAnchor">If true, resets the selected text anchor.</param> /// <param name="isMoved">True if the focus has changed.</param> public virtual void MoveFocusHorizontally(int direction, bool resetAnchor, out bool isMoved) { Debug.Assert(direction != 0); ulong OldFocusHash = FocusHash; int OldFocusIndex = FocusChain.IndexOf(Focus); int NewFocusIndex = -1; double BestSquaredDistance = 0; FindClosestFocusHorizontal(direction, ref NewFocusIndex, ref BestSquaredDistance); if (NewFocusIndex >= 0 && NewFocusIndex != OldFocusIndex) { Debug.Assert(NewFocusIndex >= MinFocusMove + OldFocusIndex && NewFocusIndex <= MaxFocusMove + OldFocusIndex); ChangeFocus(direction, OldFocusIndex, NewFocusIndex, resetAnchor, out bool IsRefreshed); isMoved = true; resetAnchor = true; } else { isMoved = false; } // Also move the caret. if (Focus is ILayoutTextFocus AsTextFocus) { string Text = GetFocusedText(AsTextFocus); bool IsCaretMoved; if (direction < 0) { SetTextCaretPosition(Text, 0, resetAnchor, out IsCaretMoved); } else { SetTextCaretPosition(Text, Text.Length, resetAnchor, out IsCaretMoved); } isMoved |= IsCaretMoved; } Debug.Assert(isMoved || OldFocusHash == FocusHash); }
private protected virtual void UpdateFocusChain(IFocusNodeState state, Node focusedNode, IFocusFrame focusedFrame) { FocusFocusList NewFocusChain = CreateFocusChain(); IFocusNodeState RootState = Controller.RootState; IFocusNodeStateView RootStateView = (IFocusNodeStateView)StateViewTable[RootState]; IFocusFocus MatchingFocus = null; RootStateView.UpdateFocusChain(NewFocusChain, focusedNode, focusedFrame, ref MatchingFocus); // Ensured by all templates having at least one preferred (hence focusable) frame. Debug.Assert(NewFocusChain.Count > 0); // First run, initialize the focus to the first focusable cell. if (Focus == null) { Debug.Assert(FocusChain == null); Focus = NewFocusChain[0]; ResetCaretPosition(0, true); } else if (MatchingFocus != null) { Focus = MatchingFocus; UpdateMaxCaretPosition(); // The focus didn't change, but the content may have. } else { RecoverFocus(state, NewFocusChain); // The focus has forcibly changed. } FocusChain = NewFocusChain; DebugObjects.AddReference(NewFocusChain); Debug.Assert(Focus != null); Debug.Assert(FocusChain.Contains(Focus)); SelectionAnchor = Focus.CellView.StateView; SelectionExtension = 0; }