|
|
|
@ -53,8 +53,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -53,8 +53,12 @@ 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); |
|
|
|
|
|
|
|
|
|
this.hoverLogic = new MouseHoverLogic(this); |
|
|
|
|
this.hoverLogic.MouseHover += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverEvent, MouseHoverEvent); |
|
|
|
|
this.hoverLogic.MouseHoverStopped += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverStoppedEvent, MouseHoverStoppedEvent); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
@ -294,7 +298,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -294,7 +298,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.
|
|
|
|
@ -303,6 +307,47 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -303,6 +307,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>
|
|
|
|
@ -352,18 +397,128 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -352,18 +397,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
|
|
|
|
|
|
|
|
|
@ -410,7 +565,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -410,7 +565,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
{ |
|
|
|
|
VerifyAccess(); |
|
|
|
|
if (allVisualLines.Remove(visualLine)) { |
|
|
|
|
visibleVisualLines = null; |
|
|
|
|
DisposeVisualLine(visualLine); |
|
|
|
|
InvalidateMeasure(redrawPriority); |
|
|
|
|
} |
|
|
|
@ -422,7 +576,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -422,7 +576,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
public void Redraw(int offset, int length, DispatcherPriority redrawPriority = DispatcherPriority.Normal) |
|
|
|
|
{ |
|
|
|
|
VerifyAccess(); |
|
|
|
|
bool removedLine = false; |
|
|
|
|
bool changedSomethingBeforeOrInLine = false; |
|
|
|
|
for (int i = 0; i < allVisualLines.Count; i++) { |
|
|
|
|
VisualLine visualLine = allVisualLines[i]; |
|
|
|
@ -431,15 +584,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -431,15 +584,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.
|
|
|
|
@ -492,11 +641,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -492,11 +641,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
|
|
|
|
|
|
|
|
|
@ -677,11 +827,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -677,11 +827,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
ClearVisualLines(); |
|
|
|
|
lastAvailableSize = availableSize; |
|
|
|
|
|
|
|
|
|
textLayer.RemoveInlineObjectsNow(); |
|
|
|
|
|
|
|
|
|
foreach (UIElement layer in layers) { |
|
|
|
|
layer.Measure(availableSize); |
|
|
|
|
} |
|
|
|
|
MeasureInlineObjects(); |
|
|
|
|
|
|
|
|
|
InvalidateVisual(); // = InvalidateArrange+InvalidateRender
|
|
|
|
|
textLayer.InvalidateVisual(); |
|
|
|
|
|
|
|
|
@ -700,7 +850,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -700,7 +850,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; |
|
|
|
@ -949,7 +1100,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -949,7 +1100,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)); |
|
|
|
|
} |
|
|
|
@ -1566,63 +1717,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -1566,63 +1717,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
remove { RemoveHandler(MouseHoverStoppedEvent, value); } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DispatcherTimer mouseHoverTimer; |
|
|
|
|
Point mouseHoverStartPoint; |
|
|
|
|
MouseEventArgs mouseHoverLastEventArgs; |
|
|
|
|
bool mouseHovering; |
|
|
|
|
MouseHoverLogic hoverLogic; |
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
protected override void OnMouseMove(MouseEventArgs e) |
|
|
|
|
void RaiseHoverEventPair(MouseEventArgs e, RoutedEvent tunnelingEvent, RoutedEvent bubblingEvent) |
|
|
|
|
{ |
|
|
|
|
base.OnMouseMove(e); |
|
|
|
|
Point newPosition = e.GetPosition(this); |
|
|
|
|
Vector mouseMovement = mouseHoverStartPoint - newPosition; |
|
|
|
|
if (Math.Abs(mouseMovement.X) > SystemParameters.MouseHoverWidth |
|
|
|
|
|| Math.Abs(mouseMovement.Y) > SystemParameters.MouseHoverHeight) |
|
|
|
|
{ |
|
|
|
|
StopHovering(); |
|
|
|
|
mouseHoverStartPoint = newPosition; |
|
|
|
|
mouseHoverLastEventArgs = e; |
|
|
|
|
mouseHoverTimer = new DispatcherTimer(SystemParameters.MouseHoverTime, DispatcherPriority.Background, |
|
|
|
|
OnMouseHoverTimerElapsed, this.Dispatcher); |
|
|
|
|
mouseHoverTimer.Start(); |
|
|
|
|
} |
|
|
|
|
// do not set e.Handled - allow others to also handle MouseMove
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
protected override void OnMouseLeave(MouseEventArgs e) |
|
|
|
|
{ |
|
|
|
|
base.OnMouseLeave(e); |
|
|
|
|
StopHovering(); |
|
|
|
|
// do not set e.Handled - allow others to also handle MouseLeave
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void StopHovering() |
|
|
|
|
{ |
|
|
|
|
if (mouseHoverTimer != null) { |
|
|
|
|
mouseHoverTimer.Stop(); |
|
|
|
|
mouseHoverTimer = null; |
|
|
|
|
} |
|
|
|
|
if (mouseHovering) { |
|
|
|
|
mouseHovering = false; |
|
|
|
|
RaiseHoverEventPair(PreviewMouseHoverStoppedEvent, MouseHoverStoppedEvent); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void OnMouseHoverTimerElapsed(object sender, EventArgs e) |
|
|
|
|
{ |
|
|
|
|
mouseHoverTimer.Stop(); |
|
|
|
|
mouseHoverTimer = null; |
|
|
|
|
|
|
|
|
|
mouseHovering = true; |
|
|
|
|
RaiseHoverEventPair(PreviewMouseHoverEvent, MouseHoverEvent); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void RaiseHoverEventPair(RoutedEvent tunnelingEvent, RoutedEvent bubblingEvent) |
|
|
|
|
{ |
|
|
|
|
var mouseDevice = mouseHoverLastEventArgs.MouseDevice; |
|
|
|
|
var stylusDevice = mouseHoverLastEventArgs.StylusDevice; |
|
|
|
|
var mouseDevice = e.MouseDevice; |
|
|
|
|
var stylusDevice = e.StylusDevice; |
|
|
|
|
int inputTime = Environment.TickCount; |
|
|
|
|
var args1 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) { |
|
|
|
|
RoutedEvent = tunnelingEvent, |
|
|
|
@ -1636,8 +1736,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
@@ -1636,8 +1736,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
|
|
|
|
|
}; |
|
|
|
|
RaiseEvent(args2); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|