diff --git a/Perspex/Controls/ContentControl.cs b/Perspex/Controls/ContentControl.cs index 5cb7e47087..54bf329ef8 100644 --- a/Perspex/Controls/ContentControl.cs +++ b/Perspex/Controls/ContentControl.cs @@ -47,36 +47,5 @@ namespace Perspex.Controls return Enumerable.Repeat(logicalChild, logicalChild != null ? 1 : 0); } } - - protected override Size ArrangeOverride(Size finalSize) - { - Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; - - if (child != null) - { - child.Arrange(new Rect(finalSize)); - return child.ActualSize; - } - else - { - return new Size(); - } - } - - protected override Size MeasureOverride(Size availableSize) - { - if (this.Visibility != Visibility.Collapsed) - { - Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; - - if (child != null) - { - child.Measure(availableSize); - return child.DesiredSize.Value; - } - } - - return new Size(); - } } } diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs index 6c54d9a55a..53b99914b4 100644 --- a/Perspex/Controls/Control.cs +++ b/Perspex/Controls/Control.cs @@ -33,7 +33,7 @@ namespace Perspex.Controls Bottom, } - public class Control : Interactive, ILayoutable, ILogical, IStyleable, IStyled + public class Control : Interactive, ILayoutable, IFocusable, ILogical, IStyleable, IStyled { public static readonly PerspexProperty BackgroundProperty = PerspexProperty.Register("Background", inherits: true); @@ -44,6 +44,9 @@ namespace Perspex.Controls public static readonly PerspexProperty BorderThicknessProperty = PerspexProperty.Register("BorderThickness"); + public static readonly PerspexProperty FocusableProperty = + PerspexProperty.Register("Focusable"); + public static readonly PerspexProperty FontSizeProperty = PerspexProperty.Register( "FontSize", @@ -56,6 +59,9 @@ namespace Perspex.Controls public static readonly PerspexProperty HeightProperty = PerspexProperty.Register("Height", double.NaN); + public static readonly PerspexProperty IsFocusedProperty = + PerspexProperty.Register("IsFocused", false); + public static readonly PerspexProperty IsPointerOverProperty = PerspexProperty.Register("IsPointerOver"); @@ -129,6 +135,18 @@ namespace Perspex.Controls this.Classes.Remove(":pointerover"); } }); + + this.GetObservable(IsFocusedProperty).Subscribe(x => + { + if (x) + { + this.Classes.Add(":focus"); + } + else + { + this.Classes.Remove(":focus"); + } + }); } public event EventHandler PointerEnter @@ -207,12 +225,30 @@ namespace Perspex.Controls set { this.SetValue(FontSizeProperty, value); } } + public bool Focusable + { + get { return this.GetValue(FocusableProperty); } + set { this.SetValue(FocusableProperty, value); } + } + public Brush Foreground { get { return this.GetValue(ForegroundProperty); } set { this.SetValue(ForegroundProperty, value); } } + public double Height + { + get { return this.GetValue(HeightProperty); } + set { this.SetValue(HeightProperty, value); } + } + + public bool IsFocused + { + get { return this.GetValue(IsFocusedProperty); } + set { this.SetValue(IsFocusedProperty, value); } + } + public string Id { get @@ -236,12 +272,6 @@ namespace Perspex.Controls } } - public double Height - { - get { return this.GetValue(HeightProperty); } - set { this.SetValue(HeightProperty, value); } - } - public bool IsPointerOver { get { return this.GetValue(IsPointerOverProperty); } @@ -353,6 +383,11 @@ namespace Perspex.Controls this.DesiredSize = this.MeasureCore(availableSize).Constrain(availableSize); } + public void Focus() + { + this.IsFocused = true; + } + public void InvalidateArrange() { ILayoutRoot root = this.GetLayoutRoot(); diff --git a/Perspex/Controls/TemplatedControl.cs b/Perspex/Controls/TemplatedControl.cs index 7f24a948f7..9d75c5f819 100644 --- a/Perspex/Controls/TemplatedControl.cs +++ b/Perspex/Controls/TemplatedControl.cs @@ -45,6 +45,7 @@ namespace Perspex.Controls this.visualChild = template.Build(this); this.visualChild.VisualParent = this; + this.OnTemplateApplied(); } return Enumerable.Repeat(this.visualChild, this.visualChild != null ? 1 : 0); @@ -59,5 +60,40 @@ namespace Perspex.Controls public sealed override void Render(IDrawingContext context) { } + + protected override Size ArrangeOverride(Size finalSize) + { + Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; + + if (child != null) + { + child.Arrange(new Rect(finalSize)); + return child.ActualSize; + } + else + { + return new Size(); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + if (this.Visibility != Visibility.Collapsed) + { + Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; + + if (child != null) + { + child.Measure(availableSize); + return child.DesiredSize.Value; + } + } + + return new Size(); + } + + protected virtual void OnTemplateApplied() + { + } } } diff --git a/Perspex/Controls/TextBlock.cs b/Perspex/Controls/TextBlock.cs index a96f711aea..d517521166 100644 --- a/Perspex/Controls/TextBlock.cs +++ b/Perspex/Controls/TextBlock.cs @@ -59,12 +59,9 @@ namespace Perspex.Controls { if (this.Visibility != Visibility.Collapsed) { - IPlatformFactory factory = Locator.Current.GetService(); - ITextService service = factory.GetTextService(); - if (!string.IsNullOrEmpty(this.Text)) { - return service.Measure(this.FormattedText); + return this.FormattedText.Size; } } diff --git a/Perspex/Controls/TextBox.cs b/Perspex/Controls/TextBox.cs new file mode 100644 index 0000000000..f8d9eaef76 --- /dev/null +++ b/Perspex/Controls/TextBox.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Linq; + using Perspex.Styling; + + public class TextBox : TemplatedControl + { + public static readonly PerspexProperty TextProperty = + TextBlock.TextProperty.AddOwner(); + + private int caretIndex; + + private TextBoxView textBoxView; + + public TextBox() + { + this.GetObservable(TextProperty).Subscribe(_ => this.InvalidateVisual()); + FocusableProperty.OverrideDefaultValue(typeof(TextBox), true); + } + + public int CaretIndex + { + get + { + return this.caretIndex; + } + + set + { + value = Math.Min(Math.Max(value, 0), this.Text.Length); + + if (this.caretIndex != value) + { + this.caretIndex = value; + this.textBoxView.CaretMoved(); + } + } + } + + public string Text + { + get { return this.GetValue(TextProperty); } + set { this.SetValue(TextProperty, value); } + } + + protected override void OnTemplateApplied() + { + Decorator textContainer = this.GetVisualDescendents() + .OfType() + .FirstOrDefault(x => x.Id == "textContainer"); + + if (textContainer == null) + { + throw new Exception( + "TextBox template doesn't contain a textContainer " + + "or textContainer is not a Decorator."); + } + + textContainer.Content = this.textBoxView = new TextBoxView(this); + } + } +} diff --git a/Perspex/Controls/TextBoxView.cs b/Perspex/Controls/TextBoxView.cs new file mode 100644 index 0000000000..4bdfcc7999 --- /dev/null +++ b/Perspex/Controls/TextBoxView.cs @@ -0,0 +1,125 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Globalization; + using Perspex.Media; + + internal class TextBoxView : Control + { + private TextBox parent; + + private FormattedText formattedText; + + //private DispatcherTimer caretTimer; + + private bool caretBlink; + + public TextBoxView(TextBox parent) + { + //this.caretTimer = new DispatcherTimer(); + //this.caretTimer.Interval = PlatformInterface.Instance.CaretBlinkTime; + //this.caretTimer.Tick += this.CaretTimerTick; + this.parent = parent; + } + + public FormattedText FormattedText + { + get + { + if (this.formattedText == null) + { + this.formattedText = this.CreateFormattedText(); + } + + return this.formattedText; + } + } + + public void GotFocus() + { + this.caretBlink = true; + //this.caretTimer.Start(); + } + + public void LostFocus() + { + //this.caretTimer.Stop(); + this.InvalidateVisual(); + } + + public void InvalidateText() + { + this.formattedText = null; + this.InvalidateMeasure(); + } + + internal void CaretMoved() + { + //this.caretBlink = true; + //this.caretTimer.Stop(); + //this.caretTimer.Start(); + this.InvalidateVisual(); + } + + public override void Render(IDrawingContext context) + { + Rect rect = new Rect(this.ActualSize); + + context.DrawText(Brushes.Black, rect, this.FormattedText); + + //if (this.parent.IsKeyboardFocused) + //{ + // Point caretPos = this.FormattedText.GetCaretPosition(this.parent.CaretIndex); + // Brush caretBrush = this.parent.CaretBrush; + + // if (caretBrush == null) + // { + // Color color = Colors.Black; + // SolidColorBrush background = this.parent.Background as SolidColorBrush; + + // if (background != null) + // { + // color = Color.FromUInt32(0x00ffffffu ^ background.Color.ToUint32()); + // } + + // caretBrush = new SolidColorBrush(color); + // } + + // if (this.caretBlink) + // { + // drawingContext.DrawLine( + // new Pen(caretBrush, 1), + // caretPos, + // caretPos + new Vector(0, this.FormattedText.Height)); + // } + //} + } + + protected override Size MeasureOverride(Size constraint) + { + return new Size(this.FormattedText.Size.Width, this.FormattedText.Size.Height); + } + + private FormattedText CreateFormattedText() + { + return new FormattedText + { + FontFamilyName = "Segoe UI", + FontSize = this.FontSize, + Text = this.parent.Text, + }; + } + + private void CaretTimerTick(object sender, EventArgs e) + { + this.caretBlink = !this.caretBlink; + this.InvalidateVisual(); + } + } +} diff --git a/Perspex/Input/FocusEventArgs.cs b/Perspex/Input/FocusEventArgs.cs new file mode 100644 index 0000000000..ac99821e31 --- /dev/null +++ b/Perspex/Input/FocusEventArgs.cs @@ -0,0 +1,14 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + + public class FocusEventArgs : RoutedEventArgs + { + } +} diff --git a/Perspex/Input/IFocusable.cs b/Perspex/Input/IFocusable.cs new file mode 100644 index 0000000000..03ec8d3561 --- /dev/null +++ b/Perspex/Input/IFocusable.cs @@ -0,0 +1,17 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + public interface IFocusable + { + bool Focusable { get; } + + bool IsFocused { get; } + + void Focus(); + } +} diff --git a/Perspex/Input/InputManager.cs b/Perspex/Input/InputManager.cs index eee08ae83f..4b2da523e3 100644 --- a/Perspex/Input/InputManager.cs +++ b/Perspex/Input/InputManager.cs @@ -66,18 +66,26 @@ namespace Perspex.Input if (hit != null) { - Interactive source = device.Captured ?? (hit as Interactive) ?? hit.GetVisualAncestor(); + Interactive interactive = device.Captured ?? (hit as Interactive) ?? hit.GetVisualAncestor(); + IFocusable focusable = + device.Captured as IFocusable ?? + hit.GetVisualAncestorsAndSelf().OfType().FirstOrDefault(x => x.Focusable); - if (source != null) + if (interactive != null) { - source.RaiseEvent(new PointerEventArgs + interactive.RaiseEvent(new PointerEventArgs { Device = device, RoutedEvent = Control.PointerPressedEvent, - OriginalSource = source, - Source = source, + OriginalSource = interactive, + Source = interactive, }); } + + if (focusable != null && focusable.Focusable) + { + focusable.Focus(); + } } } diff --git a/Perspex/Media/FormattedText.cs b/Perspex/Media/FormattedText.cs index 556d2a9f9f..63bb0f5ead 100644 --- a/Perspex/Media/FormattedText.cs +++ b/Perspex/Media/FormattedText.cs @@ -6,6 +6,9 @@ namespace Perspex.Media { + using Perspex.Platform; + using Splat; + public class FormattedText { public string FontFamilyName { get; set; } @@ -13,5 +16,15 @@ namespace Perspex.Media public double FontSize { get; set; } public string Text { get; set; } + + public Size Size + { + get + { + IPlatformFactory factory = Locator.Current.GetService(); + ITextService service = factory.GetTextService(); + return service.Measure(this); + } + } } } diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj index 934326132a..ad9662ee7c 100644 --- a/Perspex/Perspex.csproj +++ b/Perspex/Perspex.csproj @@ -78,15 +78,19 @@ + + + + @@ -163,6 +167,7 @@ + @@ -173,7 +178,7 @@ - ..\packages\Splat.1.1.1\lib\Portable-Net45+WinRT45+WP8\Splat.dll + ..\packages\Splat.1.3.3\lib\Portable-net45+win+wpa81+wp80\Splat.dll ..\packages\Rx-Core.2.1.30214.0\lib\Portable-Net45+WinRT45+WP8\System.Reactive.Core.dll diff --git a/Perspex/Themes/Default/DefaultTheme.cs b/Perspex/Themes/Default/DefaultTheme.cs index e092d9975a..abb18a70b7 100644 --- a/Perspex/Themes/Default/DefaultTheme.cs +++ b/Perspex/Themes/Default/DefaultTheme.cs @@ -14,6 +14,8 @@ namespace Perspex.Themes.Default { this.Add(new ButtonStyle()); this.Add(new CheckBoxStyle()); + this.Add(new TextBoxStyle()); + } } } diff --git a/Perspex/Themes/Default/TextBoxStyle.cs b/Perspex/Themes/Default/TextBoxStyle.cs new file mode 100644 index 0000000000..7b36d60a18 --- /dev/null +++ b/Perspex/Themes/Default/TextBoxStyle.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Themes.Default +{ + using System; + using System.Linq; + using Perspex.Controls; + using Perspex.Media; + using Perspex.Shapes; + using Perspex.Styling; + + public class TextBoxStyle : Styles + { + public TextBoxStyle() + { + this.AddRange(new[] + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter(Button.TemplateProperty, ControlTemplate.Create(this.Template)), + new Setter(TextBox.BorderBrushProperty, new SolidColorBrush(0xff707070)), + new Setter(TextBox.BorderThicknessProperty, 2.0), + }, + }, + new Style(x => x.OfType().Class(":focus").Template().Id("border")) + { + Setters = new[] + { + new Setter(TextBox.BorderBrushProperty, Brushes.Black), + }, + } + }); + } + + private Control Template(TextBox control) + { + Border result = new Border + { + Id = "border", + [~Border.BackgroundProperty] = control[~TextBox.BackgroundProperty], + [~Border.BorderBrushProperty] = control[~TextBox.BorderBrushProperty], + [~Border.BorderThicknessProperty] = control[~TextBox.BorderThicknessProperty], + Content = new Decorator + { + Id = "textContainer", + } + }; + + return result; + } + } +} diff --git a/Perspex/Visual.cs b/Perspex/Visual.cs index 96fb3a63de..3887a6a05b 100644 --- a/Perspex/Visual.cs +++ b/Perspex/Visual.cs @@ -9,7 +9,6 @@ namespace Perspex using System; using System.Collections.Generic; using System.Linq; - using Perspex.Controls; using Perspex.Layout; using Perspex.Media; using Perspex.Rendering; diff --git a/Perspex/VisualExtensions.cs b/Perspex/VisualExtensions.cs index 93e58b1724..427adf2e4f 100644 --- a/Perspex/VisualExtensions.cs +++ b/Perspex/VisualExtensions.cs @@ -69,6 +69,19 @@ namespace Perspex } } + public static IEnumerable GetVisualAncestorsAndSelf(this IVisual visual) + { + yield return visual; + + visual = visual.VisualParent; + + while (visual != null) + { + yield return visual; + visual = visual.VisualParent; + } + } + public static IVisual GetVisualAt(this IVisual visual, Point p) { Contract.Requires(visual != null); diff --git a/Perspex/packages.config b/Perspex/packages.config index d96c5b1754..e4448261c1 100644 --- a/Perspex/packages.config +++ b/Perspex/packages.config @@ -5,6 +5,6 @@ - + \ No newline at end of file diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 482b903d0f..c0974d4a39 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -18,12 +18,29 @@ using Splat; namespace TestApplication { + class TestLogger : ILogger + { + public LogLevel Level + { + get; + set; + } + + public void Write(string message, LogLevel logLevel) + { + if ((int)logLevel < (int)Level) return; + System.Diagnostics.Debug.WriteLine(message); + } + } + class Program { static void Main(string[] args) { App application = new App(); + Locator.CurrentMutable.Register(() => new TestLogger { Level = LogLevel.Debug } , typeof(ILogger)); + Window window = new Window { Content = new StackPanel @@ -47,6 +64,10 @@ namespace TestApplication { Content = "Checkbox", }, + new TextBox + { + Text = "Hello World!", + } } } }; diff --git a/TestApplication/TestApplication.csproj b/TestApplication/TestApplication.csproj index 7c587efdab..f7524686ca 100644 --- a/TestApplication/TestApplication.csproj +++ b/TestApplication/TestApplication.csproj @@ -31,6 +31,9 @@ prompt 4 + + + False diff --git a/packages/Splat.1.3.3/Splat.1.3.3.nupkg b/packages/Splat.1.3.3/Splat.1.3.3.nupkg new file mode 100644 index 0000000000..5d80a012cb Binary files /dev/null and b/packages/Splat.1.3.3/Splat.1.3.3.nupkg differ diff --git a/packages/Splat.1.3.3/lib/MonoMac/Splat.dll b/packages/Splat.1.3.3/lib/MonoMac/Splat.dll new file mode 100644 index 0000000000..ca510578b4 Binary files /dev/null and b/packages/Splat.1.3.3/lib/MonoMac/Splat.dll differ diff --git a/packages/Splat.1.3.3/lib/MonoMac/Splat.dll.mdb b/packages/Splat.1.3.3/lib/MonoMac/Splat.dll.mdb new file mode 100644 index 0000000000..c1562268ec Binary files /dev/null and b/packages/Splat.1.3.3/lib/MonoMac/Splat.dll.mdb differ diff --git a/packages/Splat.1.3.3/lib/Net45/Splat.dll b/packages/Splat.1.3.3/lib/Net45/Splat.dll new file mode 100644 index 0000000000..007f514b5b Binary files /dev/null and b/packages/Splat.1.3.3/lib/Net45/Splat.dll differ diff --git a/packages/Splat.1.3.3/lib/NetCore45/Splat.dll b/packages/Splat.1.3.3/lib/NetCore45/Splat.dll new file mode 100644 index 0000000000..05a249e1bc Binary files /dev/null and b/packages/Splat.1.3.3/lib/NetCore45/Splat.dll differ diff --git a/packages/Splat.1.3.3/lib/NetCore45/Splat.pri b/packages/Splat.1.3.3/lib/NetCore45/Splat.pri new file mode 100644 index 0000000000..f340032854 Binary files /dev/null and b/packages/Splat.1.3.3/lib/NetCore45/Splat.pri differ diff --git a/packages/Splat.1.3.3/lib/Portable-Win81+Wpa81/Splat.dll b/packages/Splat.1.3.3/lib/Portable-Win81+Wpa81/Splat.dll new file mode 100644 index 0000000000..e0b7bafb72 Binary files /dev/null and b/packages/Splat.1.3.3/lib/Portable-Win81+Wpa81/Splat.dll differ diff --git a/packages/Splat.1.3.3/lib/Portable-Win81+Wpa81/Splat.pri b/packages/Splat.1.3.3/lib/Portable-Win81+Wpa81/Splat.pri new file mode 100644 index 0000000000..6f53372767 Binary files /dev/null and b/packages/Splat.1.3.3/lib/Portable-Win81+Wpa81/Splat.pri differ diff --git a/packages/Splat.1.3.3/lib/Portable-net45+win+wpa81+wp80/Splat.dll b/packages/Splat.1.3.3/lib/Portable-net45+win+wpa81+wp80/Splat.dll new file mode 100644 index 0000000000..29422ae2ab Binary files /dev/null and b/packages/Splat.1.3.3/lib/Portable-net45+win+wpa81+wp80/Splat.dll differ diff --git a/packages/Splat.1.3.3/lib/monoandroid/Splat.dll b/packages/Splat.1.3.3/lib/monoandroid/Splat.dll new file mode 100644 index 0000000000..e2b1e5a5ae Binary files /dev/null and b/packages/Splat.1.3.3/lib/monoandroid/Splat.dll differ diff --git a/packages/Splat.1.3.3/lib/monoandroid/Splat.dll.mdb b/packages/Splat.1.3.3/lib/monoandroid/Splat.dll.mdb new file mode 100644 index 0000000000..cc534c361d Binary files /dev/null and b/packages/Splat.1.3.3/lib/monoandroid/Splat.dll.mdb differ diff --git a/packages/Splat.1.3.3/lib/monotouch/Splat.dll b/packages/Splat.1.3.3/lib/monotouch/Splat.dll new file mode 100644 index 0000000000..e331db6527 Binary files /dev/null and b/packages/Splat.1.3.3/lib/monotouch/Splat.dll differ diff --git a/packages/Splat.1.3.3/lib/monotouch/Splat.dll.mdb b/packages/Splat.1.3.3/lib/monotouch/Splat.dll.mdb new file mode 100644 index 0000000000..836554aa34 Binary files /dev/null and b/packages/Splat.1.3.3/lib/monotouch/Splat.dll.mdb differ diff --git a/packages/Splat.1.3.3/lib/wp8/Splat.dll b/packages/Splat.1.3.3/lib/wp8/Splat.dll new file mode 100644 index 0000000000..3fdbefdd38 Binary files /dev/null and b/packages/Splat.1.3.3/lib/wp8/Splat.dll differ