diff --git a/Perspex.Controls/TextBlock.cs b/Perspex.Controls/TextBlock.cs index a38b48e8c4..47fd736a1a 100644 --- a/Perspex.Controls/TextBlock.cs +++ b/Perspex.Controls/TextBlock.cs @@ -7,12 +7,17 @@ namespace Perspex.Controls { using System; + using System.Reactive; + using System.Reactive.Linq; using Perspex.Media; using Perspex.Platform; using Splat; public class TextBlock : Control { + public static readonly PerspexProperty FontFamilyProperty = + PerspexProperty.Register("FontFamily", "Segoe UI", inherits: true); + public static readonly PerspexProperty FontSizeProperty = PerspexProperty.Register( "FontSize", @@ -25,27 +30,24 @@ namespace Perspex.Controls public static readonly PerspexProperty TextProperty = PerspexProperty.Register("Text"); - private FormattedText formattedText = new FormattedText(); + private FormattedText formattedText; public TextBlock() { - this.GetObservable(TextProperty).Subscribe(x => - { - this.formattedText.Text = x; - this.InvalidateMeasure(); - }); - - this.GetObservable(FontSizeProperty).Subscribe(x => - { - this.formattedText.FontSize = x; - this.InvalidateMeasure(); - }); - - this.GetObservable(FontStyleProperty).Subscribe(x => - { - this.formattedText.FontStyle = x; - this.InvalidateMeasure(); - }); + Observable.Merge( + this.GetObservable(TextProperty).Select(_ => Unit.Default), + this.GetObservable(FontSizeProperty).Select(_ => Unit.Default), + this.GetObservable(FontStyleProperty).Select(_ => Unit.Default)) + .Subscribe(_ => + { + if (this.formattedText != null) + { + this.formattedText.Dispose(); + this.formattedText = null; + } + + this.InvalidateMeasure(); + }); } public string Text @@ -54,6 +56,12 @@ namespace Perspex.Controls set { this.SetValue(TextProperty, value); } } + public string FontFamily + { + get { return this.GetValue(FontFamilyProperty); } + set { this.SetValue(FontFamilyProperty, value); } + } + public double FontSize { get { return this.GetValue(FontSizeProperty); } @@ -66,6 +74,24 @@ namespace Perspex.Controls set { this.SetValue(FontStyleProperty, value); } } + protected FormattedText FormattedText + { + get + { + if (this.formattedText == null) + { + this.formattedText = new FormattedText( + this.Text, + this.FontFamily, + this.FontSize, + this.FontStyle); + } + + return this.formattedText; + } + } + + public override void Render(IDrawingContext context) { Brush background = this.Background; @@ -75,18 +101,15 @@ namespace Perspex.Controls context.FillRectange(background, new Rect(this.ActualSize)); } - context.DrawText( - this.Foreground, - new Rect(this.ActualSize), - this.formattedText); + context.DrawText(this.Foreground, new Point(), this.FormattedText); } protected override Size MeasureOverride(Size availableSize) { if (!string.IsNullOrEmpty(this.Text)) { - this.formattedText.Constraint = availableSize; - return this.formattedText.Measure(); + this.FormattedText.Constraint = availableSize; + return this.FormattedText.Measure(); } return new Size(); diff --git a/Perspex.Controls/TextBox.cs b/Perspex.Controls/TextBox.cs index 5b4dfa963f..a93e663d7b 100644 --- a/Perspex.Controls/TextBox.cs +++ b/Perspex.Controls/TextBox.cs @@ -10,9 +10,7 @@ namespace Perspex.Controls using System.Linq; using Perspex.Controls.Primitives; using Perspex.Input; - using Perspex.Platform; using Perspex.Styling; - using Splat; public class TextBox : TemplatedControl { @@ -160,8 +158,7 @@ namespace Perspex.Controls private void OnPointerPressed(object sender, PointerEventArgs e) { var point = e.GetPosition(this.textBoxView); - var hit = this.textBoxView.FormattedText.HitTestPoint(point); - this.CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0); + this.CaretIndex = this.textBoxView.GetCaretIndex(point); } } } diff --git a/Perspex.Controls/TextBoxView.cs b/Perspex.Controls/TextBoxView.cs index b5f64fb99f..ddc3ab9112 100644 --- a/Perspex.Controls/TextBoxView.cs +++ b/Perspex.Controls/TextBoxView.cs @@ -8,55 +8,29 @@ namespace Perspex.Controls { using System; using Perspex.Media; - using Perspex.Platform; using Perspex.Threading; - using Splat; - internal class TextBoxView : Control + internal class TextBoxView : TextBlock { private TextBox parent; - private FormattedText formattedText; - private DispatcherTimer caretTimer; private bool caretBlink; public TextBoxView(TextBox parent) { - this.FormattedText = new FormattedText(); - - // TODO: Implement TextBlock.FontFamilyName. - this.FormattedText.FontFamilyName = "Segoe UI"; - - parent.GetObservable(TextBox.TextProperty).Subscribe(x => - { - this.FormattedText.Text = x; - this.InvalidateMeasure(); - }); - - this.GetObservable(TextBlock.FontSizeProperty).Subscribe(x => - { - this.FormattedText.FontSize = x; - this.InvalidateMeasure(); - }); - - this.GetObservable(TextBlock.FontStyleProperty).Subscribe(x => - { - this.FormattedText.FontStyle = x; - this.InvalidateMeasure(); - }); - this.caretTimer = new DispatcherTimer(); this.caretTimer.Interval = TimeSpan.FromMilliseconds(500); this.caretTimer.Tick += this.CaretTimerTick; this.parent = parent; + this[!TextProperty] = parent[!TextProperty]; } - public FormattedText FormattedText + public int GetCaretIndex(Point point) { - get; - private set; + var hit = this.FormattedText.HitTestPoint(point); + return hit.TextPosition + (hit.IsTrailing ? 1 : 0); } public new void GotFocus() @@ -73,9 +47,7 @@ namespace Perspex.Controls public override void Render(IDrawingContext context) { - Rect rect = new Rect(this.ActualSize); - - context.DrawText(Brushes.Black, rect, this.FormattedText); + base.Render(context); if (this.parent.IsFocused) { @@ -97,17 +69,6 @@ namespace Perspex.Controls this.InvalidateVisual(); } - protected override Size MeasureOverride(Size constraint) - { - if (!string.IsNullOrEmpty(this.parent.Text)) - { - this.FormattedText.Constraint = constraint; - return this.FormattedText.Measure(); - } - - return new Size(); - } - private void CaretTimerTick(object sender, EventArgs e) { this.caretBlink = !this.caretBlink; diff --git a/Perspex.SceneGraph/Media/FontStyle.cs b/Perspex.SceneGraph/Media/FontStyle.cs new file mode 100644 index 0000000000..2729233ecd --- /dev/null +++ b/Perspex.SceneGraph/Media/FontStyle.cs @@ -0,0 +1,15 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + public enum FontStyle + { + Normal, + Oblique, + Italic, + } +} diff --git a/Perspex.SceneGraph/Media/FormattedText.cs b/Perspex.SceneGraph/Media/FormattedText.cs index 61a7c54b3d..3d0915bc96 100644 --- a/Perspex.SceneGraph/Media/FormattedText.cs +++ b/Perspex.SceneGraph/Media/FormattedText.cs @@ -6,21 +6,25 @@ namespace Perspex.Media { + using System; using Perspex.Platform; using Splat; - public enum FontStyle + public class FormattedText : IDisposable { - Normal, - Oblique, - Italic, - } - - public class FormattedText - { - public FormattedText() + public FormattedText( + string text, + string fontFamilyName, + double fontSize, + FontStyle fontStyle) { - this.PlatformImpl = Locator.Current.GetService(); + this.Text = text; + this.FontFamilyName = fontFamilyName; + this.FontSize = fontSize; + this.FontStyle = fontStyle; + + var platform = Locator.Current.GetService(); + this.PlatformImpl = platform.CreateFormattedText(text, fontFamilyName, fontSize, fontStyle); } public Size Constraint @@ -31,26 +35,26 @@ namespace Perspex.Media public string FontFamilyName { - get { return this.PlatformImpl.FontFamilyName; } - set { this.PlatformImpl.FontFamilyName = value; } + get; + private set; } public double FontSize { - get { return this.PlatformImpl.FontSize; } - set { this.PlatformImpl.FontSize = value; } + get; + private set; } public FontStyle FontStyle { - get { return this.PlatformImpl.FontStyle; } - set { this.PlatformImpl.FontStyle = value; } + get; + private set; } public string Text { - get { return this.PlatformImpl.Text; } - set { this.PlatformImpl.Text = value; } + get; + private set; } public IFormattedTextImpl PlatformImpl @@ -59,6 +63,11 @@ namespace Perspex.Media private set; } + public void Dispose() + { + this.PlatformImpl.Dispose(); + } + public TextHitTestResult HitTestPoint(Point point) { return this.PlatformImpl.HitTestPoint(point); diff --git a/Perspex.SceneGraph/Media/IDrawingContext.cs b/Perspex.SceneGraph/Media/IDrawingContext.cs index 38d98b6b43..018812d7c1 100644 --- a/Perspex.SceneGraph/Media/IDrawingContext.cs +++ b/Perspex.SceneGraph/Media/IDrawingContext.cs @@ -45,9 +45,9 @@ namespace Perspex.Media /// Draws text. /// /// The foreground brush. - /// The bounding rectangle. + /// The upper-left corner of the text. /// The text. - void DrawText(Brush foreground, Rect rect, FormattedText text); + void DrawText(Brush foreground, Point origin, FormattedText text); /// /// Draws a filled rectangle. diff --git a/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/Perspex.SceneGraph/Perspex.SceneGraph.csproj index ae3f515249..60bd42f23a 100644 --- a/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -45,6 +45,7 @@ + diff --git a/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs b/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs index 914f5b55f9..91727b7b3b 100644 --- a/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs +++ b/Perspex.SceneGraph/Platform/IFormattedTextImpl.cs @@ -6,20 +6,13 @@ namespace Perspex.Platform { + using System; using Perspex.Media; - public interface IFormattedTextImpl + public interface IFormattedTextImpl : IDisposable { Size Constraint { get; set; } - string FontFamilyName { get; set; } - - double FontSize { get; set; } - - FontStyle FontStyle { get; set; } - - string Text { get; set; } - TextHitTestResult HitTestPoint(Point point); Rect HitTestTextPosition(int index); diff --git a/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs b/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs index a142e056bb..644e65db21 100644 --- a/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs +++ b/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs @@ -6,13 +6,18 @@ namespace Perspex.Platform { - using System; - using Perspex.Threading; + using Perspex.Media; public interface IPlatformRenderInterface { IBitmapImpl CreateBitmap(int width, int height); + IFormattedTextImpl CreateFormattedText( + string text, + string fontFamily, + double fontSize, + FontStyle fontStyle); + IStreamGeometryImpl CreateStreamGeometry(); IRenderer CreateRenderer(IPlatformHandle handle, double width, double height); diff --git a/Windows/Perspex.Direct2D1/Direct2D1Platform.cs b/Windows/Perspex.Direct2D1/Direct2D1Platform.cs index b070c417ec..f337f39f23 100644 --- a/Windows/Perspex.Direct2D1/Direct2D1Platform.cs +++ b/Windows/Perspex.Direct2D1/Direct2D1Platform.cs @@ -8,6 +8,7 @@ namespace Perspex.Direct2D1 { using System; using Perspex.Direct2D1.Media; + using Perspex.Media; using Perspex.Platform; using Perspex.Threading; using Splat; @@ -29,7 +30,6 @@ namespace Perspex.Direct2D1 locator.Register(() => d2d1Factory, typeof(SharpDX.Direct2D1.Factory)); locator.Register(() => dwfactory, typeof(SharpDX.DirectWrite.Factory)); locator.Register(() => imagingFactory, typeof(SharpDX.WIC.ImagingFactory)); - locator.Register(() => new FormattedTextImpl(), typeof(IFormattedTextImpl)); } public IBitmapImpl CreateBitmap(int width, int height) @@ -37,6 +37,15 @@ namespace Perspex.Direct2D1 return new BitmapImpl(imagingFactory, width, height); } + public IFormattedTextImpl CreateFormattedText( + string text, + string fontFamily, + double fontSize, + FontStyle fontStyle) + { + return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle); + } + public IRenderer CreateRenderer(IPlatformHandle handle, double width, double height) { if (handle.HandleDescriptor == "HWND") diff --git a/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/Windows/Perspex.Direct2D1/Media/DrawingContext.cs index f9f5151a37..e9c88f1b6d 100644 --- a/Windows/Perspex.Direct2D1/Media/DrawingContext.cs +++ b/Windows/Perspex.Direct2D1/Media/DrawingContext.cs @@ -134,9 +134,9 @@ namespace Perspex.Direct2D1.Media /// Draws text. /// /// The foreground brush. - /// The output rectangle. + /// The upper-left corner of the text. /// The text. - public void DrawText(Perspex.Media.Brush foreground, Rect rect, FormattedText text) + public void DrawText(Perspex.Media.Brush foreground, Perspex.Point origin, FormattedText text) { if (!string.IsNullOrEmpty(text.Text)) { @@ -144,10 +144,9 @@ namespace Perspex.Direct2D1.Media using (SharpDX.Direct2D1.SolidColorBrush brush = this.Convert(foreground)) { - this.renderTarget.DrawText( - text.Text, - impl.Layout, - this.Convert(rect), + this.renderTarget.DrawTextLayout( + origin.ToSharpDX(), + impl.TextLayout, brush); } } diff --git a/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs b/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs index bb517efeb0..b5e987b213 100644 --- a/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs +++ b/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs @@ -14,121 +14,45 @@ namespace Perspex.Direct2D1.Media public class FormattedTextImpl : IFormattedTextImpl { - private string text; - - private string fontFamilyName = "Ariel"; - - private double fontSize = 10; - - private FontStyle fontStyle; - - private DWrite.Factory factory; - - private DWrite.TextLayout layout; - - public FormattedTextImpl() + public FormattedTextImpl( + string text, + string fontFamily, + double fontSize, + FontStyle fontStyle) { - this.factory = Locator.Current.GetService(); + var factory = Locator.Current.GetService(); + + this.TextLayout = new DWrite.TextLayout( + factory, + text ?? string.Empty, + new DWrite.TextFormat(factory, fontFamily, (float)fontSize), + float.MaxValue, + float.MaxValue); } public Size Constraint { get { - return new Size(this.Layout.MaxWidth, this.Layout.MaxHeight); - } - - set - { - this.Layout.MaxWidth = (float)value.Width; - this.Layout.MaxHeight = (float)value.Height; - } - } - - public string FontFamilyName - { - get - { - return this.fontFamilyName; - } - - set - { - if (this.fontFamilyName != value) - { - this.fontFamilyName = value; - this.DisposeLayout(); - } - } - } - - public double FontSize - { - get - { - return this.fontSize; + return new Size(this.TextLayout.MaxWidth, this.TextLayout.MaxHeight); } set { - if (this.fontSize != value) - { - this.fontSize = value; - this.DisposeLayout(); - } + this.TextLayout.MaxWidth = (float)value.Width; + this.TextLayout.MaxHeight = (float)value.Height; } } - public FontStyle FontStyle + public DWrite.TextLayout TextLayout { - get - { - return this.fontStyle; - } - - set - { - if (this.fontStyle != value) - { - this.fontStyle = value; - this.DisposeLayout(); - } - } - } - - public string Text - { - get - { - return this.text; - } - - set - { - if (this.text != value) - { - this.text = value; - this.DisposeLayout(); - } - } + get; + private set; } - public DWrite.TextLayout Layout + public void Dispose() { - get - { - if (this.layout == null) - { - this.layout = new DWrite.TextLayout( - this.factory, - this.text ?? string.Empty, - new DWrite.TextFormat(this.factory, this.fontFamilyName, (float)this.fontSize), - float.MaxValue, - float.MaxValue); - } - - return this.layout; - } + this.TextLayout.Dispose(); } public TextHitTestResult HitTestPoint(Point point) @@ -136,7 +60,7 @@ namespace Perspex.Direct2D1.Media SharpDX.Bool isTrailingHit; SharpDX.Bool isInside; - DWrite.HitTestMetrics result = layout.HitTestPoint( + DWrite.HitTestMetrics result = this.TextLayout.HitTestPoint( (float)point.X, (float)point.Y, out isTrailingHit, @@ -154,7 +78,7 @@ namespace Perspex.Direct2D1.Media float x; float y; - DWrite.HitTestMetrics result = layout.HitTestTextPosition( + DWrite.HitTestMetrics result = this.TextLayout.HitTestTextPosition( index, false, out x, @@ -166,17 +90,8 @@ namespace Perspex.Direct2D1.Media public Size Measure() { return new Size( - layout.Metrics.WidthIncludingTrailingWhitespace, - layout.Metrics.Height); - } - - private void DisposeLayout() - { - if (this.layout != null) - { - this.layout.Dispose(); - this.layout = null; - } + this.TextLayout.Metrics.WidthIncludingTrailingWhitespace, + this.TextLayout.Metrics.Height); } } }