A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

189 lines
6.0 KiB

// -----------------------------------------------------------------------
// <copyright file="TextPresenter.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls.Presenters
{
using System;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Media;
using Perspex.Threading;
using Perspex.VisualTree;
public class TextPresenter : TextBlock, IPresenter
{
public static readonly PerspexProperty<int> CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner<TextPresenter>();
public static readonly PerspexProperty<int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>();
public static readonly PerspexProperty<int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<TextPresenter>();
private DispatcherTimer caretTimer;
private bool caretBlink;
private IObservable<bool> canScrollHorizontally;
public TextPresenter()
{
this.caretTimer = new DispatcherTimer();
this.caretTimer.Interval = TimeSpan.FromMilliseconds(500);
this.caretTimer.Tick += this.CaretTimerTick;
this.canScrollHorizontally = this.GetObservable(TextWrappingProperty)
.Select(x => x == TextWrapping.NoWrap);
Observable.Merge(
this.GetObservable(SelectionStartProperty),
this.GetObservable(SelectionEndProperty))
.Subscribe(_ => this.InvalidateFormattedText());
this.GetObservable(TextPresenter.CaretIndexProperty)
.Subscribe(this.CaretIndexChanged);
}
public int CaretIndex
{
get { return this.GetValue(CaretIndexProperty); }
set { this.SetValue(CaretIndexProperty, value); }
}
public int SelectionStart
{
get { return this.GetValue(SelectionStartProperty); }
set { this.SetValue(SelectionStartProperty, value); }
}
public int SelectionEnd
{
get { return this.GetValue(SelectionEndProperty); }
set { this.SetValue(SelectionEndProperty, value); }
}
public int GetCaretIndex(Point point)
{
var hit = this.FormattedText.HitTestPoint(point);
return hit.TextPosition + (hit.IsTrailing ? 1 : 0);
}
public override void Render(IDrawingContext context)
{
var selectionStart = this.SelectionStart;
var selectionEnd = this.SelectionEnd;
if (selectionStart != selectionEnd)
{
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
var rects = this.FormattedText.HitTestTextRange(start, length);
var brush = new SolidColorBrush(0xff086f9e);
foreach (var rect in rects)
{
context.FillRectange(brush, rect);
}
}
base.Render(context);
if (selectionStart == selectionEnd)
{
var charPos = this.FormattedText.HitTestTextPosition(this.CaretIndex);
Brush caretBrush = Brushes.Black;
if (this.caretBlink)
{
var x = Math.Floor(charPos.X) + 0.5;
var y = Math.Floor(charPos.Y) + 0.5;
var b = Math.Ceiling(charPos.Bottom) - 0.5;
context.DrawLine(
new Pen(caretBrush, 1),
new Point(x, y),
new Point(x, b));
}
}
}
public void ShowCaret()
{
this.caretBlink = true;
this.caretTimer.Start();
this.InvalidateVisual();
}
public void HideCaret()
{
this.caretBlink = false;
this.caretTimer.Stop();
this.InvalidateVisual();
}
internal void CaretIndexChanged(int caretIndex)
{
if (this.GetVisualParent() != null)
{
this.caretBlink = true;
this.caretTimer.Stop();
this.caretTimer.Start();
this.InvalidateVisual();
var rect = this.FormattedText.HitTestTextPosition(caretIndex);
this.BringIntoView(rect);
}
}
protected override FormattedText CreateFormattedText()
{
var result = base.CreateFormattedText();
var selectionStart = this.SelectionStart;
var selectionEnd = this.SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
if (length > 0)
{
result.SetForegroundBrush(Brushes.White, start, length);
}
return result;
}
protected override Size MeasureOverride(Size availableSize)
{
var text = this.Text;
if (!string.IsNullOrWhiteSpace(text))
{
return base.MeasureOverride(availableSize);
}
else
{
// TODO: Pretty sure that measuring "X" isn't the right way to do this...
using (var formattedText = new FormattedText(
"X",
this.FontFamily,
this.FontSize,
this.FontStyle,
TextAlignment.Left,
this.FontWeight))
{
return formattedText.Measure();
}
}
}
private void CaretTimerTick(object sender, EventArgs e)
{
this.caretBlink = !this.caretBlink;
this.InvalidateVisual();
}
}
}