From 6d31e7d865ed4bae604bc974399de228bd0fcce3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Jun 2014 21:01:29 +0200 Subject: [PATCH] Started on TextBox. Can type but not much else. --- Perspex.Direct2D1/Direct2D1Platform.cs | 4 +- Perspex.Windows/Input/KeyInterop.cs | 381 ++++++++++++++++++ .../Input/WindowsKeyboardDevice.cs | 96 +++++ Perspex.Windows/Input/WindowsMouseDevice.cs | 3 +- Perspex.Windows/Perspex.Windows.csproj | 2 + Perspex.Windows/Window.cs | 20 +- Perspex/Controls/Control.cs | 84 ++-- Perspex/Controls/TextBox.cs | 61 ++- Perspex/Controls/ToggleButton.cs | 13 +- Perspex/Input/FocusManager.cs | 26 +- Perspex/Input/IFocusable.cs | 2 +- Perspex/Input/IKeyboardDevice.cs | 33 ++ Perspex/Input/Key.cs | 214 ++++++++++ .../{FocusEventArgs.cs => KeyEventArgs.cs} | 9 +- Perspex/Input/KeyboardDevice.cs | 61 +++ Perspex/Input/MouseDevice.cs | 6 +- Perspex/Input/Raw/RawKeyEventArgs.cs | 31 ++ Perspex/Media/FormattedText.cs | 2 +- Perspex/Media/Imaging/Bitmap.cs | 2 +- Perspex/Media/Imaging/RenderTargetBitmap.cs | 2 +- Perspex/Media/RectangleGeometry.cs | 2 +- Perspex/Media/StreamGeometry.cs | 2 +- Perspex/Perspex.csproj | 8 +- ...atformFactory.cs => IPlatformInterface.cs} | 2 +- 24 files changed, 983 insertions(+), 83 deletions(-) create mode 100644 Perspex.Windows/Input/KeyInterop.cs create mode 100644 Perspex.Windows/Input/WindowsKeyboardDevice.cs create mode 100644 Perspex/Input/IKeyboardDevice.cs create mode 100644 Perspex/Input/Key.cs rename Perspex/Input/{FocusEventArgs.cs => KeyEventArgs.cs} (54%) create mode 100644 Perspex/Input/KeyboardDevice.cs create mode 100644 Perspex/Input/Raw/RawKeyEventArgs.cs rename Perspex/Platform/{IPlatformFactory.cs => IPlatformInterface.cs} (94%) diff --git a/Perspex.Direct2D1/Direct2D1Platform.cs b/Perspex.Direct2D1/Direct2D1Platform.cs index 2679a9a1ee..fd5e9bd376 100644 --- a/Perspex.Direct2D1/Direct2D1Platform.cs +++ b/Perspex.Direct2D1/Direct2D1Platform.cs @@ -11,7 +11,7 @@ namespace Perspex.Direct2D1 using Perspex.Platform; using Splat; - public class Direct2D1Platform : IPlatformFactory + public class Direct2D1Platform : IPlatformInterface { private static Direct2D1Platform instance = new Direct2D1Platform(); @@ -26,7 +26,7 @@ namespace Perspex.Direct2D1 public static void Initialize() { var locator = Locator.CurrentMutable; - locator.Register(() => instance, typeof(IPlatformFactory)); + locator.Register(() => instance, typeof(IPlatformInterface)); locator.Register(() => d2d1Factory, typeof(SharpDX.Direct2D1.Factory)); locator.Register(() => dwFactory, typeof(SharpDX.DirectWrite.Factory)); locator.Register(() => imagingFactory, typeof(SharpDX.WIC.ImagingFactory)); diff --git a/Perspex.Windows/Input/KeyInterop.cs b/Perspex.Windows/Input/KeyInterop.cs new file mode 100644 index 0000000000..966b7b83ba --- /dev/null +++ b/Perspex.Windows/Input/KeyInterop.cs @@ -0,0 +1,381 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Windows.Input +{ + using System.Collections.Generic; + using System.Text; + using Perspex.Input; + using Perspex.Windows.Interop; + + public static class KeyInterop + { + private static Dictionary virtualKeyFromKey = new Dictionary + { + { Key.None, 0 }, + { Key.Cancel, 3 }, + { Key.Back, 8 }, + { Key.Tab, 9 }, + { Key.LineFeed, 0 }, + { Key.Clear, 12 }, + { Key.Return, 13 }, + { Key.Pause, 19 }, + { Key.Capital, 20 }, + { Key.KanaMode, 21 }, + { Key.JunjaMode, 23 }, + { Key.FinalMode, 24 }, + { Key.HanjaMode, 25 }, + { Key.Escape, 27 }, + { Key.ImeConvert, 28 }, + { Key.ImeNonConvert, 29 }, + { Key.ImeAccept, 30 }, + { Key.ImeModeChange, 31 }, + { Key.Space, 32 }, + { Key.PageUp, 33 }, + { Key.Next, 34 }, + { Key.End, 35 }, + { Key.Home, 36 }, + { Key.Left, 37 }, + { Key.Up, 38 }, + { Key.Right, 39 }, + { Key.Down, 40 }, + { Key.Select, 41 }, + { Key.Print, 42 }, + { Key.Execute, 43 }, + { Key.Snapshot, 44 }, + { Key.Insert, 45 }, + { Key.Delete, 46 }, + { Key.Help, 47 }, + { Key.D0, 48 }, + { Key.D1, 49 }, + { Key.D2, 50 }, + { Key.D3, 51 }, + { Key.D4, 52 }, + { Key.D5, 53 }, + { Key.D6, 54 }, + { Key.D7, 55 }, + { Key.D8, 56 }, + { Key.D9, 57 }, + { Key.A, 65 }, + { Key.B, 66 }, + { Key.C, 67 }, + { Key.D, 68 }, + { Key.E, 69 }, + { Key.F, 70 }, + { Key.G, 71 }, + { Key.H, 72 }, + { Key.I, 73 }, + { Key.J, 74 }, + { Key.K, 75 }, + { Key.L, 76 }, + { Key.M, 77 }, + { Key.N, 78 }, + { Key.O, 79 }, + { Key.P, 80 }, + { Key.Q, 81 }, + { Key.R, 82 }, + { Key.S, 83 }, + { Key.T, 84 }, + { Key.U, 85 }, + { Key.V, 86 }, + { Key.W, 87 }, + { Key.X, 88 }, + { Key.Y, 89 }, + { Key.Z, 90 }, + { Key.LWin, 91 }, + { Key.RWin, 92 }, + { Key.Apps, 93 }, + { Key.Sleep, 95 }, + { Key.NumPad0, 96 }, + { Key.NumPad1, 97 }, + { Key.NumPad2, 98 }, + { Key.NumPad3, 99 }, + { Key.NumPad4, 100 }, + { Key.NumPad5, 101 }, + { Key.NumPad6, 102 }, + { Key.NumPad7, 103 }, + { Key.NumPad8, 104 }, + { Key.NumPad9, 105 }, + { Key.Multiply, 106 }, + { Key.Add, 107 }, + { Key.Separator, 108 }, + { Key.Subtract, 109 }, + { Key.Decimal, 110 }, + { Key.Divide, 111 }, + { Key.F1, 112 }, + { Key.F2, 113 }, + { Key.F3, 114 }, + { Key.F4, 115 }, + { Key.F5, 116 }, + { Key.F6, 117 }, + { Key.F7, 118 }, + { Key.F8, 119 }, + { Key.F9, 120 }, + { Key.F10, 121 }, + { Key.F11, 122 }, + { Key.F12, 123 }, + { Key.F13, 124 }, + { Key.F14, 125 }, + { Key.F15, 126 }, + { Key.F16, 127 }, + { Key.F17, 128 }, + { Key.F18, 129 }, + { Key.F19, 130 }, + { Key.F20, 131 }, + { Key.F21, 132 }, + { Key.F22, 133 }, + { Key.F23, 134 }, + { Key.F24, 135 }, + { Key.NumLock, 144 }, + { Key.Scroll, 145 }, + { Key.LeftShift, 160 }, + { Key.RightShift, 161 }, + { Key.LeftCtrl, 162 }, + { Key.RightCtrl, 163 }, + { Key.LeftAlt, 164 }, + { Key.RightAlt, 165 }, + { Key.BrowserBack, 166 }, + { Key.BrowserForward, 167 }, + { Key.BrowserRefresh, 168 }, + { Key.BrowserStop, 169 }, + { Key.BrowserSearch, 170 }, + { Key.BrowserFavorites, 171 }, + { Key.BrowserHome, 172 }, + { Key.VolumeMute, 173 }, + { Key.VolumeDown, 174 }, + { Key.VolumeUp, 175 }, + { Key.MediaNextTrack, 176 }, + { Key.MediaPreviousTrack, 177 }, + { Key.MediaStop, 178 }, + { Key.MediaPlayPause, 179 }, + { Key.LaunchMail, 180 }, + { Key.SelectMedia, 181 }, + { Key.LaunchApplication1, 182 }, + { Key.LaunchApplication2, 183 }, + { Key.Oem1, 186 }, + { Key.OemPlus, 187 }, + { Key.OemComma, 188 }, + { Key.OemMinus, 189 }, + { Key.OemPeriod, 190 }, + { Key.OemQuestion, 191 }, + { Key.Oem3, 192 }, + { Key.AbntC1, 193 }, + { Key.AbntC2, 194 }, + { Key.OemOpenBrackets, 219 }, + { Key.Oem5, 220 }, + { Key.Oem6, 221 }, + { Key.OemQuotes, 222 }, + { Key.Oem8, 223 }, + { Key.OemBackslash, 226 }, + { Key.ImeProcessed, 229 }, + { Key.System, 0 }, + { Key.OemAttn, 240 }, + { Key.OemFinish, 241 }, + { Key.OemCopy, 242 }, + { Key.DbeSbcsChar, 243 }, + { Key.OemEnlw, 244 }, + { Key.OemBackTab, 245 }, + { Key.DbeNoRoman, 246 }, + { Key.DbeEnterWordRegisterMode, 247 }, + { Key.DbeEnterImeConfigureMode, 248 }, + { Key.EraseEof, 249 }, + { Key.Play, 250 }, + { Key.DbeNoCodeInput, 251 }, + { Key.NoName, 252 }, + { Key.Pa1, 253 }, + { Key.OemClear, 254 }, + { Key.DeadCharProcessed, 0 }, + }; + + private static Dictionary keyFromVirtualKey = new Dictionary + { + { 0, Key.None }, + { 3, Key.Cancel }, + { 8, Key.Back }, + { 9, Key.Tab }, + { 12, Key.Clear }, + { 13, Key.Return }, + { 19, Key.Pause }, + { 20, Key.Capital }, + { 21, Key.KanaMode }, + { 23, Key.JunjaMode }, + { 24, Key.FinalMode }, + { 25, Key.HanjaMode }, + { 27, Key.Escape }, + { 28, Key.ImeConvert }, + { 29, Key.ImeNonConvert }, + { 30, Key.ImeAccept }, + { 31, Key.ImeModeChange }, + { 32, Key.Space }, + { 33, Key.PageUp }, + { 34, Key.Next }, + { 35, Key.End }, + { 36, Key.Home }, + { 37, Key.Left }, + { 38, Key.Up }, + { 39, Key.Right }, + { 40, Key.Down }, + { 41, Key.Select }, + { 42, Key.Print }, + { 43, Key.Execute }, + { 44, Key.Snapshot }, + { 45, Key.Insert }, + { 46, Key.Delete }, + { 47, Key.Help }, + { 48, Key.D0 }, + { 49, Key.D1 }, + { 50, Key.D2 }, + { 51, Key.D3 }, + { 52, Key.D4 }, + { 53, Key.D5 }, + { 54, Key.D6 }, + { 55, Key.D7 }, + { 56, Key.D8 }, + { 57, Key.D9 }, + { 65, Key.A }, + { 66, Key.B }, + { 67, Key.C }, + { 68, Key.D }, + { 69, Key.E }, + { 70, Key.F }, + { 71, Key.G }, + { 72, Key.H }, + { 73, Key.I }, + { 74, Key.J }, + { 75, Key.K }, + { 76, Key.L }, + { 77, Key.M }, + { 78, Key.N }, + { 79, Key.O }, + { 80, Key.P }, + { 81, Key.Q }, + { 82, Key.R }, + { 83, Key.S }, + { 84, Key.T }, + { 85, Key.U }, + { 86, Key.V }, + { 87, Key.W }, + { 88, Key.X }, + { 89, Key.Y }, + { 90, Key.Z }, + { 91, Key.LWin }, + { 92, Key.RWin }, + { 93, Key.Apps }, + { 95, Key.Sleep }, + { 96, Key.NumPad0 }, + { 97, Key.NumPad1 }, + { 98, Key.NumPad2 }, + { 99, Key.NumPad3 }, + { 100, Key.NumPad4 }, + { 101, Key.NumPad5 }, + { 102, Key.NumPad6 }, + { 103, Key.NumPad7 }, + { 104, Key.NumPad8 }, + { 105, Key.NumPad9 }, + { 106, Key.Multiply }, + { 107, Key.Add }, + { 108, Key.Separator }, + { 109, Key.Subtract }, + { 110, Key.Decimal }, + { 111, Key.Divide }, + { 112, Key.F1 }, + { 113, Key.F2 }, + { 114, Key.F3 }, + { 115, Key.F4 }, + { 116, Key.F5 }, + { 117, Key.F6 }, + { 118, Key.F7 }, + { 119, Key.F8 }, + { 120, Key.F9 }, + { 121, Key.F10 }, + { 122, Key.F11 }, + { 123, Key.F12 }, + { 124, Key.F13 }, + { 125, Key.F14 }, + { 126, Key.F15 }, + { 127, Key.F16 }, + { 128, Key.F17 }, + { 129, Key.F18 }, + { 130, Key.F19 }, + { 131, Key.F20 }, + { 132, Key.F21 }, + { 133, Key.F22 }, + { 134, Key.F23 }, + { 135, Key.F24 }, + { 144, Key.NumLock }, + { 145, Key.Scroll }, + { 160, Key.LeftShift }, + { 161, Key.RightShift }, + { 162, Key.LeftCtrl }, + { 163, Key.RightCtrl }, + { 164, Key.LeftAlt }, + { 165, Key.RightAlt }, + { 166, Key.BrowserBack }, + { 167, Key.BrowserForward }, + { 168, Key.BrowserRefresh }, + { 169, Key.BrowserStop }, + { 170, Key.BrowserSearch }, + { 171, Key.BrowserFavorites }, + { 172, Key.BrowserHome }, + { 173, Key.VolumeMute }, + { 174, Key.VolumeDown }, + { 175, Key.VolumeUp }, + { 176, Key.MediaNextTrack }, + { 177, Key.MediaPreviousTrack }, + { 178, Key.MediaStop }, + { 179, Key.MediaPlayPause }, + { 180, Key.LaunchMail }, + { 181, Key.SelectMedia }, + { 182, Key.LaunchApplication1 }, + { 183, Key.LaunchApplication2 }, + { 186, Key.Oem1 }, + { 187, Key.OemPlus }, + { 188, Key.OemComma }, + { 189, Key.OemMinus }, + { 190, Key.OemPeriod }, + { 191, Key.OemQuestion }, + { 192, Key.Oem3 }, + { 193, Key.AbntC1 }, + { 194, Key.AbntC2 }, + { 219, Key.OemOpenBrackets }, + { 220, Key.Oem5 }, + { 221, Key.Oem6 }, + { 222, Key.OemQuotes }, + { 223, Key.Oem8 }, + { 226, Key.OemBackslash }, + { 229, Key.ImeProcessed }, + { 240, Key.OemAttn }, + { 241, Key.OemFinish }, + { 242, Key.OemCopy }, + { 243, Key.DbeSbcsChar }, + { 244, Key.OemEnlw }, + { 245, Key.OemBackTab }, + { 246, Key.DbeNoRoman }, + { 247, Key.DbeEnterWordRegisterMode }, + { 248, Key.DbeEnterImeConfigureMode }, + { 249, Key.EraseEof }, + { 250, Key.Play }, + { 251, Key.DbeNoCodeInput }, + { 252, Key.NoName }, + { 253, Key.Pa1 }, + { 254, Key.OemClear }, + }; + + public static Key KeyFromVirtualKey(int virtualKey) + { + Key result; + keyFromVirtualKey.TryGetValue(virtualKey, out result); + return result; + } + + public static int VirtualKeyFromKey(Key key) + { + int result; + virtualKeyFromKey.TryGetValue(key, out result); + return result; + } + } +} diff --git a/Perspex.Windows/Input/WindowsKeyboardDevice.cs b/Perspex.Windows/Input/WindowsKeyboardDevice.cs new file mode 100644 index 0000000000..f805fc312f --- /dev/null +++ b/Perspex.Windows/Input/WindowsKeyboardDevice.cs @@ -0,0 +1,96 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Windows.Input +{ + using System.Text; + using Perspex.Input; + using Perspex.Windows.Interop; + + public class WindowsKeyboardDevice : KeyboardDevice + { + private static WindowsKeyboardDevice instance = new WindowsKeyboardDevice(); + + private byte[] keyStates = new byte[256]; + + public static WindowsKeyboardDevice Instance + { + get { return instance; } + } + + public override ModifierKeys Modifiers + { + get + { + ModifierKeys result = 0; + + if (this.GetKeyStates(Key.LeftAlt) == KeyStates.Down || + this.GetKeyStates(Key.RightAlt) == KeyStates.Down) + { + result |= ModifierKeys.Alt; + } + + if (this.GetKeyStates(Key.LeftCtrl) == KeyStates.Down || + this.GetKeyStates(Key.RightCtrl) == KeyStates.Down) + { + result |= ModifierKeys.Control; + } + + if (this.GetKeyStates(Key.LeftShift) == KeyStates.Down || + this.GetKeyStates(Key.RightShift) == KeyStates.Down) + { + result |= ModifierKeys.Shift; + } + + if (this.GetKeyStates(Key.LWin) == KeyStates.Down || + this.GetKeyStates(Key.RWin) == KeyStates.Down) + { + result |= ModifierKeys.Windows; + } + + return result; + } + } + + + public string StringFromVirtualKey(uint virtualKey) + { + StringBuilder result = new StringBuilder(256); + int length = UnmanagedMethods.ToUnicode( + virtualKey, + 0, + this.keyStates, + result, + 256, + 0); + return result.ToString(); + } + + internal void UpdateKeyStates() + { + UnmanagedMethods.GetKeyboardState(this.keyStates); + } + + private KeyStates GetKeyStates(Key key) + { + int vk = KeyInterop.VirtualKeyFromKey(key); + byte state = this.keyStates[vk]; + KeyStates result = 0; + + if ((state & 0x80) != 0) + { + result |= KeyStates.Down; + } + + if ((state & 0x01) != 0) + { + result |= KeyStates.Toggled; + } + + return result; + } + } +} diff --git a/Perspex.Windows/Input/WindowsMouseDevice.cs b/Perspex.Windows/Input/WindowsMouseDevice.cs index c14dbc0742..cc675e5975 100644 --- a/Perspex.Windows/Input/WindowsMouseDevice.cs +++ b/Perspex.Windows/Input/WindowsMouseDevice.cs @@ -7,8 +7,6 @@ namespace Perspex.Windows.Input { using Perspex.Input; - using Perspex.Input.Raw; - using Splat; public class WindowsMouseDevice : MouseDevice { @@ -32,3 +30,4 @@ namespace Perspex.Windows.Input } } } + diff --git a/Perspex.Windows/Perspex.Windows.csproj b/Perspex.Windows/Perspex.Windows.csproj index 243df648e7..ced9190ca2 100644 --- a/Perspex.Windows/Perspex.Windows.csproj +++ b/Perspex.Windows/Perspex.Windows.csproj @@ -56,6 +56,8 @@ + + diff --git a/Perspex.Windows/Window.cs b/Perspex.Windows/Window.cs index 089382e2b8..9af8581151 100644 --- a/Perspex.Windows/Window.cs +++ b/Perspex.Windows/Window.cs @@ -39,7 +39,7 @@ namespace Perspex.Windows public Window() { - IPlatformFactory factory = Locator.Current.GetService(); + IPlatformInterface factory = Locator.Current.GetService(); this.CreateWindow(); Size clientSize = this.ClientSize; @@ -165,7 +165,7 @@ namespace Perspex.Windows [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { - RawMouseEventArgs e = null; + RawInputEventArgs e = null; WindowsMouseDevice.Instance.CurrentWindow = this; @@ -175,13 +175,14 @@ namespace Perspex.Windows //// this.OnClosed(); //// break; - ////case UnmanagedMethods.WindowsMessage.WM_KEYDOWN: - //// InputManager.Current.ProcessInput( - //// new RawKeyEventArgs( - //// keyboard, - //// RawKeyEventType.KeyDown, - //// KeyInterop.KeyFromVirtualKey((int)wParam))); - //// break; + case UnmanagedMethods.WindowsMessage.WM_KEYDOWN: + WindowsKeyboardDevice.Instance.UpdateKeyStates(); + e = new RawKeyEventArgs( + WindowsKeyboardDevice.Instance, + RawKeyEventType.KeyDown, + KeyInterop.KeyFromVirtualKey((int)wParam), + WindowsKeyboardDevice.Instance.StringFromVirtualKey((uint)wParam)); + break; case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: e = new RawMouseEventArgs( @@ -215,7 +216,6 @@ namespace Perspex.Windows if (e != null) { - WindowsMouseDevice.Instance.Position = e.Position; this.inputManager.Process(e); } diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs index c2155b17a4..3f7867ee0a 100644 --- a/Perspex/Controls/Control.cs +++ b/Perspex/Controls/Control.cs @@ -92,6 +92,15 @@ namespace Perspex.Controls public static readonly PerspexProperty WidthProperty = PerspexProperty.Register("Width", double.NaN); + public static readonly RoutedEvent GotFocusEvent = + RoutedEvent.Register("GotFocus", RoutingStrategy.Bubble); + + public static readonly RoutedEvent LostFocusEvent = + RoutedEvent.Register("LostFocus", RoutingStrategy.Bubble); + + public static readonly RoutedEvent KeyDownEvent = + RoutedEvent.Register("KeyDown", RoutingStrategy.Bubble); + public static readonly RoutedEvent PointerEnterEvent = RoutedEvent.Register("PointerEnter", RoutingStrategy.Direct); @@ -113,40 +122,30 @@ namespace Perspex.Controls public Control() { this.classes = new Classes(); + this.GotFocus += (s, e) => this.IsFocused = true; + this.LostFocus += (s, e) => this.IsFocused = false; + this.PointerEnter += (s, e) => this.IsPointerOver = true; + this.PointerLeave += (s, e) => this.IsPointerOver = false; + this.AddPseudoClass(IsPointerOverProperty, ":pointerover"); + this.AddPseudoClass(IsFocusedProperty, ":focus"); + } - this.PointerEnter += (s, e) => - { - this.IsPointerOver = true; - }; - - this.PointerLeave += (s, e) => - { - this.IsPointerOver = false; - }; + public event EventHandler GotFocus + { + add { this.AddHandler(GotFocusEvent, value); } + remove { this.RemoveHandler(GotFocusEvent, value); } + } - this.GetObservable(IsPointerOverProperty).Subscribe(x => - { - if (x) - { - this.Classes.Add(":pointerover"); - } - else - { - this.Classes.Remove(":pointerover"); - } - }); + public event EventHandler LostFocus + { + add { this.AddHandler(LostFocusEvent, value); } + remove { this.RemoveHandler(LostFocusEvent, value); } + } - this.GetObservable(IsFocusedProperty).Subscribe(x => - { - if (x) - { - this.Classes.Add(":focus"); - } - else - { - this.Classes.Remove(":focus"); - } - }); + public event EventHandler KeyDown + { + add { this.AddHandler(KeyDownEvent, value); } + remove { this.RemoveHandler(KeyDownEvent, value); } } public event EventHandler PointerEnter @@ -246,7 +245,7 @@ namespace Perspex.Controls public bool IsFocused { get { return this.GetValue(IsFocusedProperty); } - internal set { this.SetValue(IsFocusedProperty, value); } + private set { this.SetValue(IsFocusedProperty, value); } } public string Id @@ -356,12 +355,6 @@ namespace Perspex.Controls set { this.SetValue(WidthProperty, value); } } - bool IFocusable.IsFocused - { - get { return this.GetValue(IsFocusedProperty); } - set { this.SetValue(IsFocusedProperty, value); } - } - ILogical ILogical.LogicalParent { get { return this.Parent; } @@ -414,6 +407,21 @@ namespace Perspex.Controls } } + protected void AddPseudoClass(PerspexProperty property, string className) + { + this.GetObservable(property).Subscribe(x => + { + if (x) + { + this.classes.Add(className); + } + else + { + this.classes.Remove(className); + } + }); + } + protected virtual void ArrangeCore(Rect finalRect) { double originX = finalRect.X + this.Margin.Left; diff --git a/Perspex/Controls/TextBox.cs b/Perspex/Controls/TextBox.cs index ba327f870d..c190435292 100644 --- a/Perspex/Controls/TextBox.cs +++ b/Perspex/Controls/TextBox.cs @@ -8,7 +8,10 @@ namespace Perspex.Controls { using System; using System.Linq; + using Perspex.Input; + using Perspex.Platform; using Perspex.Styling; + using Splat; public class TextBox : TemplatedControl { @@ -26,7 +29,10 @@ namespace Perspex.Controls public TextBox() { - this.GetObservable(TextProperty).Subscribe(_ => this.InvalidateVisual()); + this.GotFocus += (s, e) => this.textBoxView.GotFocus(); + this.LostFocus += (s, e) => this.textBoxView.LostFocus(); + this.KeyDown += this.OnKeyDown; + this.PointerPressed += this.OnPointerPressed; } public int CaretIndex @@ -68,6 +74,59 @@ namespace Perspex.Controls } textContainer.Content = this.textBoxView = new TextBoxView(this); + this.GetObservable(TextProperty).Subscribe(_ => this.textBoxView.InvalidateText()); + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + string text = this.Text; + + switch (e.Key) + { + case Key.Left: + --this.CaretIndex; + break; + + case Key.Right: + ++this.CaretIndex; + break; + + case Key.Back: + if (this.caretIndex > 0) + { + this.Text = text.Substring(0, this.caretIndex - 1) + text.Substring(this.caretIndex); + --this.CaretIndex; + } + + break; + + case Key.Delete: + if (this.caretIndex < text.Length) + { + this.Text = text.Substring(0, this.caretIndex) + text.Substring(this.caretIndex + 1); + } + + break; + + default: + if (!string.IsNullOrEmpty(e.Text)) + { + this.Text = text.Substring(0, this.caretIndex) + e.Text + text.Substring(this.caretIndex); + ++this.CaretIndex; + } + + break; + } + + e.Handled = true; + } + + private void OnPointerPressed(object sender, PointerEventArgs e) + { + //IPlatformInterface platform = Locator.Current.GetService(); + //this.CaretIndex = platform.GetTextService().GetCaretIndex( + // this.textBoxView.FormattedText, + // e.GetPosition(this.textBoxView)); } } } diff --git a/Perspex/Controls/ToggleButton.cs b/Perspex/Controls/ToggleButton.cs index c26a6517e1..288b826a30 100644 --- a/Perspex/Controls/ToggleButton.cs +++ b/Perspex/Controls/ToggleButton.cs @@ -16,18 +16,7 @@ namespace Perspex.Controls public ToggleButton() { this.Click += (s, e) => this.IsChecked = !this.IsChecked; - - this.GetObservable(IsCheckedProperty).Subscribe(x => - { - if (x) - { - this.Classes.Add(":checked"); - } - else - { - this.Classes.Remove(":checked"); - } - }); + this.AddPseudoClass(IsCheckedProperty, ":checked"); } public bool IsChecked diff --git a/Perspex/Input/FocusManager.cs b/Perspex/Input/FocusManager.cs index 58fc90ce65..83139f9e06 100644 --- a/Perspex/Input/FocusManager.cs +++ b/Perspex/Input/FocusManager.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +using Perspex.Controls; + namespace Perspex.Input { public class FocusManager : IFocusManager @@ -16,15 +18,29 @@ namespace Perspex.Input public void Focus(IFocusable control) { - if (this.Current != null) + Interactive current = this.Current as Interactive; + Interactive next = control as Interactive; + + if (current != null) { - this.Current.IsFocused = false; + current.RaiseEvent(new RoutedEventArgs + { + RoutedEvent = Control.GotFocusEvent, + Source = current, + OriginalSource = current, + }); } - if (control != null) + this.Current = control; + + if (next != null) { - control.IsFocused = true; - this.Current = control; + next.RaiseEvent(new RoutedEventArgs + { + RoutedEvent = Control.GotFocusEvent, + Source = next, + OriginalSource = next, + }); } } } diff --git a/Perspex/Input/IFocusable.cs b/Perspex/Input/IFocusable.cs index f8c81d8aef..03ec8d3561 100644 --- a/Perspex/Input/IFocusable.cs +++ b/Perspex/Input/IFocusable.cs @@ -10,7 +10,7 @@ namespace Perspex.Input { bool Focusable { get; } - bool IsFocused { get; set; } + bool IsFocused { get; } void Focus(); } diff --git a/Perspex/Input/IKeyboardDevice.cs b/Perspex/Input/IKeyboardDevice.cs new file mode 100644 index 0000000000..53e708a26e --- /dev/null +++ b/Perspex/Input/IKeyboardDevice.cs @@ -0,0 +1,33 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + + [Flags] + public enum ModifierKeys + { + None = 0, + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + } + + [Flags] + public enum KeyStates + { + None = 0, + Down = 1, + Toggled = 2, + } + + public interface IKeyboardDevice : IInputDevice + { + ModifierKeys Modifiers { get; } + } +} diff --git a/Perspex/Input/Key.cs b/Perspex/Input/Key.cs new file mode 100644 index 0000000000..b966f9223f --- /dev/null +++ b/Perspex/Input/Key.cs @@ -0,0 +1,214 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + public enum Key + { + None = 0, + Cancel = 1, + Back = 2, + Tab = 3, + LineFeed = 4, + Clear = 5, + Return = 6, + Enter = 6, + Pause = 7, + CapsLock = 8, + Capital = 8, + HangulMode = 9, + KanaMode = 9, + JunjaMode = 10, + FinalMode = 11, + KanjiMode = 12, + HanjaMode = 12, + Escape = 13, + ImeConvert = 14, + ImeNonConvert = 15, + ImeAccept = 16, + ImeModeChange = 17, + Space = 18, + PageUp = 19, + Prior = 19, + PageDown = 20, + Next = 20, + End = 21, + Home = 22, + Left = 23, + Up = 24, + Right = 25, + Down = 26, + Select = 27, + Print = 28, + Execute = 29, + Snapshot = 30, + PrintScreen = 30, + Insert = 31, + Delete = 32, + Help = 33, + D0 = 34, + D1 = 35, + D2 = 36, + D3 = 37, + D4 = 38, + D5 = 39, + D6 = 40, + D7 = 41, + D8 = 42, + D9 = 43, + A = 44, + B = 45, + C = 46, + D = 47, + E = 48, + F = 49, + G = 50, + H = 51, + I = 52, + J = 53, + K = 54, + L = 55, + M = 56, + N = 57, + O = 58, + P = 59, + Q = 60, + R = 61, + S = 62, + T = 63, + U = 64, + V = 65, + W = 66, + X = 67, + Y = 68, + Z = 69, + LWin = 70, + RWin = 71, + Apps = 72, + Sleep = 73, + NumPad0 = 74, + NumPad1 = 75, + NumPad2 = 76, + NumPad3 = 77, + NumPad4 = 78, + NumPad5 = 79, + NumPad6 = 80, + NumPad7 = 81, + NumPad8 = 82, + NumPad9 = 83, + Multiply = 84, + Add = 85, + Separator = 86, + Subtract = 87, + Decimal = 88, + Divide = 89, + F1 = 90, + F2 = 91, + F3 = 92, + F4 = 93, + F5 = 94, + F6 = 95, + F7 = 96, + F8 = 97, + F9 = 98, + F10 = 99, + F11 = 100, + F12 = 101, + F13 = 102, + F14 = 103, + F15 = 104, + F16 = 105, + F17 = 106, + F18 = 107, + F19 = 108, + F20 = 109, + F21 = 110, + F22 = 111, + F23 = 112, + F24 = 113, + NumLock = 114, + Scroll = 115, + LeftShift = 116, + RightShift = 117, + LeftCtrl = 118, + RightCtrl = 119, + LeftAlt = 120, + RightAlt = 121, + BrowserBack = 122, + BrowserForward = 123, + BrowserRefresh = 124, + BrowserStop = 125, + BrowserSearch = 126, + BrowserFavorites = 127, + BrowserHome = 128, + VolumeMute = 129, + VolumeDown = 130, + VolumeUp = 131, + MediaNextTrack = 132, + MediaPreviousTrack = 133, + MediaStop = 134, + MediaPlayPause = 135, + LaunchMail = 136, + SelectMedia = 137, + LaunchApplication1 = 138, + LaunchApplication2 = 139, + OemSemicolon = 140, + Oem1 = 140, + OemPlus = 141, + OemComma = 142, + OemMinus = 143, + OemPeriod = 144, + OemQuestion = 145, + Oem2 = 145, + OemTilde = 146, + Oem3 = 146, + AbntC1 = 147, + AbntC2 = 148, + OemOpenBrackets = 149, + Oem4 = 149, + OemPipe = 150, + Oem5 = 150, + OemCloseBrackets = 151, + Oem6 = 151, + OemQuotes = 152, + Oem7 = 152, + Oem8 = 153, + OemBackslash = 154, + Oem102 = 154, + ImeProcessed = 155, + System = 156, + OemAttn = 157, + DbeAlphanumeric = 157, + OemFinish = 158, + DbeKatakana = 158, + DbeHiragana = 159, + OemCopy = 159, + DbeSbcsChar = 160, + OemAuto = 160, + DbeDbcsChar = 161, + OemEnlw = 161, + OemBackTab = 162, + DbeRoman = 162, + DbeNoRoman = 163, + Attn = 163, + CrSel = 164, + DbeEnterWordRegisterMode = 164, + ExSel = 165, + DbeEnterImeConfigureMode = 165, + EraseEof = 166, + DbeFlushString = 166, + Play = 167, + DbeCodeInput = 167, + DbeNoCodeInput = 168, + Zoom = 168, + NoName = 169, + DbeDetermineString = 169, + DbeEnterDialogConversionMode = 170, + Pa1 = 170, + OemClear = 171, + DeadCharProcessed = 172, + } +} diff --git a/Perspex/Input/FocusEventArgs.cs b/Perspex/Input/KeyEventArgs.cs similarity index 54% rename from Perspex/Input/FocusEventArgs.cs rename to Perspex/Input/KeyEventArgs.cs index ac99821e31..a92206c2d5 100644 --- a/Perspex/Input/FocusEventArgs.cs +++ b/Perspex/Input/KeyEventArgs.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Copyright 2013 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- @@ -8,7 +8,12 @@ namespace Perspex.Input { using System; - public class FocusEventArgs : RoutedEventArgs + public class KeyEventArgs : RoutedEventArgs { + public IKeyboardDevice Device { get; set; } + + public Key Key { get; set; } + + public string Text { get; set; } } } diff --git a/Perspex/Input/KeyboardDevice.cs b/Perspex/Input/KeyboardDevice.cs new file mode 100644 index 0000000000..1bb0e11b14 --- /dev/null +++ b/Perspex/Input/KeyboardDevice.cs @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using System; + using System.Linq; + using System.Reactive.Linq; + using Perspex.Controls; + using Perspex.Input.Raw; + using Splat; + + public abstract class KeyboardDevice : IKeyboardDevice + { + public KeyboardDevice() + { + this.InputManager.RawEventReceived + .OfType() + .Where(x => x.Device == this) + .Subscribe(this.ProcessRawEvent); + } + + public IInputManager InputManager + { + get { return Locator.Current.GetService(); } + } + + public IFocusManager FocusManager + { + get { return Locator.Current.GetService(); } + } + + public abstract ModifierKeys Modifiers { get; } + + private void ProcessRawEvent(RawKeyEventArgs e) + { + Interactive interactive = FocusManager.Current as Interactive; + + if (interactive != null) + { + switch (e.Type) + { + case RawKeyEventType.KeyDown: + interactive.RaiseEvent(new KeyEventArgs + { + RoutedEvent = Control.KeyDownEvent, + Device = this, + Key = e.Key, + Text = e.Text, + Source = interactive, + OriginalSource = interactive, + }); + break; + } + } + } + } +} diff --git a/Perspex/Input/MouseDevice.cs b/Perspex/Input/MouseDevice.cs index 3763850627..9eaf573b82 100644 --- a/Perspex/Input/MouseDevice.cs +++ b/Perspex/Input/MouseDevice.cs @@ -20,7 +20,7 @@ namespace Perspex.Input this.InputManager.RawEventReceived .OfType() .Where(x => x.Device == this) - .Subscribe(this.ProcessMouse); + .Subscribe(this.ProcessRawEvent); } public Interactive Captured @@ -45,8 +45,10 @@ namespace Perspex.Input this.Captured = visual; } - private void ProcessMouse(RawMouseEventArgs e) + private void ProcessRawEvent(RawMouseEventArgs e) { + this.Position = e.Position; + switch (e.Type) { case RawMouseEventType.Move: diff --git a/Perspex/Input/Raw/RawKeyEventArgs.cs b/Perspex/Input/Raw/RawKeyEventArgs.cs new file mode 100644 index 0000000000..887030b375 --- /dev/null +++ b/Perspex/Input/Raw/RawKeyEventArgs.cs @@ -0,0 +1,31 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 Tricycle. All rights reserved. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input.Raw +{ + public enum RawKeyEventType + { + KeyDown, + KeyUp + } + + public class RawKeyEventArgs : RawInputEventArgs + { + public RawKeyEventArgs(KeyboardDevice device, RawKeyEventType type, Key key, string text) + : base(device) + { + this.Key = key; + this.Type = type; + this.Text = text; + } + + public Key Key { get; set; } + + public string Text { get; set; } + + public RawKeyEventType Type { get; set; } + } +} diff --git a/Perspex/Media/FormattedText.cs b/Perspex/Media/FormattedText.cs index 63bb0f5ead..12a1cd1b1d 100644 --- a/Perspex/Media/FormattedText.cs +++ b/Perspex/Media/FormattedText.cs @@ -21,7 +21,7 @@ namespace Perspex.Media { get { - IPlatformFactory factory = Locator.Current.GetService(); + IPlatformInterface factory = Locator.Current.GetService(); ITextService service = factory.GetTextService(); return service.Measure(this); } diff --git a/Perspex/Media/Imaging/Bitmap.cs b/Perspex/Media/Imaging/Bitmap.cs index 5db65f7a0b..5d83a8c238 100644 --- a/Perspex/Media/Imaging/Bitmap.cs +++ b/Perspex/Media/Imaging/Bitmap.cs @@ -13,7 +13,7 @@ namespace Perspex.Media { public Bitmap(int width, int height) { - IPlatformFactory factory = Locator.Current.GetService(); + IPlatformInterface factory = Locator.Current.GetService(); this.PlatformImpl = factory.CreateBitmap(width, height); } diff --git a/Perspex/Media/Imaging/RenderTargetBitmap.cs b/Perspex/Media/Imaging/RenderTargetBitmap.cs index fe1ccfb26d..08d2899731 100644 --- a/Perspex/Media/Imaging/RenderTargetBitmap.cs +++ b/Perspex/Media/Imaging/RenderTargetBitmap.cs @@ -28,7 +28,7 @@ namespace Perspex.Media private static IBitmapImpl CreateImpl(int width, int height) { - IPlatformFactory factory = Locator.Current.GetService(); + IPlatformInterface factory = Locator.Current.GetService(); return factory.CreateRenderTargetBitmap(width, height); } } diff --git a/Perspex/Media/RectangleGeometry.cs b/Perspex/Media/RectangleGeometry.cs index 9ebc3d39d4..0dbcaa9a68 100644 --- a/Perspex/Media/RectangleGeometry.cs +++ b/Perspex/Media/RectangleGeometry.cs @@ -13,7 +13,7 @@ namespace Perspex.Media { public RectangleGeometry(Rect rect) { - IPlatformFactory factory = Locator.Current.GetService(); + IPlatformInterface factory = Locator.Current.GetService(); IStreamGeometryImpl impl = factory.CreateStreamGeometry(); using (IStreamGeometryContextImpl context = impl.Open()) diff --git a/Perspex/Media/StreamGeometry.cs b/Perspex/Media/StreamGeometry.cs index b249fdbd8f..5df8c1de4b 100644 --- a/Perspex/Media/StreamGeometry.cs +++ b/Perspex/Media/StreamGeometry.cs @@ -13,7 +13,7 @@ namespace Perspex.Media { public StreamGeometry() { - IPlatformFactory factory = Locator.Current.GetService(); + IPlatformInterface factory = Locator.Current.GetService(); this.PlatformImpl = factory.CreateStreamGeometry(); } diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj index 0fdefe0219..1192c8be14 100644 --- a/Perspex/Perspex.csproj +++ b/Perspex/Perspex.csproj @@ -87,14 +87,18 @@ + + + - + + @@ -102,7 +106,7 @@ - + diff --git a/Perspex/Platform/IPlatformFactory.cs b/Perspex/Platform/IPlatformInterface.cs similarity index 94% rename from Perspex/Platform/IPlatformFactory.cs rename to Perspex/Platform/IPlatformInterface.cs index 28c934e415..5138df4846 100644 --- a/Perspex/Platform/IPlatformFactory.cs +++ b/Perspex/Platform/IPlatformInterface.cs @@ -8,7 +8,7 @@ namespace Perspex.Platform { using System; - public interface IPlatformFactory + public interface IPlatformInterface { IBitmapImpl CreateBitmap(int width, int height);