Browse Source

Merge commit '0513c24aafa0504ba6bd04fcb825ef5def83996a'

pull/1/head
Daniel Grunwald 15 years ago
parent
commit
16a4c73b1f
  1. 1
      AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  2. 15
      AvalonEdit/ICSharpCode.AvalonEdit/Rendering/InlineObjectRun.cs
  3. 102
      AvalonEdit/ICSharpCode.AvalonEdit/Rendering/MouseHoverLogic.cs
  4. 89
      AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextLayer.cs
  5. 244
      AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
  6. 1
      AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
  7. 3
      AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs

1
AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -255,6 +255,7 @@ @@ -255,6 +255,7 @@
<DependentUpon>TextView.cs</DependentUpon>
</Compile>
<Compile Include="Rendering\LinkElementGenerator.cs" />
<Compile Include="Rendering\MouseHoverLogic.cs" />
<Compile Include="Rendering\NewLineElementGenerator.cs" />
<Compile Include="Rendering\SimpleTextSource.cs">
<DependentUpon>FormattedTextElement.cs</DependentUpon>

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

@ -38,11 +38,6 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -38,11 +38,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (context == null)
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.textLayer.RemoveInlineObject(this.Element);
return new InlineObjectRun(1, this.TextRunProperties, this.Element);
}
}
@ -55,6 +50,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -55,6 +50,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
UIElement element;
int length;
TextRunProperties properties;
internal Size desiredSize;
/// <summary>
/// Creates a new InlineObjectRun instance.
@ -122,11 +118,10 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -122,11 +118,10 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <inheritdoc/>
public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth)
{
Size size = element.DesiredSize;
double baseline = TextBlock.GetBaselineOffset(element);
if (double.IsNaN(baseline))
baseline = size.Height;
return new TextEmbeddedObjectMetrics(size.Width, size.Height, baseline);
baseline = desiredSize.Height;
return new TextEmbeddedObjectMetrics(desiredSize.Width, desiredSize.Height, baseline);
}
/// <inheritdoc/>
@ -135,8 +130,8 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -135,8 +130,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (this.element.IsArrangeValid) {
double baseline = TextBlock.GetBaselineOffset(element);
if (double.IsNaN(baseline))
baseline = element.DesiredSize.Height;
return new Rect(new Point(0, -baseline), element.DesiredSize);
baseline = desiredSize.Height;
return new Rect(new Point(0, -baseline), desiredSize);
} else {
return Rect.Empty;
}

102
AvalonEdit/ICSharpCode.AvalonEdit/Rendering/MouseHoverLogic.cs

@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace ICSharpCode.AvalonEdit.Rendering
{
public class MouseHoverLogic : IDisposable
{
UIElement target;
DispatcherTimer mouseHoverTimer;
Point mouseHoverStartPoint;
MouseEventArgs mouseHoverLastEventArgs;
bool mouseHovering;
public MouseHoverLogic(UIElement target)
{
if (target == null)
throw new ArgumentNullException("target");
this.target = target;
this.target.MouseLeave += MouseHoverLogicMouseLeave;
this.target.MouseMove += MouseHoverLogicMouseMove;
}
void MouseHoverLogicMouseMove(object sender, MouseEventArgs e)
{
Point newPosition = e.GetPosition(this.target);
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.target.Dispatcher);
mouseHoverTimer.Start();
}
// do not set e.Handled - allow others to also handle MouseMove
}
void MouseHoverLogicMouseLeave(object sender, MouseEventArgs 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;
OnMouseHoverStopped(mouseHoverLastEventArgs);
}
}
void OnMouseHoverTimerElapsed(object sender, EventArgs e)
{
mouseHoverTimer.Stop();
mouseHoverTimer = null;
mouseHovering = true;
OnMouseHover(mouseHoverLastEventArgs);
}
public event EventHandler<MouseEventArgs> MouseHover;
protected virtual void OnMouseHover(MouseEventArgs e)
{
if (MouseHover != null) {
MouseHover(this, e);
}
}
public event EventHandler<MouseEventArgs> MouseHoverStopped;
protected virtual void OnMouseHoverStopped(MouseEventArgs e)
{
if (MouseHoverStopped != null) {
MouseHoverStopped(this, e);
}
}
bool disposed;
public void Dispose()
{
if (!disposed) {
this.target.MouseLeave -= MouseHoverLogicMouseLeave;
this.target.MouseMove -= MouseHoverLogicMouseMove;
}
disposed = true;
}
}
}

89
AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextLayer.cs

@ -24,6 +24,11 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -24,6 +24,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// </remarks>
sealed class TextLayer : Layer
{
/// <summary>
/// the index of the text layer in the layers collection
/// </summary>
internal int index;
public TextLayer(TextView textView) : base(textView, KnownLayer.Text)
{
}
@ -33,89 +38,5 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -33,89 +38,5 @@ namespace ICSharpCode.AvalonEdit.Rendering
base.OnRender(drawingContext);
textView.RenderTextLayer(drawingContext);
}
#region Inline object handling
internal List<InlineObjectRun> inlineObjects = new List<InlineObjectRun>();
/// <summary>
/// Adds a new inline object.
/// </summary>
internal void AddInlineObject(InlineObjectRun inlineObject)
{
inlineObjects.Add(inlineObject);
AddVisualChild(inlineObject.Element);
inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
}
List<VisualLine> visualLinesWithOutstandingInlineObjects = new List<VisualLine>();
internal 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.
visualLinesWithOutstandingInlineObjects.Add(visualLine);
}
internal void RemoveInlineObjectsNow()
{
inlineObjects.RemoveAll(
ior => {
if (visualLinesWithOutstandingInlineObjects.Contains(ior.VisualLine)) {
RemoveInlineObjectRun(ior);
return true;
}
return false;
});
visualLinesWithOutstandingInlineObjects.Clear();
}
// Remove InlineObjectRun.Element from TextLayer.
// Caller of RemoveInlineObjectRun will remove it from inlineObjects collection.
void RemoveInlineObjectRun(InlineObjectRun ior)
{
if (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 = textView;
while (element != null && !element.Focusable) {
element = VisualTreeHelper.GetParent(element) as UIElement;
}
if (element != null)
Keyboard.Focus(element);
}
ior.VisualLine = null;
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;
});
}
/// <inheritdoc/>
protected override int VisualChildrenCount {
get { return inlineObjects.Count; }
}
/// <inheritdoc/>
protected override Visual GetVisualChild(int index)
{
return inlineObjects[index].Element;
}
#endregion
}
}

244
AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs

@ -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>

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

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

3
AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs

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

Loading…
Cancel
Save