@ -53,8 +53,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
this . Options = new TextEditorOptions ( ) ;
this . Options = new TextEditorOptions ( ) ;
Debug . Assert ( singleCharacterElementGenerator ! = null ) ; // assert that the option change created the builtin element generators
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 ) ;
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
#endregion
@ -294,7 +298,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
#region Layers
#region Layers
internal readonly TextLayer textLayer ;
internal readonly TextLayer textLayer ;
readonly UIElement Collection layers ;
readonly Layer Collection layers ;
/// <summary>
/// <summary>
/// Gets the list of layers displayed in the text view.
/// Gets the list of layers displayed in the text view.
@ -303,6 +307,47 @@ namespace ICSharpCode.AvalonEdit.Rendering
get { return layers ; }
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>
/// <summary>
/// Inserts a new layer at a position specified relative to an existing layer.
/// Inserts a new layer at a position specified relative to an existing layer.
/// </summary>
/// </summary>
@ -352,18 +397,128 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <inheritdoc/>
/// <inheritdoc/>
protected override int VisualChildrenCount {
protected override int VisualChildrenCount {
get { return layers . Count ; }
get { return layers . Count + inlineObjects . Count ; }
}
}
/// <inheritdoc/>
/// <inheritdoc/>
protected override Visual GetVisualChild ( int index )
protected override Visual GetVisualChild ( int index )
{
{
int cut = textLayer . index + 1 ;
if ( index < cut )
return layers [ index ] ;
return layers [ index ] ;
else if ( index < cut + inlineObjects . Count )
return inlineObjects [ index - cut ] . Element ;
else
return layers [ index - inlineObjects . Count ] ;
}
}
/// <inheritdoc/>
/// <inheritdoc/>
protected override System . Collections . IEnumerator LogicalChildren {
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
#endregion
@ -410,7 +565,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 ) ;
}
}
@ -422,7 +576,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
public void Redraw ( int offset , int length , DispatcherPriority redrawPriority = DispatcherPriority . Normal )
public void Redraw ( int offset , int length , DispatcherPriority redrawPriority = DispatcherPriority . Normal )
{
{
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 ] ;
@ -431,15 +584,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.
@ -492,11 +641,12 @@ 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 ( ) ;
}
}
textLayer . RemoveInlineObjects ( visualLine ) ;
RemoveInlineObjects ( visualLine ) ;
}
}
#endregion
#endregion
@ -677,11 +827,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
ClearVisualLines ( ) ;
ClearVisualLines ( ) ;
lastAvailableSize = availableSize ;
lastAvailableSize = availableSize ;
textLayer . 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 ( ) ;
@ -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 ;
maxWidth + = AdditionalHorizontalScrollAmount ;
double heightTreeHeight = this . DocumentHeight ;
double heightTreeHeight = this . DocumentHeight ;
@ -949,7 +1100,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
foreach ( var span in textLine . GetTextRunSpans ( ) ) {
foreach ( var span in textLine . GetTextRunSpans ( ) ) {
InlineObjectRun inline = span . Value as InlineObjectRun ;
InlineObjectRun inline = span . Value as InlineObjectRun ;
if ( inline ! = null & & inline . VisualLine ! = null ) {
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 ) ) ;
double distance = textLine . GetDistanceFromCharacterHit ( new CharacterHit ( offset , 0 ) ) ;
inline . Element . Arrange ( new Rect ( new Point ( pos . X + distance , pos . Y ) , inline . Element . DesiredSize ) ) ;
inline . Element . Arrange ( new Rect ( new Point ( pos . X + distance , pos . Y ) , inline . Element . DesiredSize ) ) ;
}
}
@ -1566,63 +1717,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
remove { RemoveHandler ( MouseHoverStoppedEvent , value ) ; }
remove { RemoveHandler ( MouseHoverStoppedEvent , value ) ; }
}
}
DispatcherTimer mouseHoverTimer ;
MouseHoverLogic hoverLogic ;
Point mouseHoverStartPoint ;
MouseEventArgs mouseHoverLastEventArgs ;
bool mouseHovering ;
/// <inheritdoc/>
protected override void OnMouseMove ( MouseEventArgs e )
{
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 ( )
void RaiseHoverEventPair ( MouseEventArgs e , RoutedEvent tunnelingEvent , RoutedEvent bubblingEvent )
{
{
if ( mouseHoverTimer ! = null ) {
var mouseDevice = e . MouseDevice ;
mouseHoverTimer . Stop ( ) ;
var stylusDevice = e . StylusDevice ;
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 ;
int inputTime = Environment . TickCount ;
int inputTime = Environment . TickCount ;
var args1 = new MouseEventArgs ( mouseDevice , inputTime , stylusDevice ) {
var args1 = new MouseEventArgs ( mouseDevice , inputTime , stylusDevice ) {
RoutedEvent = tunnelingEvent ,
RoutedEvent = tunnelingEvent ,
@ -1636,8 +1736,6 @@ namespace ICSharpCode.AvalonEdit.Rendering
} ;
} ;
RaiseEvent ( args2 ) ;
RaiseEvent ( args2 ) ;
}
}
#endregion
#endregion
/// <summary>
/// <summary>