/// <summary> /// Calculates the value to be used for layout rounding at high DPI. /// </summary> /// <param name="value">Input value to be rounded.</param> /// <param name="dpiScale">Ratio of screen's DPI to layout DPI</param> /// <returns>Adjusted value that will produce layout rounding on screen at high dpi.</returns> /// <remarks> /// This is a layout helper method. It takes DPI into account and also does not return /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper /// associated with the UseLayoutRounding property and should not be used as a general rounding /// utility. /// </remarks> public static double RoundLayoutValue(double value, double dpiScale) { double newValue; // If DPI == 1, don't use DPI-aware rounding. if (!MathUtilities.IsOne(dpiScale)) { newValue = Math.Round(value * dpiScale) / dpiScale; // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), // use the original value. if (double.IsNaN(newValue) || double.IsInfinity(newValue) || MathUtilities.AreClose(newValue, double.MaxValue)) { newValue = value; } } else { newValue = Math.Round(value); } return(newValue); }
public void Two_Equivalent_Double_Values_Are_Close() { var actual = MathUtilities.AreClose(AnyValue, _calculatedAnyValue); Assert.True(actual); Assert.Equal(AnyValue, Math.Round(_calculatedAnyValue, 14)); }
public bool CanShapeTogether(ShapeableTextCharacters shapeableTextCharacters) { if (!Text.Buffer.Equals(shapeableTextCharacters.Text.Buffer)) { return(false); } if (Text.Start + Text.Length != shapeableTextCharacters.Text.Start) { return(false); } if (BidiLevel != shapeableTextCharacters.BidiLevel) { return(false); } if (!MathUtilities.AreClose(Properties.FontRenderingEmSize, shapeableTextCharacters.Properties.FontRenderingEmSize)) { return(false); } if (Properties.Typeface != shapeableTextCharacters.Properties.Typeface) { return(false); } if (Properties.BaselineAlignment != shapeableTextCharacters.Properties.BaselineAlignment) { return(false); } return(true); }
public static bool operator ==(RelativeValue first, RelativeValue second) { if (first._reference == second._reference) { return(MathUtilities.AreClose(first._value, second._value)); } return(false); }
public static bool operator !=(RelativeValue first, RelativeValue second) { if (!(first._reference != second._reference)) { return(!MathUtilities.AreClose(first._value, second._value)); } return(true); }
/// <summary> /// Determines whether a column can be resized by dragging the border of its header. If star sizing /// is being used, there are special conditions that can prevent a column from being resized: /// 1. The column is the last visible column. /// 2. All columns are constrained by either their maximum or minimum values. /// </summary> /// <param name="column">Column to check.</param> /// <returns>Whether or not the column can be resized by dragging its header.</returns> private static bool CanResizeColumn(DataGridColumn column) { if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && column.OwningGrid.UsesStarSizing && (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !MathUtilities.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth))) { return(false); } return(column.ActualCanUserResize); }
public IEnumerable <Rect> HitTestTextRange(int start, int length) { if (start + length <= 0) { return(Array.Empty <Rect>()); } var result = new List <Rect>(TextLines.Count); var currentY = 0d; foreach (var textLine in TextLines) { //Current line isn't covered. if (textLine.FirstTextSourceIndex + textLine.Length < start) { currentY += textLine.Height; continue; } var textBounds = textLine.GetTextBounds(start, length); if (textBounds.Count > 0) { foreach (var bounds in textBounds) { Rect?last = result.Count > 0 ? result[result.Count - 1] : null; if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY)) { result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width); } else { result.Add(bounds.Rectangle.WithY(currentY)); } foreach (var runBounds in bounds.TextRunBounds) { start += runBounds.Length; length -= runBounds.Length; } } } if (textLine.FirstTextSourceIndex + textLine.Length >= start + length) { break; } currentY += textLine.Height; } return(result); }
public void Two_Equivalent_Single_Values_Are_Close() { var expectedValue = (float)AnyValue; var actualValue = (float)_calculatedAnyValue; var actual = MathUtilities.AreClose(expectedValue, actualValue); Assert.True(actual); Assert.Equal((float)Math.Round(expectedValue, 5), (float)Math.Round(actualValue, 4)); }
public override bool EvaluateLayout(DesignerView view, UIElement adorner) { BaseAdornerLayout.LayoutCache cache = BaseAdornerLayout.GetCache((DependencyObject)adorner); Matrix m1_1 = cache.ElementToDesignerViewTransformMatrix; Matrix m1_2 = cache.DesignerViewToViewportMatrix; bool flag = base.EvaluateLayout(view, adorner); if (!MathUtilities.AreClose(m1_1, cache.ElementToDesignerViewTransformMatrix) || !MathUtilities.AreClose(m1_2, cache.DesignerViewToViewportMatrix)) { this.SetupTransform(adorner); } return(flag); }
/// <summary> /// OnLayoutUpdated handler. Validates that all participating definitions /// have updated min size value. Forces another layout update cycle if needed. /// </summary> private void OnLayoutUpdated(object sender, EventArgs e) { double sharedMinSize = 0; // accumulate min size of all participating definitions for (int i = 0, count = _registry.Count; i < count; ++i) { sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); } bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); // compare accumulated min size with min sizes of the individual definitions for (int i = 0, count = _registry.Count; i < count; ++i) { DefinitionBase definitionBase = _registry[i]; if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) { // if definition's min size is different, then need to re-measure if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateMeasure(); definitionBase.UseSharedMinimum = true; } else { definitionBase.UseSharedMinimum = false; // if measure is valid then also need to check arrange. // Note: definitionBase.SizeCache is volatile but at this point // it contains up-to-date final size if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateArrange(); } } definitionBase.LayoutWasUpdated = false; } } _minSize = sharedMinSize; _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; _layoutUpdatedHost = null; _broadcastInvalidation = true; }
public object Convert(IList <object> values, Type targetType, object parameter, CultureInfo culture) { if (parameter == null || values == null || values.Count != 4 || !(values[0] is ScrollBarVisibility visiblity) || !(values[1] is double offset) || !(values[2] is double extent) || !(values[3] is double viewport)) { return(AvaloniaProperty.UnsetValue); } if (visiblity == ScrollBarVisibility.Auto) { if (extent == viewport) { return(false); } double target; if (parameter is double d) { target = d; } else if (parameter is string s) { target = double.Parse(s, NumberFormatInfo.InvariantInfo); } else { return(AvaloniaProperty.UnsetValue); } // Calculate the percent so that we can see if we are near the edge of the range double percent = MathUtilities.Clamp(offset * 100.0 / (extent - viewport), 0, 100); if (MathUtilities.AreClose(percent, target)) { // We are at the end of the range, so no need for this button to be shown return(false); } return(true); } return(false); }
//TODO Animation internal void EnsureDetailsContentHeight() { if ((_detailsElement != null) && (_detailsContent != null) && (double.IsNaN(_detailsContent.Height)) && (AreDetailsVisible) && (!double.IsNaN(_detailsDesiredHeight)) && !MathUtilities.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight) && Slot != -1) { _detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height; if (true) { _detailsElement.ContentHeight = _detailsDesiredHeight; } } }
/// <summary> /// Attempts to convert a DataGridLength instance to the given type. /// </summary> /// <param name="context"> /// An ITypeDescriptorContext that provides a format context. /// </param> /// <param name="culture"> /// The CultureInfo to use for the conversion. /// </param> /// <param name="value">The DataGridLength to convert.</param> /// <param name="destinationType">The type to which to convert the DataGridLength instance.</param> /// <returns> /// The object which was constructed. /// </returns> /// <exception cref="ArgumentNullException"> /// An ArgumentNullException is thrown if the example object is null. /// </exception> /// <exception cref="NotSupportedException"> /// A NotSupportedException is thrown if the object is not null and is not a DataGridLength, /// or if the destinationType isn't one of the valid destination types. /// </exception> ///<SecurityNote> /// Critical: calls InstanceDescriptor ctor which LinkDemands /// PublicOK: can only make an InstanceDescriptor for DataGridLength, not an arbitrary class ///</SecurityNote> public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == null) { throw new ArgumentNullException("destinationType"); } if (destinationType != typeof(string)) { throw DataGridError.DataGridLengthConverter.CannotConvertTo(destinationType.ToString()); } DataGridLength?dataGridLength = value as DataGridLength?; if (!dataGridLength.HasValue) { throw DataGridError.DataGridLengthConverter.InvalidDataGridLength("value"); } else { // Convert dataGridLength to a string switch (dataGridLength.Value.UnitType) { // for Auto print out "Auto". value is always "1.0" case DataGridLengthUnitType.Auto: return("Auto"); case DataGridLengthUnitType.SizeToHeader: return("SizeToHeader"); case DataGridLengthUnitType.SizeToCells: return("SizeToCells"); // Star has one special case when value is "1.0". // in this case drop value part and print only "Star" case DataGridLengthUnitType.Star: return( MathUtilities.AreClose(1.0, dataGridLength.Value.Value) ? _starSuffix : Convert.ToString(dataGridLength.Value.Value, culture ?? CultureInfo.CurrentCulture) + DataGridLengthConverter._starSuffix); default: return(Convert.ToString(dataGridLength.Value.Value, culture ?? CultureInfo.CurrentCulture)); } } }
public override void Arrange(UIElement adorner) { BaseAdornerLayout.LayoutCache cache = BaseAdornerLayout.GetCache((DependencyObject)adorner); Matrix m1_1 = cache.ElementToDesignerViewTransformMatrix; Matrix m1_2 = cache.DesignerViewToViewportMatrix; if (!MathUtilities.AreClose(m1_1, cache.ElementToDesignerViewTransformMatrix) || !MathUtilities.AreClose(m1_2, cache.DesignerViewToViewportMatrix)) { this.SetupTransform(adorner); } Vector scale = (Vector)adorner.GetValue(TransformAwareAdornerLayout.DesignerElementScalingFactorWithZoom); ViewItem view = AdornerProperties.GetView((DependencyObject)adorner); this.SetAdornerBounds(adorner, view, new Point(0.0, 0.0), scale); if (!(view != (ViewItem)null) || cache.PlatformObjectHashCode == 0 || cache.PlatformObjectHashCode == view.PlatformObject.GetHashCode()) { return; } cache.View = view; cache.RenderSize = view.RenderSize; cache.PlatformObjectHashCode = view.PlatformObject.GetHashCode(); }
private void TimerTick(TimeSpan t) { // [<------------- normalizedTotalDur ------------------>] // [<---- Delay ---->][<---------- Duration ------------>] // ^- normalizedDelayEnd // [<---- normalizedInterpVal --->] var normalizedInterpVal = 1d; if (!MathUtilities.AreClose(_duration.TotalSeconds, 0d)) { var normalizedTotalDur = _delay + _duration; var normalizedDelayEnd = _delay.TotalSeconds / normalizedTotalDur.TotalSeconds; var normalizedPresentationTime = t.TotalSeconds / normalizedTotalDur.TotalSeconds; if (normalizedPresentationTime < normalizedDelayEnd || MathUtilities.AreClose(normalizedPresentationTime, normalizedDelayEnd)) { normalizedInterpVal = 0d; } else { normalizedInterpVal = (t.TotalSeconds - _delay.TotalSeconds) / _duration.TotalSeconds; } } // Clamp interpolation value. if (normalizedInterpVal >= 1d || normalizedInterpVal < 0d) { PublishNext(1d); PublishCompleted(); } else { PublishNext(normalizedInterpVal); } }
public bool NearlyEquals(Vector other) { return(MathUtilities.AreClose(_x, other._x) && MathUtilities.AreClose(_y, other._y)); }
public IEnumerable<Rect> HitTestTextRange(int start, int length) { if (start + length <= 0) { return Array.Empty<Rect>(); } var result = new List<Rect>(TextLines.Count); var currentY = 0d; var currentPosition = 0; var currentRect = Rect.Empty; foreach (var textLine in TextLines) { //Current line isn't covered. if (currentPosition + textLine.TextRange.Length <= start) { currentY += textLine.Height; currentPosition += textLine.TextRange.Length; continue; } //The whole line is covered. if (currentPosition >= start && start + length > currentPosition + textLine.TextRange.Length) { currentRect = new Rect(textLine.Start, currentY, textLine.WidthIncludingTrailingWhitespace, textLine.Height); result.Add(currentRect); currentY += textLine.Height; currentPosition += textLine.TextRange.Length; continue; } var startX = textLine.Start; //A portion of the line is covered. for (var index = 0; index < textLine.TextRuns.Count; index++) { var currentRun = (ShapedTextCharacters)textLine.TextRuns[index]; ShapedTextCharacters? nextRun = null; if (index + 1 < textLine.TextRuns.Count) { nextRun = (ShapedTextCharacters)textLine.TextRuns[index + 1]; } if (nextRun != null) { if (nextRun.Text.Start < currentRun.Text.Start && start + length < currentRun.Text.End) { goto skip; } if (currentRun.Text.Start >= start + length) { goto skip; } if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < start) { goto skip; } if (currentRun.Text.End < start) { goto skip; } goto noop; skip: { startX += currentRun.Size.Width; currentPosition = currentRun.Text.Start; } continue; noop:{ } } var endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit( currentRun.ShapedBuffer.IsLeftToRight ? new CharacterHit(start + length) : new CharacterHit(start)); var endX = startX + endOffset; var startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit( currentRun.ShapedBuffer.IsLeftToRight ? new CharacterHit(start) : new CharacterHit(start + length)); startX += startOffset; var characterHit = currentRun.GlyphRun.IsLeftToRight ? currentRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _) : currentRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; if(nextRun != null) { if (currentRun.ShapedBuffer.IsLeftToRight == nextRun.ShapedBuffer.IsLeftToRight) { endOffset = nextRun.GlyphRun.GetDistanceFromCharacterHit( nextRun.ShapedBuffer.IsLeftToRight ? new CharacterHit(start + length) : new CharacterHit(start)); index++; endX += endOffset; currentRun = nextRun; if (currentRun.ShapedBuffer.IsLeftToRight) { characterHit = nextRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; } } } if (endX < startX) { (endX, startX) = (startX, endX); } var width = endX - startX; if (result.Count > 0 && MathUtilities.AreClose(currentRect.Top, currentY) && MathUtilities.AreClose(currentRect.Right, startX)) { result[result.Count - 1] = currentRect.WithWidth(currentRect.Width + width); } else { currentRect = new Rect(startX, currentY, width, textLine.Height); result.Add(currentRect); } if (currentRun.ShapedBuffer.IsLeftToRight) { if (nextRun != null) { if (nextRun.Text.Start > currentRun.Text.Start && nextRun.Text.Start >= start + length) { break; } currentPosition = nextRun.Text.End; } else { if (currentPosition >= start + length) { break; } } } else { if (currentPosition <= start) { break; } } if (!currentRun.ShapedBuffer.IsLeftToRight && currentPosition != currentRun.Text.Start) { endX += currentRun.GlyphRun.Size.Width - endOffset; } startX = endX; } if (currentPosition == start || currentPosition == start + length) { break; } if (textLine.TextRange.Start + textLine.TextRange.Length >= start + length) { break; } currentY += textLine.Height; } return result; }
private static void CheckAndInvalidateAdorner(DesignerView view, ViewItem element, UIElement adorner) { if (view.Context == null) { return; } Matrix m1 = TransformUtil.GetTransformToImmediateParent((DependencyObject)view).Value; Matrix identity; Size s1; if (element != (ViewItem)null) { identity = TransformUtil.GetSelectionFrameTransformToDesignerView(view.Context, element).Value; s1 = element.RenderSize; } else { identity = Matrix.Identity; s1 = Size.Empty; } BaseAdornerLayout.LayoutCache cache = BaseAdornerLayout.GetCache((DependencyObject)adorner); bool flag1 = false; bool flag2 = false; bool flag3 = false; if (element != (ViewItem)null && !MathUtilities.AreClose(s1, cache.RenderSize)) { flag1 = true; } if (element != (ViewItem)null && !MathUtilities.AreClose(identity, cache.ElementToDesignerViewTransformMatrix)) { flag1 = true; flag2 = true; } if (!MathUtilities.AreClose(m1, cache.DesignerViewToViewportMatrix)) { flag1 = true; flag2 = true; } if (element != (ViewItem)null && !element.IsVisible) { ViewItem view1 = view.Context.Services.GetRequiredService <ModelService>().Root.View; for (ViewItem viewItem = element; viewItem != view1 && viewItem != (ViewItem)null; viewItem = viewItem.VisualParent) { if (viewItem.Visibility == Visibility.Collapsed) { flag3 = true; break; } } } if (flag3) { object obj = adorner.ReadLocalValue(UIElement.VisibilityProperty); if (adorner.ReadLocalValue(BaseAdornerLayout.CachedVisibilityProperty) == DependencyProperty.UnsetValue) { if (obj != DependencyProperty.UnsetValue) { adorner.SetValue(BaseAdornerLayout.CachedVisibilityProperty, obj); } else { adorner.SetValue(BaseAdornerLayout.CachedVisibilityProperty, (object)Visibility.Visible); } adorner.Visibility = Visibility.Collapsed; } } else { object obj = adorner.ReadLocalValue(BaseAdornerLayout.CachedVisibilityProperty); if (obj != DependencyProperty.UnsetValue) { adorner.SetValue(UIElement.VisibilityProperty, obj); } adorner.ClearValue(BaseAdornerLayout.CachedVisibilityProperty); } if (!flag1 && !flag2) { return; } if (element != (ViewItem)null) { cache.RenderSize = s1; cache.ElementToDesignerViewTransformMatrix = identity; cache.PlatformObjectHashCode = element.PlatformObject.GetHashCode(); } cache.DesignerViewToViewportMatrix = m1; cache.DesignerView = view; UIElement uiElement = VisualTreeHelper.GetParent((DependencyObject)adorner) as UIElement; if (flag1) { adorner.InvalidateMeasure(); if (uiElement != null) { uiElement.InvalidateMeasure(); } } if (!flag2) { return; } adorner.InvalidateVisual(); if (uiElement == null) { return; } uiElement.InvalidateVisual(); }
private IReadOnlyList <TextBounds> GetTextBoundsRightToLeft(int firstTextSourceIndex, int textLength) { var characterIndex = firstTextSourceIndex + textLength; var result = new List <TextBounds>(TextRuns.Count); var lastDirection = FlowDirection.LeftToRight; var currentDirection = lastDirection; var currentPosition = FirstTextSourceIndex; var remainingLength = textLength; var startX = Start + WidthIncludingTrailingWhitespace; double currentWidth = 0; var currentRect = Rect.Empty; for (var index = TextRuns.Count - 1; index >= 0; index--) { if (TextRuns[index] is not DrawableTextRun currentRun) { continue; } if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) { startX -= currentRun.Size.Width; currentPosition += currentRun.TextSourceLength; continue; } var characterLength = 0; var endX = startX; if (currentRun is ShapedTextCharacters currentShapedRun) { var offset = Math.Max(0, firstTextSourceIndex - currentPosition); currentPosition += offset; var startIndex = currentRun.Text.Start + offset; var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( currentShapedRun.ShapedBuffer.IsLeftToRight ? new CharacterHit(startIndex + remainingLength) : new CharacterHit(startIndex)); endX += endOffset - currentShapedRun.Size.Width; var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( currentShapedRun.ShapedBuffer.IsLeftToRight ? new CharacterHit(startIndex) : new CharacterHit(startIndex + remainingLength)); startX += startOffset - currentShapedRun.Size.Width; var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength); currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ? FlowDirection.LeftToRight : FlowDirection.RightToLeft; } else { if (currentPosition + currentRun.TextSourceLength <= characterIndex) { endX -= currentRun.Size.Width; } if (currentPosition < firstTextSourceIndex) { startX -= currentRun.Size.Width; characterLength = currentRun.TextSourceLength; } } if (endX < startX) { (endX, startX) = (startX, endX); } //Lines that only contain a linebreak need to be covered here if (characterLength == 0) { characterLength = NewLineLength; } var runWidth = endX - startX; var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) { currentRect = currentRect.WithWidth(currentWidth + runWidth); var textBounds = result[result.Count - 1]; textBounds.Rectangle = currentRect; textBounds.TextRunBounds.Add(currentRunBounds); } else { currentRect = currentRunBounds.Rectangle; result.Add(new TextBounds(currentRect, currentDirection, new List <TextRunBounds> { currentRunBounds })); } currentWidth += runWidth; currentPosition += characterLength; if (currentDirection == FlowDirection.LeftToRight) { if (currentPosition > characterIndex) { break; } } else { if (currentPosition <= firstTextSourceIndex) { break; } } lastDirection = currentDirection; remainingLength -= characterLength; if (remainingLength <= 0) { break; } } return(result); }
/// <summary> /// OnLayoutUpdated handler. Validates that all participating definitions /// have updated min size value. Forces another layout update cycle if needed. /// </summary> private void OnLayoutUpdated(object sender, EventArgs e) { double sharedMinSize = 0; // accumulate min size of all participating definitions for (int i = 0, count = _registry.Count; i < count; ++i) { sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); } bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); // compare accumulated min size with min sizes of the individual definitions for (int i = 0, count = _registry.Count; i < count; ++i) { DefinitionBase definitionBase = _registry[i]; // we'll set d.UseSharedMinimum to maintain the invariant: // d.UseSharedMinimum iff d._minSize < this.MinSize // i.e. iff d is not a "long-pole" definition. // // Measure/Arrange of d's Grid uses d._minSize for long-pole // definitions, and max(d._minSize, shared size) for // short-pole definitions. This distinction allows us to react // to changes in "long-pole-ness" more efficiently and correctly, // by avoiding remeasures when a long-pole definition changes. bool useSharedMinimum = !MathUtilities.AreClose(definitionBase._minSize, sharedMinSize); // before doing that, determine whether d's Grid needs to be remeasured. // It's important _not_ to remeasure if the last measure is still // valid, otherwise infinite loops are possible bool measureIsValid; if (!definitionBase.UseSharedMinimum) { // d was a long-pole. measure is valid iff it's still a long-pole, // since previous measure didn't use shared size. measureIsValid = !useSharedMinimum; } else if (useSharedMinimum) { // d was a short-pole, and still is. measure is valid // iff the shared size didn't change measureIsValid = !sharedMinSizeChanged; } else { // d was a short-pole, but is now a long-pole. This can // happen in several ways: // a. d's minSize increased to or past the old shared size // b. other long-pole definitions decreased, leaving // d as the new winner // In the former case, the measure is valid - it used // d's new larger minSize. In the latter case, the // measure is invalid - it used the old shared size, // which is larger than d's (possibly changed) minSize measureIsValid = (definitionBase.LayoutWasUpdated && MathUtilities.GreaterThanOrClose(definitionBase._minSize, this.MinSize)); } if (!measureIsValid) { definitionBase.Parent.InvalidateMeasure(); } else if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) { // if measure is valid then also need to check arrange. // Note: definitionBase.SizeCache is volatile but at this point // it contains up-to-date final size definitionBase.Parent.InvalidateArrange(); } // now we can restore the invariant, and clear the layout flag definitionBase.UseSharedMinimum = useSharedMinimum; definitionBase.LayoutWasUpdated = false; } _minSize = sharedMinSize; _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; _layoutUpdatedHost = null; _broadcastInvalidation = true; }
public override IReadOnlyList <TextBounds> GetTextBounds(int firstTextSourceCharacterIndex, int textLength) { if (firstTextSourceCharacterIndex + textLength <= FirstTextSourceIndex) { return(Array.Empty <TextBounds>()); } var result = new List <TextBounds>(TextRuns.Count); var lastDirection = _flowDirection; var currentDirection = lastDirection; var currentPosition = 0; var currentRect = Rect.Empty; var startX = Start; //A portion of the line is covered. for (var index = 0; index < TextRuns.Count; index++) { var currentRun = TextRuns[index] as DrawableTextRun; if (currentRun is null) { continue; } TextRun?nextRun = null; if (index + 1 < TextRuns.Count) { nextRun = TextRuns[index + 1]; } if (nextRun != null) { if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End) { goto skip; } if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength) { goto skip; } if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex) { goto skip; } if (currentRun.Text.End < firstTextSourceCharacterIndex) { goto skip; } goto noop; skip: { startX += currentRun.Size.Width; } continue; noop: { } } var endX = startX; var endOffset = 0d; switch (currentRun) { case ShapedTextCharacters shapedRun: { endOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit( shapedRun.ShapedBuffer.IsLeftToRight ? new CharacterHit(firstTextSourceCharacterIndex + textLength) : new CharacterHit(firstTextSourceCharacterIndex)); endX += endOffset; var startOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit( shapedRun.ShapedBuffer.IsLeftToRight ? new CharacterHit(firstTextSourceCharacterIndex) : new CharacterHit(firstTextSourceCharacterIndex + textLength)); startX += startOffset; var characterHit = shapedRun.GlyphRun.IsLeftToRight ? shapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _) : shapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; currentDirection = shapedRun.ShapedBuffer.IsLeftToRight ? FlowDirection.LeftToRight : FlowDirection.RightToLeft; if (nextRun is ShapedTextCharacters nextShaped) { if (shapedRun.ShapedBuffer.IsLeftToRight == nextShaped.ShapedBuffer.IsLeftToRight) { endOffset = nextShaped.GlyphRun.GetDistanceFromCharacterHit( nextShaped.ShapedBuffer.IsLeftToRight ? new CharacterHit(firstTextSourceCharacterIndex + textLength) : new CharacterHit(firstTextSourceCharacterIndex)); index++; endX += endOffset; currentRun = nextShaped; if (nextShaped.ShapedBuffer.IsLeftToRight) { characterHit = nextShaped.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; } } } break; } default: { if (firstTextSourceCharacterIndex + textLength >= currentRun.Text.Start + currentRun.Text.Length) { endX += currentRun.Size.Width; } break; } } if (endX < startX) { (endX, startX) = (startX, endX); } var width = endX - startX; if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) { var textBounds = new TextBounds(currentRect.WithWidth(currentRect.Width + width), currentDirection); result[result.Count - 1] = textBounds; } else { currentRect = new Rect(startX, 0, width, Height); result.Add(new TextBounds(currentRect, currentDirection)); } if (currentDirection == FlowDirection.LeftToRight) { if (nextRun != null) { if (nextRun.Text.Start > currentRun.Text.Start && nextRun.Text.Start >= firstTextSourceCharacterIndex + textLength) { break; } currentPosition = nextRun.Text.End; } else { if (currentPosition >= firstTextSourceCharacterIndex + textLength) { break; } } } else { if (currentPosition <= firstTextSourceCharacterIndex) { break; } if (currentPosition != currentRun.Text.Start) { endX += currentRun.Size.Width - endOffset; } } lastDirection = currentDirection; startX = endX; } return(result); }
/// <summary> /// Returns a boolean indicating whether the size is equal to the other given size (numerically). /// </summary> /// <param name="other">The other size to test equality against.</param> /// <returns>True if this size is equal to other; False otherwise.</returns> public bool NearlyEquals(Size other) { return(MathUtilities.AreClose(_width, other._width) && MathUtilities.AreClose(_height, other._height)); }