|
|
|
@ -53,7 +53,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -53,7 +53,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
this.Options = new TextEditorOptions(); |
|
|
|
|
Debug.Assert(singleCharacterElementGenerator != null); // assert that the option change created the builtin element generators
|
|
|
|
|
|
|
|
|
|
layers = new UIElementCollection(this, this); |
|
|
|
|
layers = new LayerCollection(this); |
|
|
|
|
InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace); |
|
|
|
|
} |
|
|
|
|
#endregion
|
|
|
|
@ -293,7 +293,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -293,7 +293,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
|
|
|
|
|
#region Layers
|
|
|
|
|
internal readonly TextLayer textLayer; |
|
|
|
|
readonly UIElementCollection layers; |
|
|
|
|
readonly LayerCollection layers; |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the list of layers displayed in the text view.
|
|
|
|
@ -302,6 +302,47 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -302,6 +302,47 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
get { return layers; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sealed class LayerCollection : UIElementCollection |
|
|
|
|
{ |
|
|
|
|
readonly TextView textView; |
|
|
|
|
|
|
|
|
|
public LayerCollection(TextView textView) |
|
|
|
|
: base(textView, textView) |
|
|
|
|
{ |
|
|
|
|
this.textView = textView; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override void Clear() |
|
|
|
|
{ |
|
|
|
|
base.Clear(); |
|
|
|
|
textView.LayersChanged(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override int Add(UIElement element) |
|
|
|
|
{ |
|
|
|
|
int r = base.Add(element); |
|
|
|
|
textView.LayersChanged(); |
|
|
|
|
return r; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override void RemoveAt(int index) |
|
|
|
|
{ |
|
|
|
|
base.RemoveAt(index); |
|
|
|
|
textView.LayersChanged(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override void RemoveRange(int index, int count) |
|
|
|
|
{ |
|
|
|
|
base.RemoveRange(index, count); |
|
|
|
|
textView.LayersChanged(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void LayersChanged() |
|
|
|
|
{ |
|
|
|
|
textLayer.index = layers.IndexOf(textLayer); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Inserts a new layer at a position specified relative to an existing layer.
|
|
|
|
|
/// </summary>
|
|
|
|
@ -351,18 +392,128 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -351,18 +392,128 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
protected override int VisualChildrenCount { |
|
|
|
|
get { return layers.Count; } |
|
|
|
|
get { return layers.Count + inlineObjects.Count; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
protected override Visual GetVisualChild(int index) |
|
|
|
|
{ |
|
|
|
|
return layers[index]; |
|
|
|
|
int cut = textLayer.index + 1; |
|
|
|
|
if (index < cut) |
|
|
|
|
return layers[index]; |
|
|
|
|
else if (index < cut + inlineObjects.Count) |
|
|
|
|
return inlineObjects[index - cut].Element; |
|
|
|
|
else |
|
|
|
|
return layers[index - inlineObjects.Count]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
protected override System.Collections.IEnumerator LogicalChildren { |
|
|
|
|
get { return layers.GetEnumerator(); } |
|
|
|
|
get { |
|
|
|
|
return inlineObjects.Select(io => io.Element).Concat(layers.Cast<UIElement>()).GetEnumerator(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Inline object handling
|
|
|
|
|
List<InlineObjectRun> inlineObjects = new List<InlineObjectRun>(); |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds a new inline object.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal void AddInlineObject(InlineObjectRun inlineObject) |
|
|
|
|
{ |
|
|
|
|
Debug.Assert(inlineObject.VisualLine != null); |
|
|
|
|
|
|
|
|
|
// Remove inline object if its already added, can happen e.g. when recreating textrun for word-wrapping
|
|
|
|
|
bool alreadyAdded = false; |
|
|
|
|
for (int i = 0; i < inlineObjects.Count; i++) { |
|
|
|
|
if (inlineObjects[i].Element == inlineObject.Element) { |
|
|
|
|
RemoveInlineObjectRun(inlineObjects[i], true); |
|
|
|
|
inlineObjects.RemoveAt(i); |
|
|
|
|
alreadyAdded = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
inlineObjects.Add(inlineObject); |
|
|
|
|
if (!alreadyAdded) { |
|
|
|
|
AddVisualChild(inlineObject.Element); |
|
|
|
|
} |
|
|
|
|
inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); |
|
|
|
|
inlineObject.desiredSize = inlineObject.Element.DesiredSize; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeasureInlineObjects() |
|
|
|
|
{ |
|
|
|
|
// As part of MeasureOverride(), re-measure the inline objects
|
|
|
|
|
foreach (InlineObjectRun inlineObject in inlineObjects) { |
|
|
|
|
if (inlineObject.VisualLine.IsDisposed) { |
|
|
|
|
// Don't re-measure inline objects that are going to be removed anyways.
|
|
|
|
|
// If the inline object will be reused in a different VisualLine, we'll measure it in the AddInlineObject() call.
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); |
|
|
|
|
if (!inlineObject.Element.DesiredSize.IsClose(inlineObject.desiredSize)) { |
|
|
|
|
// the element changed size -> recreate its parent visual line
|
|
|
|
|
inlineObject.desiredSize = inlineObject.Element.DesiredSize; |
|
|
|
|
if (allVisualLines.Remove(inlineObject.VisualLine)) { |
|
|
|
|
DisposeVisualLine(inlineObject.VisualLine); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
List<VisualLine> visualLinesWithOutstandingInlineObjects = new List<VisualLine>(); |
|
|
|
|
|
|
|
|
|
void RemoveInlineObjects(VisualLine visualLine) |
|
|
|
|
{ |
|
|
|
|
// Delay removing inline objects:
|
|
|
|
|
// A document change immediately invalidates affected visual lines, but it does not
|
|
|
|
|
// cause an immediate redraw.
|
|
|
|
|
// To prevent inline objects from flickering when they are recreated, we delay removing
|
|
|
|
|
// inline objects until the next redraw.
|
|
|
|
|
if (visualLine.hasInlineObjects) { |
|
|
|
|
visualLinesWithOutstandingInlineObjects.Add(visualLine); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Remove the inline objects that were marked for removal.
|
|
|
|
|
/// </summary>
|
|
|
|
|
void RemoveInlineObjectsNow() |
|
|
|
|
{ |
|
|
|
|
if (visualLinesWithOutstandingInlineObjects.Count == 0) |
|
|
|
|
return; |
|
|
|
|
inlineObjects.RemoveAll( |
|
|
|
|
ior => { |
|
|
|
|
if (visualLinesWithOutstandingInlineObjects.Contains(ior.VisualLine)) { |
|
|
|
|
RemoveInlineObjectRun(ior, false); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
return false; |
|
|
|
|
}); |
|
|
|
|
visualLinesWithOutstandingInlineObjects.Clear(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Remove InlineObjectRun.Element from TextLayer.
|
|
|
|
|
// Caller of RemoveInlineObjectRun will remove it from inlineObjects collection.
|
|
|
|
|
void RemoveInlineObjectRun(InlineObjectRun ior, bool keepElement) |
|
|
|
|
{ |
|
|
|
|
if (!keepElement && ior.Element.IsKeyboardFocusWithin) { |
|
|
|
|
// When the inline element that has the focus is removed, WPF will reset the
|
|
|
|
|
// focus to the main window without raising appropriate LostKeyboardFocus events.
|
|
|
|
|
// To work around this, we manually set focus to the next focusable parent.
|
|
|
|
|
UIElement element = this; |
|
|
|
|
while (element != null && !element.Focusable) { |
|
|
|
|
element = VisualTreeHelper.GetParent(element) as UIElement; |
|
|
|
|
} |
|
|
|
|
if (element != null) |
|
|
|
|
Keyboard.Focus(element); |
|
|
|
|
} |
|
|
|
|
ior.VisualLine = null; |
|
|
|
|
if (!keepElement) |
|
|
|
|
RemoveVisualChild(ior.Element); |
|
|
|
|
} |
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
@ -409,7 +560,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -409,7 +560,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
{ |
|
|
|
|
VerifyAccess(); |
|
|
|
|
if (allVisualLines.Remove(visualLine)) { |
|
|
|
|
visibleVisualLines = null; |
|
|
|
|
DisposeVisualLine(visualLine); |
|
|
|
|
InvalidateMeasure(redrawPriority); |
|
|
|
|
} |
|
|
|
@ -421,7 +571,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -421,7 +571,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
public void Redraw(int offset, int length, DispatcherPriority redrawPriority) |
|
|
|
|
{ |
|
|
|
|
VerifyAccess(); |
|
|
|
|
bool removedLine = false; |
|
|
|
|
bool changedSomethingBeforeOrInLine = false; |
|
|
|
|
for (int i = 0; i < allVisualLines.Count; i++) { |
|
|
|
|
VisualLine visualLine = allVisualLines[i]; |
|
|
|
@ -430,15 +579,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -430,15 +579,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
if (offset <= lineEnd) { |
|
|
|
|
changedSomethingBeforeOrInLine = true; |
|
|
|
|
if (offset + length >= lineStart) { |
|
|
|
|
removedLine = true; |
|
|
|
|
allVisualLines.RemoveAt(i--); |
|
|
|
|
DisposeVisualLine(visualLine); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (removedLine) { |
|
|
|
|
visibleVisualLines = null; |
|
|
|
|
} |
|
|
|
|
if (changedSomethingBeforeOrInLine) { |
|
|
|
|
// Repaint not only when something in visible area was changed, but also when anything in front of it
|
|
|
|
|
// was changed. We might have to redraw the line number margin. Or the highlighting changed.
|
|
|
|
@ -491,11 +636,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -491,11 +636,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
if (newVisualLines != null && newVisualLines.Contains(visualLine)) { |
|
|
|
|
throw new ArgumentException("Cannot dispose visual line because it is in construction!"); |
|
|
|
|
} |
|
|
|
|
visibleVisualLines = null; |
|
|
|
|
visualLine.IsDisposed = true; |
|
|
|
|
foreach (TextLine textLine in visualLine.TextLines) { |
|
|
|
|
textLine.Dispose(); |
|
|
|
|
} |
|
|
|
|
textLayer.RemoveInlineObjects(visualLine); |
|
|
|
|
RemoveInlineObjects(visualLine); |
|
|
|
|
} |
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
@ -676,11 +822,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -676,11 +822,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
ClearVisualLines(); |
|
|
|
|
lastAvailableSize = availableSize; |
|
|
|
|
|
|
|
|
|
textLayer.RemoveInlineObjectsNow(); |
|
|
|
|
|
|
|
|
|
foreach (UIElement layer in layers) { |
|
|
|
|
layer.Measure(availableSize); |
|
|
|
|
} |
|
|
|
|
MeasureInlineObjects(); |
|
|
|
|
|
|
|
|
|
InvalidateVisual(); // = InvalidateArrange+InvalidateRender
|
|
|
|
|
textLayer.InvalidateVisual(); |
|
|
|
|
|
|
|
|
@ -699,7 +845,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -699,7 +845,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
textLayer.RemoveInlineObjectsNow(); |
|
|
|
|
// remove inline objects only at the end, so that inline objects that were re-used are not removed from the editor
|
|
|
|
|
RemoveInlineObjectsNow(); |
|
|
|
|
|
|
|
|
|
maxWidth += AdditionalHorizontalScrollAmount; |
|
|
|
|
double heightTreeHeight = this.DocumentHeight; |
|
|
|
@ -946,7 +1093,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -946,7 +1093,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
foreach (var span in textLine.GetTextRunSpans()) { |
|
|
|
|
InlineObjectRun inline = span.Value as InlineObjectRun; |
|
|
|
|
if (inline != null && inline.VisualLine != null) { |
|
|
|
|
Debug.Assert(textLayer.inlineObjects.Contains(inline)); |
|
|
|
|
Debug.Assert(inlineObjects.Contains(inline)); |
|
|
|
|
double distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(offset, 0)); |
|
|
|
|
inline.Element.Arrange(new Rect(new Point(pos.X + distance, pos.Y), inline.Element.DesiredSize)); |
|
|
|
|
} |
|
|
|
|