Browse Source

Improved inline object handling. Inline objects that change their size in response to user input are now supported.

4.0
Daniel Grunwald 15 years ago
parent
commit
c8dc5154dd
  1. 15
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/InlineObjectRun.cs
  2. 91
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
  3. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
  4. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs

15
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/InlineObjectRun.cs

@ -38,11 +38,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (context == null) if (context == null)
throw new ArgumentNullException("context"); throw new ArgumentNullException("context");
// remove inline object if its already added, can happen e.g. when recreating textrun for word-wrapping
// TODO: certainly the text view should handle this internally? external code might want to use InlineObjectRun,
// but doesn't have access to textLayer.RemoveInlineObject
context.TextView.RemoveInlineObject(this.Element);
return new InlineObjectRun(1, this.TextRunProperties, this.Element); return new InlineObjectRun(1, this.TextRunProperties, this.Element);
} }
} }
@ -55,6 +50,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
UIElement element; UIElement element;
int length; int length;
TextRunProperties properties; TextRunProperties properties;
internal Size desiredSize;
/// <summary> /// <summary>
/// Creates a new InlineObjectRun instance. /// Creates a new InlineObjectRun instance.
@ -122,11 +118,10 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <inheritdoc/> /// <inheritdoc/>
public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth) public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth)
{ {
Size size = element.DesiredSize;
double baseline = TextBlock.GetBaselineOffset(element); double baseline = TextBlock.GetBaselineOffset(element);
if (double.IsNaN(baseline)) if (double.IsNaN(baseline))
baseline = size.Height; baseline = desiredSize.Height;
return new TextEmbeddedObjectMetrics(size.Width, size.Height, baseline); return new TextEmbeddedObjectMetrics(desiredSize.Width, desiredSize.Height, baseline);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -135,8 +130,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (this.element.IsArrangeValid) { if (this.element.IsArrangeValid) {
double baseline = TextBlock.GetBaselineOffset(element); double baseline = TextBlock.GetBaselineOffset(element);
if (double.IsNaN(baseline)) if (double.IsNaN(baseline))
baseline = element.DesiredSize.Height; baseline = desiredSize.Height;
return new Rect(new Point(0, -baseline), element.DesiredSize); return new Rect(new Point(0, -baseline), desiredSize);
} else { } else {
return Rect.Empty; return Rect.Empty;
} }

91
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs

@ -302,7 +302,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
get { return layers; } get { return layers; }
} }
sealed class LayerCollection : UIElementCollection sealed class LayerCollection : UIElementCollection
{ {
readonly TextView textView; readonly TextView textView;
@ -416,36 +416,79 @@ namespace ICSharpCode.AvalonEdit.Rendering
#endregion #endregion
#region Inline object handling #region Inline object handling
internal List<InlineObjectRun> inlineObjects = new List<InlineObjectRun>(); List<InlineObjectRun> inlineObjects = new List<InlineObjectRun>();
/// <summary> /// <summary>
/// Adds a new inline object. /// Adds a new inline object.
/// </summary> /// </summary>
internal void AddInlineObject(InlineObjectRun inlineObject) 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); inlineObjects.Add(inlineObject);
AddVisualChild(inlineObject.Element); if (!alreadyAdded) {
AddVisualChild(inlineObject.Element);
}
inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 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>(); List<VisualLine> visualLinesWithOutstandingInlineObjects = new List<VisualLine>();
internal void RemoveInlineObjects(VisualLine visualLine) void RemoveInlineObjects(VisualLine visualLine)
{ {
// Delay removing inline objects: // Delay removing inline objects:
// A document change immediately invalidates affected visual lines, but it does not // A document change immediately invalidates affected visual lines, but it does not
// cause an immediate redraw. // cause an immediate redraw.
// To prevent inline objects from flickering when they are recreated, we delay removing // To prevent inline objects from flickering when they are recreated, we delay removing
// inline objects until the next redraw. // inline objects until the next redraw.
visualLinesWithOutstandingInlineObjects.Add(visualLine); if (visualLine.hasInlineObjects) {
visualLinesWithOutstandingInlineObjects.Add(visualLine);
}
} }
internal void RemoveInlineObjectsNow() /// <summary>
/// Remove the inline objects that were marked for removal.
/// </summary>
void RemoveInlineObjectsNow()
{ {
if (visualLinesWithOutstandingInlineObjects.Count == 0)
return;
inlineObjects.RemoveAll( inlineObjects.RemoveAll(
ior => { ior => {
if (visualLinesWithOutstandingInlineObjects.Contains(ior.VisualLine)) { if (visualLinesWithOutstandingInlineObjects.Contains(ior.VisualLine)) {
RemoveInlineObjectRun(ior); RemoveInlineObjectRun(ior, false);
return true; return true;
} }
return false; return false;
@ -455,9 +498,9 @@ namespace ICSharpCode.AvalonEdit.Rendering
// Remove InlineObjectRun.Element from TextLayer. // Remove InlineObjectRun.Element from TextLayer.
// Caller of RemoveInlineObjectRun will remove it from inlineObjects collection. // Caller of RemoveInlineObjectRun will remove it from inlineObjects collection.
void RemoveInlineObjectRun(InlineObjectRun ior) void RemoveInlineObjectRun(InlineObjectRun ior, bool keepElement)
{ {
if (ior.Element.IsKeyboardFocusWithin) { if (!keepElement && ior.Element.IsKeyboardFocusWithin) {
// When the inline element that has the focus is removed, WPF will reset the // When the inline element that has the focus is removed, WPF will reset the
// focus to the main window without raising appropriate LostKeyboardFocus events. // focus to the main window without raising appropriate LostKeyboardFocus events.
// To work around this, we manually set focus to the next focusable parent. // To work around this, we manually set focus to the next focusable parent.
@ -469,22 +512,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
Keyboard.Focus(element); Keyboard.Focus(element);
} }
ior.VisualLine = null; ior.VisualLine = null;
RemoveVisualChild(ior.Element); if (!keepElement)
} RemoveVisualChild(ior.Element);
/// <summary>
/// Removes the inline object that displays the specified UIElement.
/// </summary>
internal void RemoveInlineObject(UIElement element)
{
inlineObjects.RemoveAll(
ior => {
if (ior.Element == element) {
RemoveInlineObjectRun(ior);
return true;
}
return false;
});
} }
#endregion #endregion
@ -531,7 +560,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
{ {
VerifyAccess(); VerifyAccess();
if (allVisualLines.Remove(visualLine)) { if (allVisualLines.Remove(visualLine)) {
visibleVisualLines = null;
DisposeVisualLine(visualLine); DisposeVisualLine(visualLine);
InvalidateMeasure(redrawPriority); InvalidateMeasure(redrawPriority);
} }
@ -543,7 +571,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
public void Redraw(int offset, int length, DispatcherPriority redrawPriority) public void Redraw(int offset, int length, DispatcherPriority redrawPriority)
{ {
VerifyAccess(); VerifyAccess();
bool removedLine = false;
bool changedSomethingBeforeOrInLine = false; bool changedSomethingBeforeOrInLine = false;
for (int i = 0; i < allVisualLines.Count; i++) { for (int i = 0; i < allVisualLines.Count; i++) {
VisualLine visualLine = allVisualLines[i]; VisualLine visualLine = allVisualLines[i];
@ -552,15 +579,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (offset <= lineEnd) { if (offset <= lineEnd) {
changedSomethingBeforeOrInLine = true; changedSomethingBeforeOrInLine = true;
if (offset + length >= lineStart) { if (offset + length >= lineStart) {
removedLine = true;
allVisualLines.RemoveAt(i--); allVisualLines.RemoveAt(i--);
DisposeVisualLine(visualLine); DisposeVisualLine(visualLine);
} }
} }
} }
if (removedLine) {
visibleVisualLines = null;
}
if (changedSomethingBeforeOrInLine) { if (changedSomethingBeforeOrInLine) {
// Repaint not only when something in visible area was changed, but also when anything in front of it // 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. // was changed. We might have to redraw the line number margin. Or the highlighting changed.
@ -613,6 +636,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (newVisualLines != null && newVisualLines.Contains(visualLine)) { if (newVisualLines != null && newVisualLines.Contains(visualLine)) {
throw new ArgumentException("Cannot dispose visual line because it is in construction!"); throw new ArgumentException("Cannot dispose visual line because it is in construction!");
} }
visibleVisualLines = null;
visualLine.IsDisposed = true; visualLine.IsDisposed = true;
foreach (TextLine textLine in visualLine.TextLines) { foreach (TextLine textLine in visualLine.TextLines) {
textLine.Dispose(); textLine.Dispose();
@ -798,11 +822,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
ClearVisualLines(); ClearVisualLines();
lastAvailableSize = availableSize; lastAvailableSize = availableSize;
RemoveInlineObjectsNow();
foreach (UIElement layer in layers) { foreach (UIElement layer in layers) {
layer.Measure(availableSize); layer.Measure(availableSize);
} }
MeasureInlineObjects();
InvalidateVisual(); // = InvalidateArrange+InvalidateRender InvalidateVisual(); // = InvalidateArrange+InvalidateRender
textLayer.InvalidateVisual(); textLayer.InvalidateVisual();
@ -821,6 +845,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
} }
} }
// remove inline objects only at the end, so that inline objects that were re-used are not removed from the editor
RemoveInlineObjectsNow(); RemoveInlineObjectsNow();
maxWidth += AdditionalHorizontalScrollAmount; maxWidth += AdditionalHorizontalScrollAmount;

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs

@ -22,6 +22,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
{ {
TextView textView; TextView textView;
List<VisualLineElement> elements; List<VisualLineElement> elements;
internal bool hasInlineObjects;
/// <summary> /// <summary>
/// Gets the document to which this VisualLine belongs. /// Gets the document to which this VisualLine belongs.

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs

@ -42,6 +42,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
InlineObjectRun inlineRun = run as InlineObjectRun; InlineObjectRun inlineRun = run as InlineObjectRun;
if (inlineRun != null) { if (inlineRun != null) {
inlineRun.VisualLine = VisualLine; inlineRun.VisualLine = VisualLine;
VisualLine.hasInlineObjects = true;
TextView.AddInlineObject(inlineRun); TextView.AddInlineObject(inlineRun);
} }
return run; return run;

Loading…
Cancel
Save