From 4e40ed0397296c27bbcb81adf313a6d38f8a3d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Tue, 12 Mar 2024 13:44:45 +0800 Subject: [PATCH 1/7] Add IME Support for DX and XNA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provide by ImeSharp Signed-off-by: 舰队的偶像-岛风酱! --- Input/IME/IMEHandler.cs | 75 +++++++++++++++ Input/IME/Sdl/SdlIMEHandler.cs | 20 ++++ Input/IME/WinForms/WinFormsIMEHandler.cs | 44 +++++++++ Rampastring.XNAUI.csproj | 11 +++ WindowManager.cs | 7 +- XNAControls/XNATextBox.cs | 112 ++++++++++++++++++++++- 6 files changed, 263 insertions(+), 6 deletions(-) create mode 100644 Input/IME/IMEHandler.cs create mode 100644 Input/IME/Sdl/SdlIMEHandler.cs create mode 100644 Input/IME/WinForms/WinFormsIMEHandler.cs diff --git a/Input/IME/IMEHandler.cs b/Input/IME/IMEHandler.cs new file mode 100644 index 0000000..4117bad --- /dev/null +++ b/Input/IME/IMEHandler.cs @@ -0,0 +1,75 @@ +// MonoGame.IMEHelper.Common +// Copyright (c) 2020 ryancheung + +using System; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace Rampastring.XNAUI.Input.IME +{ + internal abstract class IMEHandler + { + public static IMEHandler Create(Game game) + { +#if !GL + return new WinForms.WinFormsIMEHandler(game); +#else + return new Sdl.SdlIMEHandler(game); +#endif + } + + /// + /// Check if text composition is enabled currently. + /// + public abstract bool Enabled { get; protected set; } + + /// + /// Enable the system IMM service to support composited character input. + /// This should be called when you expect text input from a user and you support languages + /// that require an IME (Input Method Editor). + /// + /// + public abstract void StartTextComposition(); + + /// + /// Stop the system IMM service. + /// + /// + public abstract void StopTextComposition(); + + /// + /// Use this function to set the rectangle used to type Unicode text inputs if IME supported. + /// In SDL2, this method call gives the OS a hint for where to show the candidate text list, + /// since the OS doesn't know where you want to draw the text you received via SDL_TEXTEDITING event. + /// + public virtual void SetTextInputRect(in Rectangle rect) + { } + + /// + /// Composition String + /// + public virtual string Composition { get; protected set; } = string.Empty; + + /// + /// Caret position of the composition + /// + public virtual int CompositionCursorPos { get; protected set; } + + /// + /// Invoked when the IMM service emit character input event. + /// + /// + /// + public event EventHandler TextInput; + + /// + /// Trigger a text input event. + /// + /// + protected virtual void OnTextInput(char character) + { + TextInput?.Invoke(this, character); + } + } +} \ No newline at end of file diff --git a/Input/IME/Sdl/SdlIMEHandler.cs b/Input/IME/Sdl/SdlIMEHandler.cs new file mode 100644 index 0000000..3b3f8e7 --- /dev/null +++ b/Input/IME/Sdl/SdlIMEHandler.cs @@ -0,0 +1,20 @@ +using Microsoft.Xna.Framework; + +namespace Rampastring.XNAUI.Input.IME.Sdl +{ + /// + /// Integrate IME to DesktopGL(SDL2) platform. + /// + internal sealed class SdlIMEHandler(Game game) : IMEHandler + { + public override bool Enabled { get => false; protected set => _ = value; } + + public override void StartTextComposition() + { + } + + public override void StopTextComposition() + { + } + } +} \ No newline at end of file diff --git a/Input/IME/WinForms/WinFormsIMEHandler.cs b/Input/IME/WinForms/WinFormsIMEHandler.cs new file mode 100644 index 0000000..8ccd4f9 --- /dev/null +++ b/Input/IME/WinForms/WinFormsIMEHandler.cs @@ -0,0 +1,44 @@ +using ImeSharp; + +using Microsoft.Xna.Framework; + +namespace Rampastring.XNAUI.Input.IME.WinForms +{ + /// + /// Integrate IME to XNA framework. + /// + internal class WinFormsIMEHandler : IMEHandler + { + public WinFormsIMEHandler(Game game) + { + InputMethod.Initialize(game.Window.Handle); + InputMethod.TextInputCallback = OnTextInput; + InputMethod.TextCompositionCallback = (compositionText, cursorPosition, _, _, _, _) => + { + Composition = compositionText.ToString(); + CompositionCursorPos = cursorPosition; + }; + } + + public override bool Enabled + { + get => InputMethod.Enabled; + protected set => InputMethod.Enabled = value; + } + + public override void StartTextComposition() + { + Enabled = true; + } + + public override void StopTextComposition() + { + Enabled = false; + } + + public override void SetTextInputRect(in Rectangle rect) + { + InputMethod.SetTextInputRect(rect.X, rect.Y, rect.Width, rect.Height); + } + } +} \ No newline at end of file diff --git a/Rampastring.XNAUI.csproj b/Rampastring.XNAUI.csproj index b4a94f6..0432d9e 100644 --- a/Rampastring.XNAUI.csproj +++ b/Rampastring.XNAUI.csproj @@ -121,6 +121,17 @@ + + + + + + + + + + + diff --git a/WindowManager.cs b/WindowManager.cs index 91dfdbe..fc11184 100644 --- a/WindowManager.cs +++ b/WindowManager.cs @@ -11,6 +11,7 @@ using System.Diagnostics; using System.Linq; using Rampastring.XNAUI.PlatformSpecific; +using Rampastring.XNAUI.Input.IME; #if NETFRAMEWORK using System.Reflection; #endif @@ -27,6 +28,7 @@ namespace Rampastring.XNAUI; public class WindowManager : DrawableGameComponent { private const int XNA_MAX_TEXTURE_SIZE = 2048; + internal IMEHandler IMEHandler { get; } /// /// Creates a new WindowManager. @@ -35,6 +37,7 @@ public class WindowManager : DrawableGameComponent /// The game's GraphicsDeviceManager. public WindowManager(Game game, GraphicsDeviceManager graphics) : base(game) { + IMEHandler = IMEHandler.Create(game); this.graphics = graphics; } @@ -768,8 +771,8 @@ public override void Draw(GameTime gameTime) Game.Window.ClientBounds.Width - (SceneXPosition * 2), Game.Window.ClientBounds.Height - (SceneYPosition * 2)), Color.White); #if DEBUG - Renderer.DrawString("Active control " + activeControlName, 0, Vector2.Zero, Color.Red, 1.0f); - + Renderer.DrawString("Active control: " + activeControlName, 0, Vector2.Zero, Color.Red, 1.0f); + Renderer.DrawString("IME Status: " + (IMEHandler.Enabled ? "Enabled" : "Disabled"), 0, new Vector2(0, 16), Color.Red, 1.0f); #endif if (Cursor.Visible) Cursor.Draw(gameTime); diff --git a/XNAControls/XNATextBox.cs b/XNAControls/XNATextBox.cs index 985618e..449dabb 100644 --- a/XNAControls/XNATextBox.cs +++ b/XNAControls/XNATextBox.cs @@ -1,4 +1,4 @@ -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Rampastring.Tools; using Rampastring.XNAUI.Input; @@ -20,6 +20,8 @@ public class XNATextBox : XNAControl protected const double FAST_SCROLL_TRIGGER_TIME = 0.4; protected const double BAR_ON_TIME = 0.5; protected const double BAR_OFF_TIME = 0.5; + private static volatile XNATextBox _IMEFocus; + private volatile bool _textCompositionDelay; /// /// Creates a new text box. @@ -107,6 +109,11 @@ public Color BackColor /// public int FontIndex { get; set; } + /// + /// Disable IME + /// + public bool DisableIME { get; set; } + /// /// The maximum length of the text of this text box, in characters. /// @@ -215,6 +222,8 @@ public override void Initialize() KeyboardEventInput.CharEntered += KeyboardEventInput_CharEntered; #endif Keyboard.OnKeyPressed += Keyboard_OnKeyPressed; + + InitializeIMEHelper(); } public override void Kill() @@ -232,12 +241,14 @@ public override void Kill() #if XNA private void KeyboardEventInput_CharEntered(object sender, KeyboardEventArgs e) { - HandleCharInput(e.Character); + if (DisableIME || !WindowManager.IMEHandler.Enabled) + HandleCharInput(e.Character); } #else private void Window_TextInput(object sender, TextInputEventArgs e) { - HandleCharInput(e.Character); + if (DisableIME || !WindowManager.IMEHandler.Enabled) + HandleCharInput(e.Character); } #endif @@ -472,6 +483,15 @@ public override void Update(GameTime gameTime) if (WindowManager.SelectedControl == this) { + if (!DisableIME + && Enabled && Visible && IsActive + && !WindowManager.IMEHandler.Enabled) + { + _IMEFocus = this; + WindowManager.IMEHandler.StartTextComposition(); + WindowManager.IMEHandler.SetTextInputRect(RenderRectangle()); + } + if (Keyboard.IsKeyHeldDown(Keys.Left)) { HandleScrollKeyDown(gameTime, ScrollLeft); @@ -495,10 +515,20 @@ public override void Update(GameTime gameTime) scrollKeyTime = TimeSpan.Zero; } } + else if ( + _IMEFocus == this && !DisableIME + && Enabled && Visible && !IsActive + && WindowManager.IMEHandler.Enabled) + { + WindowManager.IMEHandler.StopTextComposition(); + } } private void ScrollLeft() { + if (!DisableIME && !string.IsNullOrEmpty(WindowManager.IMEHandler.Composition)) + return; + if (InputPosition == 0) return; @@ -514,6 +544,9 @@ private void ScrollLeft() private void ScrollRight() { + if (!DisableIME && !string.IsNullOrEmpty(WindowManager.IMEHandler.Composition)) + return; + if (InputPosition >= text.Length) return; @@ -532,6 +565,21 @@ private void ScrollRight() private void DeleteCharacter() { + if (!DisableIME) + { + if (!string.IsNullOrEmpty(WindowManager.IMEHandler.Composition)) + { + _textCompositionDelay = false; + return; + } + + if (!_textCompositionDelay) + { + _textCompositionDelay = true; + return; + } + } + if (text.Length > InputPosition) { text = text.Remove(InputPosition, 1); @@ -550,6 +598,22 @@ private void DeleteCharacter() private void Backspace() { + if (!DisableIME) + { + if (!string.IsNullOrEmpty(WindowManager.IMEHandler.Composition)) + { + _textCompositionDelay = false; + return; + } + + if (!_textCompositionDelay) + { + _textCompositionDelay = true; + return; + } + } + + if (text.Length > 0 && InputPosition > 0) { text = text.Remove(InputPosition - 1, 1); @@ -607,7 +671,10 @@ public override void Draw(GameTime gameTime) FontIndex, new Vector2(TEXT_HORIZONTAL_MARGIN, TEXT_VERTICAL_MARGIN), TextColor); - if (WindowManager.SelectedControl == this && Enabled && WindowManager.HasFocus && barTimer.TotalSeconds < BAR_ON_TIME) + if ((DisableIME || _IMEFocus != this || string.IsNullOrEmpty(WindowManager.IMEHandler.Composition)) + && WindowManager.SelectedControl == this + && Enabled && WindowManager.HasFocus + && barTimer.TotalSeconds < BAR_ON_TIME) { int barLocationX = TEXT_HORIZONTAL_MARGIN; @@ -617,6 +684,43 @@ public override void Draw(GameTime gameTime) FillRectangle(new Rectangle(barLocationX, 2, 1, Height - 4), Color.White); } + if (!DisableIME && _IMEFocus == this && WindowManager.IMEHandler.Enabled) + { + var drawPos = + new Vector2( + Renderer.GetTextDimensions(Text.Substring(TextStartPosition, InputPosition - TextStartPosition), + FontIndex).X + 6, 2); + + for (var i = 0; i < WindowManager.IMEHandler.Composition.Length; i++) + { + string val = WindowManager.IMEHandler.Composition[i].ToString(); + DrawString(val, FontIndex, drawPos, Color.Orange); + Vector2 measStr = Renderer.GetTextDimensions(val, FontIndex); + drawPos += new Vector2(measStr.X, 0); + if (WindowManager.SelectedControl == this && Enabled && WindowManager.HasFocus && + barTimer.TotalSeconds < 0.5 && + i + 1 == WindowManager.IMEHandler.CompositionCursorPos) + { + DrawRectangle(new Rectangle((int)drawPos.X, (int)drawPos.Y, 1, (int)measStr.Y), + Color.White); + } + } + } base.Draw(gameTime); } + + + private void InitializeIMEHelper() + { + if (DisableIME) + return; + + WindowManager.IMEHandler.TextInput += IMEHandler_TextInput; + } + + private void IMEHandler_TextInput(object sender, char e) + { + if (WindowManager.IMEHandler.Enabled) + HandleCharInput(e); + } } \ No newline at end of file From a7898cb46b1a81f017b204a719fd3ac5d4fdca69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Tue, 12 Mar 2024 18:15:32 +0800 Subject: [PATCH 2/7] Supported Tab to switch controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- XNAControls/XNATextBox.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/XNAControls/XNATextBox.cs b/XNAControls/XNATextBox.cs index 449dabb..8a053fc 100644 --- a/XNAControls/XNATextBox.cs +++ b/XNAControls/XNATextBox.cs @@ -20,7 +20,7 @@ public class XNATextBox : XNAControl protected const double FAST_SCROLL_TRIGGER_TIME = 0.4; protected const double BAR_ON_TIME = 0.5; protected const double BAR_OFF_TIME = 0.5; - private static volatile XNATextBox _IMEFocus; + private static volatile XNAControl _IMEFocus; private volatile bool _textCompositionDelay; /// @@ -424,11 +424,15 @@ protected virtual bool HandleKeyPress(Keys key) if (Keyboard.IsShiftHeldDown()) { if (PreviousControl != null) - WindowManager.SelectedControl = PreviousControl; + { + _IMEFocus = WindowManager.SelectedControl = PreviousControl; + WindowManager.IMEHandler.SetTextInputRect(_IMEFocus.RenderRectangle()); + } } else if (NextControl != null) { - WindowManager.SelectedControl = NextControl; + _IMEFocus = WindowManager.SelectedControl = NextControl; + WindowManager.IMEHandler.SetTextInputRect(_IMEFocus.RenderRectangle()); } return true; From 383af2b1ae736faf5dce76e6f75e4103f4a3c02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Fri, 15 Mar 2024 21:08:24 +0800 Subject: [PATCH 3/7] refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- XNAControls/XNATextBox.cs | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/XNAControls/XNATextBox.cs b/XNAControls/XNATextBox.cs index 8a053fc..47cea36 100644 --- a/XNAControls/XNATextBox.cs +++ b/XNAControls/XNATextBox.cs @@ -675,41 +675,27 @@ public override void Draw(GameTime gameTime) FontIndex, new Vector2(TEXT_HORIZONTAL_MARGIN, TEXT_VERTICAL_MARGIN), TextColor); - if ((DisableIME || _IMEFocus != this || string.IsNullOrEmpty(WindowManager.IMEHandler.Composition)) - && WindowManager.SelectedControl == this - && Enabled && WindowManager.HasFocus - && barTimer.TotalSeconds < BAR_ON_TIME) + if (WindowManager.SelectedControl == this + && Enabled && WindowManager.HasFocus) { int barLocationX = TEXT_HORIZONTAL_MARGIN; string inputText = Text.Substring(TextStartPosition, InputPosition - TextStartPosition); barLocationX += (int)Renderer.GetTextDimensions(inputText, FontIndex).X; - FillRectangle(new Rectangle(barLocationX, 2, 1, Height - 4), Color.White); + if (!DisableIME && _IMEFocus == this && WindowManager.IMEHandler.Enabled && !string.IsNullOrEmpty(WindowManager.IMEHandler.Composition)) + { + DrawString(WindowManager.IMEHandler.Composition, FontIndex, new(barLocationX, TEXT_VERTICAL_MARGIN), Color.Orange); + Vector2 measStr = Renderer.GetTextDimensions(WindowManager.IMEHandler.Composition.Substring(0, WindowManager.IMEHandler.CompositionCursorPos), FontIndex); + barLocationX += (int)measStr.X; } - if (!DisableIME && _IMEFocus == this && WindowManager.IMEHandler.Enabled) + if (barTimer.TotalSeconds < BAR_ON_TIME) { - var drawPos = - new Vector2( - Renderer.GetTextDimensions(Text.Substring(TextStartPosition, InputPosition - TextStartPosition), - FontIndex).X + 6, 2); - - for (var i = 0; i < WindowManager.IMEHandler.Composition.Length; i++) - { - string val = WindowManager.IMEHandler.Composition[i].ToString(); - DrawString(val, FontIndex, drawPos, Color.Orange); - Vector2 measStr = Renderer.GetTextDimensions(val, FontIndex); - drawPos += new Vector2(measStr.X, 0); - if (WindowManager.SelectedControl == this && Enabled && WindowManager.HasFocus && - barTimer.TotalSeconds < 0.5 && - i + 1 == WindowManager.IMEHandler.CompositionCursorPos) - { - DrawRectangle(new Rectangle((int)drawPos.X, (int)drawPos.Y, 1, (int)measStr.Y), - Color.White); - } + FillRectangle(new Rectangle(barLocationX, 2, 1, Height - 4), Color.White); } } + base.Draw(gameTime); } From fc40bc33fc33466711591e90b2ccbbd0c1828117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Fri, 15 Mar 2024 21:47:37 +0800 Subject: [PATCH 4/7] fixed position --- XNAControls/XNATextBox.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/XNAControls/XNATextBox.cs b/XNAControls/XNATextBox.cs index 47cea36..8cd5c6a 100644 --- a/XNAControls/XNATextBox.cs +++ b/XNAControls/XNATextBox.cs @@ -493,7 +493,10 @@ public override void Update(GameTime gameTime) { _IMEFocus = this; WindowManager.IMEHandler.StartTextComposition(); - WindowManager.IMEHandler.SetTextInputRect(RenderRectangle()); + var rect = RenderRectangle(); + rect.X += WindowManager.SceneXPosition; + rect.Y += WindowManager.SceneYPosition; + WindowManager.IMEHandler.SetTextInputRect(rect); } if (Keyboard.IsKeyHeldDown(Keys.Left)) From dfc474d5b0ab81e6f1c17a301bca5c275fda399b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 21 Mar 2024 06:27:43 +0800 Subject: [PATCH 5/7] clean --- Input/IME/IMEHandler.cs | 75 ------------------------ Input/IME/Sdl/SdlIMEHandler.cs | 20 ------- Input/IME/WinForms/WinFormsIMEHandler.cs | 44 -------------- Input/IMEHandler.cs | 73 +++++++++++++++++++++++ PlatformSpecific/SdlIMEHandler.cs | 27 +++++++++ PlatformSpecific/WinFormsIMEHandler.cs | 42 +++++++++++++ Rampastring.XNAUI.csproj | 8 +-- WindowManager.cs | 1 - XNAControls/XNATextBox.cs | 4 +- 9 files changed, 148 insertions(+), 146 deletions(-) delete mode 100644 Input/IME/IMEHandler.cs delete mode 100644 Input/IME/Sdl/SdlIMEHandler.cs delete mode 100644 Input/IME/WinForms/WinFormsIMEHandler.cs create mode 100644 Input/IMEHandler.cs create mode 100644 PlatformSpecific/SdlIMEHandler.cs create mode 100644 PlatformSpecific/WinFormsIMEHandler.cs diff --git a/Input/IME/IMEHandler.cs b/Input/IME/IMEHandler.cs deleted file mode 100644 index 4117bad..0000000 --- a/Input/IME/IMEHandler.cs +++ /dev/null @@ -1,75 +0,0 @@ -// MonoGame.IMEHelper.Common -// Copyright (c) 2020 ryancheung - -using System; - -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; - -namespace Rampastring.XNAUI.Input.IME -{ - internal abstract class IMEHandler - { - public static IMEHandler Create(Game game) - { -#if !GL - return new WinForms.WinFormsIMEHandler(game); -#else - return new Sdl.SdlIMEHandler(game); -#endif - } - - /// - /// Check if text composition is enabled currently. - /// - public abstract bool Enabled { get; protected set; } - - /// - /// Enable the system IMM service to support composited character input. - /// This should be called when you expect text input from a user and you support languages - /// that require an IME (Input Method Editor). - /// - /// - public abstract void StartTextComposition(); - - /// - /// Stop the system IMM service. - /// - /// - public abstract void StopTextComposition(); - - /// - /// Use this function to set the rectangle used to type Unicode text inputs if IME supported. - /// In SDL2, this method call gives the OS a hint for where to show the candidate text list, - /// since the OS doesn't know where you want to draw the text you received via SDL_TEXTEDITING event. - /// - public virtual void SetTextInputRect(in Rectangle rect) - { } - - /// - /// Composition String - /// - public virtual string Composition { get; protected set; } = string.Empty; - - /// - /// Caret position of the composition - /// - public virtual int CompositionCursorPos { get; protected set; } - - /// - /// Invoked when the IMM service emit character input event. - /// - /// - /// - public event EventHandler TextInput; - - /// - /// Trigger a text input event. - /// - /// - protected virtual void OnTextInput(char character) - { - TextInput?.Invoke(this, character); - } - } -} \ No newline at end of file diff --git a/Input/IME/Sdl/SdlIMEHandler.cs b/Input/IME/Sdl/SdlIMEHandler.cs deleted file mode 100644 index 3b3f8e7..0000000 --- a/Input/IME/Sdl/SdlIMEHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Xna.Framework; - -namespace Rampastring.XNAUI.Input.IME.Sdl -{ - /// - /// Integrate IME to DesktopGL(SDL2) platform. - /// - internal sealed class SdlIMEHandler(Game game) : IMEHandler - { - public override bool Enabled { get => false; protected set => _ = value; } - - public override void StartTextComposition() - { - } - - public override void StopTextComposition() - { - } - } -} \ No newline at end of file diff --git a/Input/IME/WinForms/WinFormsIMEHandler.cs b/Input/IME/WinForms/WinFormsIMEHandler.cs deleted file mode 100644 index 8ccd4f9..0000000 --- a/Input/IME/WinForms/WinFormsIMEHandler.cs +++ /dev/null @@ -1,44 +0,0 @@ -using ImeSharp; - -using Microsoft.Xna.Framework; - -namespace Rampastring.XNAUI.Input.IME.WinForms -{ - /// - /// Integrate IME to XNA framework. - /// - internal class WinFormsIMEHandler : IMEHandler - { - public WinFormsIMEHandler(Game game) - { - InputMethod.Initialize(game.Window.Handle); - InputMethod.TextInputCallback = OnTextInput; - InputMethod.TextCompositionCallback = (compositionText, cursorPosition, _, _, _, _) => - { - Composition = compositionText.ToString(); - CompositionCursorPos = cursorPosition; - }; - } - - public override bool Enabled - { - get => InputMethod.Enabled; - protected set => InputMethod.Enabled = value; - } - - public override void StartTextComposition() - { - Enabled = true; - } - - public override void StopTextComposition() - { - Enabled = false; - } - - public override void SetTextInputRect(in Rectangle rect) - { - InputMethod.SetTextInputRect(rect.X, rect.Y, rect.Width, rect.Height); - } - } -} \ No newline at end of file diff --git a/Input/IMEHandler.cs b/Input/IMEHandler.cs new file mode 100644 index 0000000..9cb7ff9 --- /dev/null +++ b/Input/IMEHandler.cs @@ -0,0 +1,73 @@ +// MonoGame.IMEHelper.Common +// Copyright (c) 2020 ryancheung + +using System; + +using Microsoft.Xna.Framework; + +using Rampastring.XNAUI.PlatformSpecific; + +namespace Rampastring.XNAUI.Input; + +internal abstract class IMEHandler +{ + /// + /// Check if text composition is enabled currently. + /// + public abstract bool Enabled { get; protected set; } + + /// + /// Composition String + /// + public virtual string Composition { get; protected set; } = string.Empty; + + /// + /// Caret position of the composition + /// + public virtual int CompositionCursorPos { get; protected set; } + + /// + /// Invoked when the IMM service emit character input event. + /// + /// + /// + public event EventHandler TextInput; + + public static IMEHandler Create(Game game) + { +#if !GL + return new WinFormsIMEHandler(game); +#else + return new SdlIMEHandler(game); +#endif + } + + /// + /// Enable the system IMM service to support composited character input. + /// This should be called when you expect text input from a user and you support languages + /// that require an IME (Input Method Editor). + /// + /// + public abstract void StartTextComposition(); + + /// + /// Stop the system IMM service. + /// + /// + public abstract void StopTextComposition(); + + /// + /// Use this function to set the rectangle used to type Unicode text inputs if IME supported. + /// In SDL2, this method call gives the OS a hint for where to show the candidate text list, + /// since the OS doesn't know where you want to draw the text you received via SDL_TEXTEDITING event. + /// + public virtual void SetTextInputRect(in Rectangle rect) + { } + + /// + /// Trigger a text input event. + /// + /// + protected virtual void OnTextInput(char character) + => TextInput?.Invoke(this, character); +} diff --git a/PlatformSpecific/SdlIMEHandler.cs b/PlatformSpecific/SdlIMEHandler.cs new file mode 100644 index 0000000..25eca5e --- /dev/null +++ b/PlatformSpecific/SdlIMEHandler.cs @@ -0,0 +1,27 @@ +using Microsoft.Xna.Framework; + +using Rampastring.XNAUI.Input; + +namespace Rampastring.XNAUI.PlatformSpecific; + +/// +/// Integrate IME to DesktopGL(SDL2) platform. +/// +/// +/// Note: We were unable to provide reliable input method support for +/// SDL2 due to the lack of a way to be able to stabilize hooks for +/// the SDL2 main loop.
+/// Perhaps this requires some changes in Monogame. +///
+internal sealed class SdlIMEHandler(Game game) : IMEHandler +{ + public override bool Enabled { get => false; protected set => _ = value; } + + public override void StartTextComposition() + { + } + + public override void StopTextComposition() + { + } +} diff --git a/PlatformSpecific/WinFormsIMEHandler.cs b/PlatformSpecific/WinFormsIMEHandler.cs new file mode 100644 index 0000000..cf23add --- /dev/null +++ b/PlatformSpecific/WinFormsIMEHandler.cs @@ -0,0 +1,42 @@ +using ImeSharp; + +using Microsoft.Xna.Framework; + +using Rampastring.XNAUI.Input; + +namespace Rampastring.XNAUI.PlatformSpecific; + +/// +/// Integrate IME to XNA framework. +/// +internal class WinFormsIMEHandler : IMEHandler +{ + public override bool Enabled + { + get => InputMethod.Enabled; + protected set => InputMethod.Enabled = value; + } + + public WinFormsIMEHandler(Game game) + { + InputMethod.Initialize(game.Window.Handle); + InputMethod.TextInputCallback = OnTextInput; + InputMethod.TextCompositionCallback = (compositionText, cursorPosition, _, _, _, _) => + { + Composition = compositionText.ToString(); + CompositionCursorPos = cursorPosition; + }; + } + + + public override void StartTextComposition() + => Enabled = true; + + + public override void StopTextComposition() + => Enabled = false; + + + public override void SetTextInputRect(in Rectangle rect) + => InputMethod.SetTextInputRect(rect.X, rect.Y, rect.Width, rect.Height); +} diff --git a/Rampastring.XNAUI.csproj b/Rampastring.XNAUI.csproj index 0432d9e..4852cf6 100644 --- a/Rampastring.XNAUI.csproj +++ b/Rampastring.XNAUI.csproj @@ -123,13 +123,13 @@ - - + + - - + + diff --git a/WindowManager.cs b/WindowManager.cs index fc11184..ea4652b 100644 --- a/WindowManager.cs +++ b/WindowManager.cs @@ -11,7 +11,6 @@ using System.Diagnostics; using System.Linq; using Rampastring.XNAUI.PlatformSpecific; -using Rampastring.XNAUI.Input.IME; #if NETFRAMEWORK using System.Reflection; #endif diff --git a/XNAControls/XNATextBox.cs b/XNAControls/XNATextBox.cs index 8cd5c6a..cf8e38b 100644 --- a/XNAControls/XNATextBox.cs +++ b/XNAControls/XNATextBox.cs @@ -691,10 +691,10 @@ public override void Draw(GameTime gameTime) DrawString(WindowManager.IMEHandler.Composition, FontIndex, new(barLocationX, TEXT_VERTICAL_MARGIN), Color.Orange); Vector2 measStr = Renderer.GetTextDimensions(WindowManager.IMEHandler.Composition.Substring(0, WindowManager.IMEHandler.CompositionCursorPos), FontIndex); barLocationX += (int)measStr.X; - } + } if (barTimer.TotalSeconds < BAR_ON_TIME) - { + { FillRectangle(new Rectangle(barLocationX, 2, 1, Height - 4), Color.White); } } From 59d2c2c3100810f96e9d168e2922611031b7f16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 21 Mar 2024 06:47:14 +0800 Subject: [PATCH 6/7] Fixed an issue that could cause tab switching focus to result in a false start IME and incorrect IME position. --- XNAControls/XNATextBox.cs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/XNAControls/XNATextBox.cs b/XNAControls/XNATextBox.cs index cf8e38b..eb212ab 100644 --- a/XNAControls/XNATextBox.cs +++ b/XNAControls/XNATextBox.cs @@ -425,14 +425,24 @@ protected virtual bool HandleKeyPress(Keys key) { if (PreviousControl != null) { - _IMEFocus = WindowManager.SelectedControl = PreviousControl; - WindowManager.IMEHandler.SetTextInputRect(_IMEFocus.RenderRectangle()); + WindowManager.SelectedControl = PreviousControl; + _IMEFocus = null; + if (PreviousControl is XNATextBox tb) + { + _IMEFocus = tb; + tb.SetTextInputRect(); } } + } else if (NextControl != null) { - _IMEFocus = WindowManager.SelectedControl = NextControl; - WindowManager.IMEHandler.SetTextInputRect(_IMEFocus.RenderRectangle()); + WindowManager.SelectedControl = NextControl; + _IMEFocus = null; + if (NextControl is XNATextBox tb) + { + _IMEFocus = tb; + tb.SetTextInputRect(); + } } return true; @@ -476,6 +486,14 @@ public override void OnLeftClick() base.OnLeftClick(); } + private void SetTextInputRect() + { + var rect = RenderRectangle(); + rect.X += WindowManager.SceneXPosition; + rect.Y += WindowManager.SceneYPosition; + WindowManager.IMEHandler.SetTextInputRect(rect); + } + public override void Update(GameTime gameTime) { base.Update(gameTime); @@ -493,10 +511,7 @@ public override void Update(GameTime gameTime) { _IMEFocus = this; WindowManager.IMEHandler.StartTextComposition(); - var rect = RenderRectangle(); - rect.X += WindowManager.SceneXPosition; - rect.Y += WindowManager.SceneYPosition; - WindowManager.IMEHandler.SetTextInputRect(rect); + SetTextInputRect(); } if (Keyboard.IsKeyHeldDown(Keys.Left)) From 74399f9ab0ffe4f4a5c2cd83abe4a1d70edb1b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Sun, 27 Oct 2024 20:40:07 +0800 Subject: [PATCH 7/7] fixed a bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixed the backspace was not work on first time. Signed-off-by: 舰队的偶像-岛风酱! --- Input/CompositionChangedEventArgs.cs | 12 ++++++++++++ Input/IMEHandler.cs | 15 +++++++++++++-- XNAControls/XNATextBox.cs | 9 ++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 Input/CompositionChangedEventArgs.cs diff --git a/Input/CompositionChangedEventArgs.cs b/Input/CompositionChangedEventArgs.cs new file mode 100644 index 0000000..0980cc2 --- /dev/null +++ b/Input/CompositionChangedEventArgs.cs @@ -0,0 +1,12 @@ +// MonoGame.IMEHelper.Common +// Copyright (c) 2020 ryancheung + +using System; + +namespace Rampastring.XNAUI.Input; + +internal sealed class CompositionChangedEventArgs(string oldValue, string newValue) : EventArgs +{ + public string OldValue => oldValue; + public string NewValue => newValue; +} diff --git a/Input/IMEHandler.cs b/Input/IMEHandler.cs index 9cb7ff9..d8ad217 100644 --- a/Input/IMEHandler.cs +++ b/Input/IMEHandler.cs @@ -11,6 +11,8 @@ namespace Rampastring.XNAUI.Input; internal abstract class IMEHandler { + private string composition = string.Empty; + /// /// Check if text composition is enabled currently. /// @@ -19,8 +21,16 @@ internal abstract class IMEHandler /// /// Composition String /// - public virtual string Composition { get; protected set; } = string.Empty; - + public virtual string Composition + { + get => composition; + protected set + { + string old = composition; + composition = value; + CompositionChanged?.Invoke(null, new(old, value)); + } + } /// /// Caret position of the composition /// @@ -32,6 +42,7 @@ internal abstract class IMEHandler /// /// public event EventHandler TextInput; + public event EventHandler CompositionChanged; public static IMEHandler Create(Game game) { diff --git a/XNAControls/XNATextBox.cs b/XNAControls/XNATextBox.cs index eb212ab..b130833 100644 --- a/XNAControls/XNATextBox.cs +++ b/XNAControls/XNATextBox.cs @@ -21,7 +21,7 @@ public class XNATextBox : XNAControl protected const double BAR_ON_TIME = 0.5; protected const double BAR_OFF_TIME = 0.5; private static volatile XNAControl _IMEFocus; - private volatile bool _textCompositionDelay; + private volatile bool _textCompositionDelay = true; /// /// Creates a new text box. @@ -724,6 +724,13 @@ private void InitializeIMEHelper() return; WindowManager.IMEHandler.TextInput += IMEHandler_TextInput; +WindowManager.IMEHandler.CompositionChanged += IMEHandler_CompositionChanged; + } + + private void IMEHandler_CompositionChanged(object sender, CompositionChangedEventArgs e) + { + if (_textCompositionDelay && !string.IsNullOrEmpty(e.OldValue) && string.IsNullOrEmpty(e.NewValue)) + _textCompositionDelay = false; } private void IMEHandler_TextInput(object sender, char e)