diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 7dcb91862..b5ca45799 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -174,9 +174,9 @@ namespace Barotrauma Character.DisableControls = true; - if (PlayerInput.KeyHit(Keys.Tab)) + if (PlayerInput.KeyHit(Keys.Tab) && !textBox.IsIMEActive) { - textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 ); + textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 ); } if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/EventInput/EventInput.cs b/Barotrauma/BarotraumaClient/ClientSource/EventInput/EventInput.cs index fdf54af3f..be47a0793 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/EventInput/EventInput.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/EventInput/EventInput.cs @@ -1,72 +1,18 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; -using System; -using System.Runtime.InteropServices; -using System.Text; namespace EventInput { - public class CharacterEventArgs : EventArgs + public readonly record struct CharacterEventArgs(char Character, long Param) { - private readonly char character; - private readonly long lParam; - - public CharacterEventArgs(char character, long lParam) - { - this.character = character; - this.lParam = lParam; - } - - public char Character - { - get { return character; } - } - - public long Param - { - get { return lParam; } - } - - public long RepeatCount - { - get { return lParam & 0xffff; } - } - - public bool ExtendedKey - { - get { return (lParam & (1 << 24)) > 0; } - } - - public bool AltPressed - { - get { return (lParam & (1 << 29)) > 0; } - } - - public bool PreviousState - { - get { return (lParam & (1 << 30)) > 0; } - } - - public bool TransitionState - { - get { return (lParam & (1 << 31)) > 0; } - } + public long RepeatCount => Param & 0xffff; + public bool ExtendedKey => (Param & (1 << 24)) > 0; + public bool AltPressed => (Param & (1 << 29)) > 0; + public bool PreviousState => (Param & (1 << 30)) > 0; + public bool TransitionState => (Param & (1 << 31)) > 0; } - public class KeyEventArgs : EventArgs - { - private Keys keyCode; - - public KeyEventArgs(Keys keyCode) - { - this.keyCode = keyCode; - } - - public Keys KeyCode - { - get { return keyCode; } - } - } + public readonly record struct KeyEventArgs(Keys KeyCode, char Character); public delegate void CharEnteredHandler(object sender, CharacterEventArgs e); public delegate void KeyEventHandler(object sender, KeyEventArgs e); @@ -89,14 +35,11 @@ namespace EventInput /// public static event KeyEventHandler KeyUp; - -#if !WINDOWS /// /// Raised when the user is editing text and IME is in progress. - /// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243) /// public static event EditingTextHandler EditingText; -#endif + static bool initialized; @@ -110,11 +53,10 @@ namespace EventInput { return; } - + window.TextInput += ReceiveInput; -#if !WINDOWS + window.KeyDown += ReceiveKeyDown; window.TextEditing += ReceiveTextEditing; -#endif initialized = true; } @@ -122,15 +64,17 @@ namespace EventInput private static void ReceiveInput(object sender, TextInputEventArgs e) { OnCharEntered(e.Character); - KeyDown?.Invoke(sender, new KeyEventArgs(e.Key)); } -#if !WINDOWS + private static void ReceiveKeyDown(object sender, TextInputEventArgs e) + { + KeyDown?.Invoke(sender, new KeyEventArgs(e.Key, e.Character)); + } + private static void ReceiveTextEditing(object sender, TextEditingEventArgs e) { EditingText?.Invoke(sender, e); } -#endif public static void OnCharEntered(char character) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/EventInput/KeyboardDispatcher.cs b/Barotrauma/BarotraumaClient/ClientSource/EventInput/KeyboardDispatcher.cs index f18dbcd73..dc25c25d7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/EventInput/KeyboardDispatcher.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/EventInput/KeyboardDispatcher.cs @@ -10,11 +10,7 @@ namespace EventInput void ReceiveTextInput(string text); void ReceiveCommandInput(char command); void ReceiveSpecialInput(Keys key); - -#if !WINDOWS - /// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243) - void ReceiveEditingInput(string text, int start); -#endif + void ReceiveEditingInput(string text, int start, int length); bool Selected { get; set; } //or Focused } @@ -26,59 +22,27 @@ namespace EventInput EventInput.Initialize(window); EventInput.CharEntered += EventInput_CharEntered; EventInput.KeyDown += EventInput_KeyDown; -#if !WINDOWS EventInput.EditingText += EventInput_TextEditing; -#endif - - /* - * SDL by default starts in a state where it accepts IME inputs - * this is bad because this blocks keybinds since the IME thinks - * it's typing in a text box and not forwarding keybinds to the game. - */ - TextInput.StopTextInput(); + GameMain.ResetIMEWorkaround(); } -#if !WINDOWS + public void EventInput_TextEditing(object sender, TextEditingEventArgs e) { - _subscriber?.ReceiveEditingInput(e.Text, e.Start); + _subscriber?.ReceiveEditingInput(e.Text, e.Start, e.Length); } -#endif + public void EventInput_KeyDown(object sender, KeyEventArgs e) { _subscriber?.ReceiveSpecialInput(e.KeyCode); + if (char.IsControl(e.Character)) + { + _subscriber?.ReceiveCommandInput(e.Character); + } } void EventInput_CharEntered(object sender, CharacterEventArgs e) { - if (_subscriber == null) - return; - if (char.IsControl(e.Character)) - { - _subscriber.ReceiveCommandInput(e.Character); - // Doesn't work as expected. Not sure why this should be run in a separate thread. - //#if WINDOWS - // //ctrl-v - // if (e.Character == 0x16) // 22 - // { - // //XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard - // Thread thread = new Thread(PasteThread); - // thread.SetApartmentState(ApartmentState.STA); - // thread.Start(); - // thread.Join(); - // _subscriber.ReceiveTextInput(_pasteResult); - // } - // else - // { - // _subscriber.ReceiveCommandInput(e.Character); - // } - //#else - // _subscriber.ReceiveCommandInput(e.Character); - //#endif - } - else - { - _subscriber.ReceiveTextInput(e.Character); - } + _subscriber?.ReceiveTextInput(e.Character); } IKeyboardSubscriber _subscriber; @@ -97,8 +61,9 @@ namespace EventInput if (value is GUITextBox box) { - TextInput.SetTextInputRect(box.Rect); + TextInput.SetTextInputRect(box.MouseRect); TextInput.StartTextInput(); + TextInput.SetTextInputRect(box.MouseRect); } _subscriber = value; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 1cc059039..5074af0aa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -1248,10 +1248,6 @@ namespace Barotrauma UpdateMessages(deltaTime); UpdateSavingIndicator(deltaTime); } - -#if WINDOWS - GUITextBox.UpdateIME(); -#endif } public static void UpdateGUIMessageBoxesOnly(float deltaTime) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs index 465e5d771..dd848bd65 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs @@ -111,10 +111,7 @@ namespace Barotrauma } public void ReceiveTextInput(string text) { } public void ReceiveCommandInput(char command) { } - -#if !WINDOWS - public void ReceiveEditingInput(string text, int start) { } -#endif + public void ReceiveEditingInput(string text, int start, int length) { } public void ReceiveSpecialInput(Keys key) { @@ -125,9 +122,7 @@ namespace Barotrauma listBox.ReceiveSpecialInput(key); GUI.KeyboardDispatcher.Subscriber = this; break; - case Keys.Enter: - case Keys.Space: - case Keys.Escape: + default: GUI.KeyboardDispatcher.Subscriber = null; break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index d5f2e3a7e..b03bc27a6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -1347,10 +1347,7 @@ namespace Barotrauma } public void ReceiveTextInput(string text) { } public void ReceiveCommandInput(char command) { } - -#if !WINDOWS - public void ReceiveEditingInput(string text, int start) { } -#endif + public void ReceiveEditingInput(string text, int start, int length) { } public void ReceiveSpecialInput(Keys key) { @@ -1368,9 +1365,7 @@ namespace Barotrauma case Keys.Right: if (isHorizontal && AllowArrowKeyScroll) { SelectNext(playSelectSound: PlaySelectSound.Yes); } break; - case Keys.Enter: - case Keys.Space: - case Keys.Escape: + default: GUI.KeyboardDispatcher.Subscriber = null; break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs index d3ab8134d..c9a7631cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs @@ -223,24 +223,24 @@ namespace Barotrauma public ScalableFont GetFontForStr(string str) => Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str)); - public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth) + public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth) { - DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth); + DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth); } - public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth) + public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth) { - GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth); + GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth); } - public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft) + public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft) { - DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth, alignment); + DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment); } - public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit) + public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit) { - GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth, alignment, forceUpperCase); + GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment, forceUpperCase); } public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit, bool italics = false) @@ -253,9 +253,9 @@ namespace Barotrauma GetFontForStr(text).DrawString(sb, text, position, color, forceUpperCase, italics); } - public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, in ImmutableArray? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit) + public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, in ImmutableArray? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit) { - GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, se, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase); + GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase); } public Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing = false) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs index dae60140f..4972e95d3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs @@ -256,6 +256,8 @@ namespace Barotrauma private readonly IMEPreviewTextHandler imePreviewTextHandler; + public bool IsIMEActive => imePreviewTextHandler is { HasText: true }; + public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true) : base(style, rectT) @@ -574,6 +576,7 @@ namespace Barotrauma { CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length); OnTextChanged?.Invoke(this, Text); + imePreviewTextHandler?.Reset(); } } @@ -602,10 +605,12 @@ namespace Barotrauma public void ReceiveCommandInput(char command) { - if (Text == null) Text = ""; + if (IsIMEActive) { return; } + + if (Text == null) { Text = ""; } // Prevent alt gr from triggering any of these as that combination is often needed for special characters - if (PlayerInput.IsAltDown()) return; + if (PlayerInput.IsAltDown()) { return; } switch (command) { @@ -678,21 +683,22 @@ namespace Barotrauma break; } } -#if !WINDOWS - public void ReceiveEditingInput(string text, int start) + + public void ReceiveEditingInput(string text, int start, int length) { if (string.IsNullOrEmpty(text)) { - if (start is 0) { imePreviewTextHandler.Reset(); } + imePreviewTextHandler.Reset(); return; } - imePreviewTextHandler.UpdateText(text, start); + imePreviewTextHandler.UpdateText(text, start, length); } -#endif public void ReceiveSpecialInput(Keys key) { + if (IsIMEActive) { return; } + switch (key) { case Keys.Left: @@ -883,20 +889,7 @@ namespace Barotrauma public void DrawIMEPreview(SpriteBatch spriteBatch) { - if (!imePreviewTextHandler.HasText) { return; } - - Vector2 imePosition = CaretScreenPos; - int inflate = GUI.IntScale(3); - - RectangleF rect = new RectangleF(imePosition, imePreviewTextHandler.TextSize); - rect.Inflate(inflate, inflate); - - RectangleF borderRect = rect; - borderRect.Inflate(1, 1); - - GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f); - GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f); - Font.DrawString(spriteBatch, imePreviewTextHandler.PreviewText, imePosition, GUIStyle.Orange, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, 0, alignment: textBlock.TextAlignment, forceUpperCase: textBlock.ForceUpperCase); + imePreviewTextHandler.DrawIMEPreview(spriteBatch, CaretScreenPos, textBlock); } private void CalculateSelection() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBoxIME.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBoxIME.cs deleted file mode 100644 index ca20e30cf..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBoxIME.cs +++ /dev/null @@ -1,86 +0,0 @@ -using ImeSharp; -using Microsoft.Xna.Framework; -using System; - -namespace Barotrauma; - -/// -/// A class for handling Input Method Editor (used for inputting e.g. Chinese and Japanese text) -/// -public partial class GUITextBox : GUIComponent -{ - private static bool initialized; - - public static GUIFrame IMEWindow { get; set; } - public static GUITextBlock IMETextBlock { get; set; } - - public static void UpdateIME() - { - if (!initialized) { InitializeIME(); } - if (GUI.KeyboardDispatcher.Subscriber is GUITextBox { Selected: true }) - { - IMEWindow?.AddToGUIUpdateList(order: 10); - } - } - - private static void InitializeIME() - { - InputMethod.Initialize(GameMain.Instance.Window.Hwnd, false); - InputMethod.TextCompositionCallback = OnTextComposition; - InputMethod.CommitTextCompositionCallback = OnCommitTextComposition; - InputMethod.Enabled = true; - IMEWindow = new GUIFrame(new RectTransform(new Point(GUI.IntScale(300), GUI.IntScale(300)), GUI.Canvas), "InnerFrame") { CanBeFocused = false, Visible = false }; - IMETextBlock = new GUITextBlock(new RectTransform(Vector2.One, IMEWindow.RectTransform), "") { CanBeFocused = false }; - - initialized = true; - } - - private static void OnTextComposition(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection) - { - if (GUI.KeyboardDispatcher.Subscriber is not GUITextBox { Selected: true } textBox) { return; } - IMEWindow.Visible = true; - string text = compositionText.ToString().Insert(cursorPosition, "|"); - if (candidateList != null) - { - text += "\n"; - for (int i = 0; i < candidatePageSize; i++) - { - string candidateStr = $"\t{candidatePageStart + i + 1} {candidateList[i]}"; - if (candidateSelection == i) - { - candidateStr = $" ‖color:{XMLExtensions.ToStringHex(Color.White)}‖{candidateStr}‖end‖"; - } - candidateStr += "\n"; - text += candidateStr; - } - } - IMETextBlock.Text = RichString.Rich(text); - - IMEWindow.RectTransform.NonScaledSize = new Point( - Math.Max(IMEWindow.Rect.Width, (int)IMETextBlock.TextSize.X + GUI.IntScale(32)), - (int)IMETextBlock.TextSize.Y); - - Point windowPos = new Point(textBox.Rect.X, textBox.Rect.Bottom); - if (windowPos.Y + IMEWindow.Rect.Height > GameMain.GraphicsHeight) - { - windowPos.Y = textBox.Rect.Y - IMEWindow.Rect.Height; - } - IMEWindow.RectTransform.ScreenSpaceOffset = windowPos; - } - - private static void OnCommitTextComposition(string text) - { - if (IMEWindow.Visible) - { - foreach (char c in text) - { - if (!char.IsControl(c)) - { - GUI.KeyboardDispatcher.Subscriber?.ReceiveTextInput(c); - } - } - } - IMEWindow.Visible = false; - } -} - diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/IMEPreviewText.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/IMEPreviewText.cs index 96f137a08..3e57935de 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/IMEPreviewText.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/IMEPreviewText.cs @@ -1,18 +1,25 @@ #nullable enable +using System.Collections.Immutable; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; namespace Barotrauma { + public sealed class IMEPreviewTextHandler { - public string PreviewText { get; private set; } = string.Empty; - public Vector2 TextSize { get; private set; } - public bool HasText => !string.IsNullOrEmpty(PreviewText); + public bool HasText => !string.IsNullOrEmpty(previewText); // This has to be settable because for some reason we update the font of GUITextBox in some places public GUIFont Font { get; set; } + private string previewText = string.Empty; + private Vector2 textSize; + + private bool isSectioned; + private ImmutableArray? richTextData; + public IMEPreviewTextHandler(GUIFont font) { Font = font; @@ -20,35 +27,64 @@ namespace Barotrauma public void Reset() { - TextSize = Vector2.Zero; - PreviewText = string.Empty; + textSize = Vector2.Zero; + previewText = string.Empty; + richTextData = null; + isSectioned = false; } - public void UpdateText(string text, int start) + public void UpdateText(string text, int start, int length) { - if (string.IsNullOrEmpty(text) && start is 0) + isSectioned = start >= 0 && length > 0; + richTextData = null; + + if (string.IsNullOrEmpty(text)) { Reset(); return; } - int totalLength = start + text.Length; - string newText = PreviewText; - if (newText.Length > totalLength) - { - newText = newText[..totalLength]; - } + previewText = text; - if (totalLength > newText.Length) - { - // this is required for some reason on Windows - // my guess is that the order which TextEditing events come thru is not guaranteed - newText = newText.PadRight(totalLength); - } + textSize = Font.MeasureString(text); - newText = newText.Remove(start, text.Length).Insert(start, text); - PreviewText = newText; - TextSize = Font.MeasureString(PreviewText); + if (!isSectioned) { return; } + + string coloredText = ToolBox.ColorSectionOfString(text, start, length, GUIStyle.Orange); + + RichString richString = RichString.Rich(coloredText); + + previewText = richString.SanitizedValue; + richTextData = richString.RichTextData; + } + + public void DrawIMEPreview(SpriteBatch spriteBatch, Vector2 position, GUITextBlock textBlock) + { + if (!HasText) { return; } + + int inflate = GUI.IntScale(3); + + RectangleF rect = new RectangleF(position, textSize); + rect.Inflate(inflate, inflate); + + RectangleF borderRect = rect; + borderRect.Inflate(1, 1); + + GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f); + GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f); + + Font.DrawStringWithColors(spriteBatch, + text: previewText, + position: position, + color: isSectioned ? GUIStyle.TextColorNormal : GUIStyle.Orange, + rotation: 0.0f, + origin: Vector2.Zero, + scale: 1f, + spriteEffects: SpriteEffects.None, + layerDepth: 0, + richTextData: richTextData, + alignment: textBlock.TextAlignment, + forceUpperCase: textBlock.ForceUpperCase); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 3e9908ee4..1133e7e63 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -377,6 +377,7 @@ namespace Barotrauma Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull)); performanceCounterTimer = Stopwatch.StartNew(); + ResetIMEWorkaround(); } /// @@ -1239,5 +1240,20 @@ namespace Barotrauma }; msgBox.Buttons[1].OnClicked = msgBox.Close; } + + /* + * On some systems, IME input is enabled by default, and being able to set the game to a state + * where it doesn't accept IME input on game launch seems very inconsistent. + * This function quickly cycles through IME input states and is called from couple different places + * to ensure that IME input is disabled properly when it's not needed. + */ + public static void ResetIMEWorkaround() + { + Rectangle rect = new Rectangle(0, 0, GraphicsWidth, GraphicsHeight); + TextInput.SetTextInputRect(rect); + TextInput.StartTextInput(); + TextInput.SetTextInputRect(rect); + TextInput.StopTextInput(); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 49d21469b..e8b831765 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -999,6 +999,7 @@ namespace Barotrauma foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory))) { + if (category == MapEntityCategory.None) { continue; } entityCategoryButtons.Add(new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), entityMenuTop.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "CategoryButton." + category.ToString()) { @@ -1085,6 +1086,7 @@ namespace Barotrauma foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory))) { + if (category == MapEntityCategory.None) { continue; } LocalizedString categoryName = TextManager.Get("MapEntityCategory." + category); maxTextWidth = (int)Math.Max(maxTextWidth, GUIStyle.SubHeadingFont.MeasureString(categoryName.Replace(" ", "\n")).X + GUI.IntScale(50)); foreach (MapEntityPrefab ep in MapEntityPrefab.List) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index e919d204e..ee18c028c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -902,22 +902,21 @@ namespace Barotrauma PlayDamageSound(damageType, damage, bodyPosition, 800.0f); } - private static readonly List tempList = new List(); public static void PlayDamageSound(string damageType, float damage, Vector2 position, float range = 2000.0f, IEnumerable tags = null) { - damage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f); - tempList.Clear(); - foreach (var s in damageSounds) - { - if ((s.DamageRange == Vector2.Zero || - (damage >= s.DamageRange.X && damage <= s.DamageRange.Y)) && - s.DamageType == damageType && - (s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag)))) - { - tempList.Add(s); - } - } - var damageSound = tempList.GetRandomUnsynced(); + //if the damage is too low for any sound, don't play anything + if (damageSounds.All(d => damage < d.DamageRange.X)) { return; } + + //allow the damage to differ by 10 from the configured damage range, + //so the same amount of damage doesn't always play the same sound + float randomizedDamage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f); + + var suitableSounds = damageSounds.Where(s => + s.DamageType == damageType && + (s.DamageRange == Vector2.Zero || (randomizedDamage >= s.DamageRange.X && randomizedDamage <= s.DamageRange.Y)) && + (s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag)))); + + var damageSound = suitableSounds.GetRandomUnsynced(); damageSound?.Sound?.Play(1.0f, range, position, muffle: !damageSound.IgnoreMuffling && ShouldMuffleSound(Character.Controlled, position, range, null)); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs index 3d193471b..e04dfc070 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs @@ -512,5 +512,34 @@ namespace Barotrauma return new Vector2(paddingX, paddingY); } + + public static string ColorSectionOfString(string text, int start, int length, Color color) + { + int end = start + length; + + if (start < 0 || length < 0 || end > text.Length) + { + throw new ArgumentOutOfRangeException($"Invalid start ({start}) or length ({length}) for text \"{text}\"."); + } + + string stichedString = string.Empty; + + if (start > 0) + { + stichedString += text[..start]; + } + + // this is the highlighted part + stichedString += ColorString(text[start..end], color); + + if (end < text.Length) + { + stichedString += text[end..]; + } + + return stichedString; + + static string ColorString(string text, Color color) => $"‖color:{color.ToStringHex()}‖{text}‖end‖"; + } } } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index cfb44edfc..168039ed4 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 100.8.0.0 + 100.9.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 905c27a4c..d04c50850 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 100.8.0.0 + 100.9.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index d7867c5c5..0ca452dae 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 100.8.0.0 + 100.9.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma @@ -117,7 +117,6 @@ - @@ -127,7 +126,6 @@ - diff --git a/Barotrauma/BarotraumaClient/libSDL2-2.0.so.0 b/Barotrauma/BarotraumaClient/libSDL2-2.0.so.0 index bdd69273a..72cda07f7 100644 Binary files a/Barotrauma/BarotraumaClient/libSDL2-2.0.so.0 and b/Barotrauma/BarotraumaClient/libSDL2-2.0.so.0 differ diff --git a/Barotrauma/BarotraumaClient/x64/SDL2.dll b/Barotrauma/BarotraumaClient/x64/SDL2.dll index 6cf858caa..8c6230e6f 100644 Binary files a/Barotrauma/BarotraumaClient/x64/SDL2.dll and b/Barotrauma/BarotraumaClient/x64/SDL2.dll differ diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 933194f68..afe636dbf 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 100.8.0.0 + 100.9.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 7f9ad31c5..0804cbb26 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 100.8.0.0 + 100.9.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 8a01f4cb3..e6a845b11 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 100.8.0.0 + 100.9.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs index 3ce84413a..ad4b4df25 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs @@ -30,8 +30,7 @@ namespace Barotrauma.Abilities NotSelf = 3, Alive = 4, Monster = 5, - InFriendlySubmarine = 6, - Large = 7, + InFriendlySubmarine = 6 }; protected List ParseTargetTypes(string[] targetTypeStrings) @@ -80,9 +79,6 @@ namespace Barotrauma.Abilities return !targetCharacter.IsHuman; case TargetType.InFriendlySubmarine: return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID; - case TargetType.Large: - // mass of mudraptor is ~48 - return targetCharacter.AnimController is { Mass: > 50.0f }; default: return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs index ae8597e64..993c19b94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -1,21 +1,25 @@ -using System; +using Barotrauma.Extensions; +using System; +using System.Collections.Immutable; using System.Linq; namespace Barotrauma.Abilities { class AbilityConditionItem : AbilityConditionData { - private readonly string[] identifiers; - private readonly string[] tags; + private readonly ImmutableArray identifiers; + private readonly ImmutableArray tags; + private readonly MapEntityCategory category = MapEntityCategory.None; public AbilityConditionItem(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement) { - identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty(), convertToLowerInvariant: true); - tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + identifiers = conditionElement.GetAttributeIdentifierArray("identifiers", Array.Empty()).ToImmutableArray(); + tags = conditionElement.GetAttributeIdentifierArray("tags", Array.Empty()).ToImmutableArray(); + category = conditionElement.GetAttributeEnum("category", MapEntityCategory.None); - if (!identifiers.Any() && !tags.Any()) + if (identifiers.None() && tags.None() && category == MapEntityCategory.None) { - DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers or tags defined."); + DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers, tags or category defined."); } } @@ -33,6 +37,11 @@ namespace Barotrauma.Abilities if (itemPrefab != null) { + if (category != MapEntityCategory.None) + { + if (!itemPrefab.Category.HasFlag(category)) { return false; } + } + if (identifiers.Any()) { if (!identifiers.Any(t => itemPrefab.Identifier == t)) @@ -40,7 +49,6 @@ namespace Barotrauma.Abilities return false; } } - return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p)); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrewMemberUnconscious.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrewMemberUnconscious.cs index 09fa7aaf4..ede7abe7a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrewMemberUnconscious.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrewMemberUnconscious.cs @@ -10,7 +10,7 @@ namespace Barotrauma.Abilities { foreach (Character c in GameSession.GetSessionCrewCharacters(CharacterType.Both)) { - if (c.IsUnconscious) + if (!c.IsDead && c.IsUnconscious) { return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs index c4d73f406..5a7d22598 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -34,7 +34,7 @@ namespace Barotrauma.Abilities public bool IsViable() { - if (!AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; } + if (!AllowClientSimulation && GameMain.NetworkMember is { IsClient: true }) { return false; } if (RequiresAlive && Character.IsDead) { return false; } return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs index 287234d11..2f2ff2595 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs @@ -9,6 +9,8 @@ namespace Barotrauma.Abilities { internal sealed class CharacterAbilityUnlockApprenticeshipTalentTree : CharacterAbility { + public override bool AllowClientSimulation => false; + public CharacterAbilityUnlockApprenticeshipTalentTree(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { } public override void InitializeAbility(bool addingFirstTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 8bf71c17b..278309789 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -426,16 +426,8 @@ namespace Barotrauma.Items.Components item.body.FarseerBody.OnCollision += OnProjectileCollision; item.body.FarseerBody.IsBullet = true; - if (item.body.CollisionCategories != Category.None) - { - item.body.CollisionCategories = Physics.CollisionProjectile; - item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking; - } - if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive) - { - if (item.body.CollisionCategories == Category.None) { item.body.CollisionCategories = Physics.CollisionCharacter; } - item.body.CollidesWith |= Physics.CollisionProjectile; - } + EnableProjectileCollisions(); + IsActive = true; if (stickJoint == null) { return; } @@ -560,12 +552,12 @@ namespace Barotrauma.Items.Components } else if (fixture?.Body == null || fixture.IsSensor) { - //ignore sensors and items + //ignore sensors return true; } if (fixture.Body.UserData is VineTile) { return true; } if (fixture.CollidesWith == Category.None) { return true; } - if (fixture.Body.UserData is Item item && (item.GetComponent() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; } + if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; } //if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub @@ -575,13 +567,28 @@ namespace Barotrauma.Items.Components if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return true; } } - //ignore everything else than characters, sub walls and level walls - if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; } - if (fixture.Body.UserData is VoronoiCell && (this.item.Submarine != null || submarine != null)) { return true; } + if (fixture.Body.UserData is Item item) + { + if (item == Item) { return true; } + if (item.Condition <= 0) { return true; } + if (!item.Prefab.DamagedByProjectiles && item.GetComponent() == null) { return true; } + } + else if (fixture.Body.UserData is Holdable { CanPush: false }) + { + // Ignore holdables that can't push -> shouldn't block + return true; + } + else + { + // TODO: This might make us ignore something we don't want to ignore? + // Not item -> ignore everything else than characters, sub walls and level walls + if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && + !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && + !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; } + } + fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform); if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; } @@ -598,21 +605,17 @@ namespace Barotrauma.Items.Components } else if (fixture?.Body == null || fixture.IsSensor) { - //ignore sensors and items + //ignore sensors return -1; } if (fixture.Body.UserData is VineTile) { return -1; } - - if (fixture.Body.UserData is Item item && (item.GetComponent() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; } - if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; } if (fixture.CollidesWith == Category.None) { return -1; } - if (!(fixture.Body.UserData is Holdable holdable && holdable.CanPush)) + if (fixture.Body.UserData is Item item) { - //ignore everything else than characters, sub walls and level walls - if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; } + if (item.Condition <= 0) { return -1; } + if (!item.Prefab.DamagedByProjectiles && item.GetComponent() == null) { return -1; } } + if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; } //if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub if (submarine != null) @@ -622,6 +625,12 @@ namespace Barotrauma.Items.Components if (fixture.Body.UserData is Limb limb && limb.character?.Submarine != submarine) { return -1; } } + // Ignore holdables that can't push -> shouldn't block + if (fixture.Body.UserData is Holdable { CanPush: false }) + { + return -1; + } + //ignore level cells if the item and the point of impact are inside a sub if (fixture.Body.UserData is VoronoiCell) { @@ -652,7 +661,7 @@ namespace Barotrauma.Items.Components hits.Add(new HitscanResult(fixture, point, normal, fraction)); return 1; - }, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking); + }, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking | Physics.CollisionProjectile); return hits; } @@ -773,6 +782,12 @@ namespace Barotrauma.Items.Components else if (target.Body.UserData is Item item) { if (item.Condition <= 0.0f) { return false; } + if (!item.Prefab.DamagedByProjectiles) { return false; } + } + else if (target.Body.UserData is Holdable { CanPush: false }) + { + // Ignore holdables that can't push -> shouldn't block + return false; } //ignore character colliders (the projectile only hits limbs) @@ -1068,6 +1083,20 @@ namespace Barotrauma.Items.Components return true; } + private void EnableProjectileCollisions() + { + if (item.body.CollisionCategories != Category.None) + { + item.body.CollisionCategories = Physics.CollisionProjectile; + item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking; + } + if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive) + { + if (item.body.CollisionCategories == Category.None) { item.body.CollisionCategories = Physics.CollisionCharacter; } + item.body.CollidesWith |= Physics.CollisionProjectile; + } + } + private void DisableProjectileCollisions() { item.body.FarseerBody.OnCollision -= OnProjectileCollision; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs index bdec8d1b4..6d9077cc0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs @@ -10,6 +10,7 @@ namespace Barotrauma [Flags] enum MapEntityCategory { + None = 0, Structure = 1, Decorative = 2, Machine = 4, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs index 86cce0a43..622a47591 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs @@ -35,6 +35,7 @@ { GUI.DisableSavingIndicatorDelayed(); } + GameMain.ResetIMEWorkaround(); #endif } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 2288c7d4a..b1885c2cb 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,10 @@ +--------------------------------------------------------------------------------------------------------- +v100.9.0.0 +--------------------------------------------------------------------------------------------------------- + +- Fixes included in v0.20.10.0. +- Fixed genetic materials still being available for sale without the "Blackmarket Genes" talent. + --------------------------------------------------------------------------------------------------------- v100.8.0.0 --------------------------------------------------------------------------------------------------------- @@ -95,6 +102,31 @@ Test version of the faction overhaul: - There's now always two paths from biome to another, one controlled by the Coalition and one by Separatists. - Improvements to the campaign map. +--------------------------------------------------------------------------------------------------------- +v0.20.10.0 +--------------------------------------------------------------------------------------------------------- + +Input Method Editor (Chinese/Japanese input) fixes: +- Fixed inputs sometimes not working. Happened because listboxes stole keyboard focus, for example when rearranging bot orders or interacting with a chatbox. +- Fixed moving the input position with arrow keys in the candidate box moving the caret in the textbox too. +- Fixed current composition text disappearing when you browse through the options with arrow keys. +- Fixed candidate box not being hidden when there's no input (= when you've just selected the textbox or committed the input). + +Fixes/improvements to depth charges: +- Fixed projectile-on-projectile collisions not working properly. +- Reduced the health of the depth charges (100 -> 10) to make them easier to detonate with turrets. +- Made decoys self-destruct instead of just becoming disabled after 45 seconds, turning them into time bombs. +- Fixed depth charges disappearing with a delay (after the explosion particles had gone away, making it easy to see them vanish). +- Fixed regular depth charges being destructible before launching. + +Misc fixes: +- Fixed "Graduation Ceremony" talent unlocking 2 talent trees instead of one. +- Fixed Scrap Cannon's firing sound being barely audibly. +- Fixed "Better Than New" allowing you to repair any device past the maximum condition, not just electrical devices. +- Adjustments to Harpoon Coil-rifle sounds. +- Fixed some sprite bleed issues in the new talent icons. +- Fixed Kastrull's reactor having 1000 condition instead of 100. + --------------------------------------------------------------------------------------------------------- v0.20.9.0 --------------------------------------------------------------------------------------------------------- diff --git a/Libraries/ImeSharp/IMEString.cs b/Libraries/ImeSharp/IMEString.cs deleted file mode 100644 index 6635c85ae..000000000 --- a/Libraries/ImeSharp/IMEString.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace ImeSharp -{ - public unsafe struct IMEString : IEnumerable - { - internal const int IMECharBufferSize = 64; - - public static readonly IMEString Empty = new IMEString((List)null); - - internal struct Enumerator : IEnumerator - { - private IMEString _imeString; - private char _currentCharacter; - private int _currentIndex; - - public Enumerator(IMEString imeString) - { - _imeString = imeString; - _currentCharacter = '\0'; - _currentIndex = -1; - } - - public bool MoveNext() - { - int size = _imeString.Count; - - _currentIndex++; - - if (_currentIndex == size) - return false; - - fixed (char* ptr = _imeString.buffer) - { - _currentCharacter = *(ptr + _currentIndex); - } - - return true; - } - - public void Reset() - { - _currentIndex = -1; - } - - public void Dispose() - { - } - - public char Current { get { return _currentCharacter; } } - object IEnumerator.Current { get { return Current; } } - } - - public int Count { get { return _size; } } - - public char this[int index] - { - get - { - if (index >= Count || index < 0) - throw new ArgumentOutOfRangeException("index"); - - fixed (char* ptr = buffer) - { - return *(ptr + index); - } - } - } - - private int _size; - - fixed char buffer[IMECharBufferSize]; - - public IMEString(string characters) - { - if (string.IsNullOrEmpty(characters)) - { - _size = 0; - return; - } - - _size = characters.Length; - if (_size > IMECharBufferSize) - _size = IMECharBufferSize - 1; - - fixed (char* _ptr = buffer) - { - char* ptr = _ptr; - for (var i = 0; i < _size; i++) - { - *ptr = characters[i]; - ptr++; - } - } - } - - public IMEString(List characters) - { - if (characters == null || characters.Count == 0) - { - _size = 0; - return; - } - - _size = characters.Count; - if (_size > IMECharBufferSize) - _size = IMECharBufferSize - 1; - - fixed (char* _ptr = buffer) - { - char* ptr = _ptr; - for (var i = 0; i < _size; i++) - { - *ptr = characters[i]; - ptr++; - } - } - } - - public IMEString(char[] characters, int count) - { - if (characters == null || count <= 0) - { - _size = 0; - return; - } - - _size = count; - if (_size > IMECharBufferSize) - _size = IMECharBufferSize - 1; - - if (_size > characters.Length) - _size = characters.Length; - - fixed (char* _ptr = buffer) - { - char* ptr = _ptr; - for (var i = 0; i < _size; i++) - { - *ptr = characters[i]; - ptr++; - } - } - } - - public IMEString(IntPtr bStrPtr) - { - if (bStrPtr == IntPtr.Zero) - { - _size = 0; - return; - } - - var ptrSrc = (char*)bStrPtr; - - int i = 0; - - fixed (char* _ptr = buffer) - { - char* ptr = _ptr; - - while (ptrSrc[i] != '\0') - { - *ptr = ptrSrc[i]; - i++; - ptr++; - } - } - - _size = i; - } - - public override string ToString() - { - fixed (char* ptr = buffer) - { - return new string(ptr, 0, _size); - } - } - - public IntPtr ToIntPtr() - { - fixed (char* ptr = buffer) - { - return (IntPtr)ptr; - } - } - - public IEnumerator GetEnumerator() - { - return new Enumerator(this); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/Libraries/ImeSharp/IMETextCompositionEventArgs.cs b/Libraries/ImeSharp/IMETextCompositionEventArgs.cs deleted file mode 100644 index 93548b0be..000000000 --- a/Libraries/ImeSharp/IMETextCompositionEventArgs.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; - -namespace ImeSharp -{ - /// - /// Arguments for the event. - /// - public struct IMETextCompositionEventArgs - { - /// - // Construct a TextCompositionEventArgs with composition infos. - /// - public IMETextCompositionEventArgs(IMEString compositionText, - int cursorPosition, - IMEString[] candidateList = null, - int candidatePageStart = 0, - int candidatePageSize = 0, - int candidateSelection = 0) - { - CompositionText = compositionText; - CursorPosition = cursorPosition; - - CandidateList = candidateList; - CandidatePageStart = candidatePageStart; - CandidatePageSize = candidatePageSize; - CandidateSelection = candidateSelection; - } - - /// - /// The full string as it's composed by the IMM. - /// - public readonly IMEString CompositionText; - - /// - /// The position of the cursor inside the composed string. - /// - public readonly int CursorPosition; - - /// - /// The candidate text list for the composition. - /// This property is only supported on WindowsDX and WindowsUniversal. - /// If the composition string does not generate candidates this array is empty. - /// - public readonly IMEString[] CandidateList; - - /// - /// First candidate index of current page. - /// - public readonly int CandidatePageStart; - - /// - /// How many candidates should display per page. - /// - public readonly int CandidatePageSize; - - /// - /// The selected candidate index. - /// - public readonly int CandidateSelection; - } -} diff --git a/Libraries/ImeSharp/IMETextInputEventArgs.cs b/Libraries/ImeSharp/IMETextInputEventArgs.cs deleted file mode 100644 index 2d415a21e..000000000 --- a/Libraries/ImeSharp/IMETextInputEventArgs.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace ImeSharp -{ - public struct IMETextInputEventArgs - { - public IMETextInputEventArgs(char character) - { - Character = character; - } - - public readonly char Character; - } -} \ No newline at end of file diff --git a/Libraries/ImeSharp/ImeSharp.csproj b/Libraries/ImeSharp/ImeSharp.csproj deleted file mode 100644 index 8cd198465..000000000 --- a/Libraries/ImeSharp/ImeSharp.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net6.0 - true - - - - C# wrapper for Windows IME APIs. Its goal is to support both IMM32 and TSF. - IME;netcoreapp3.1;net5.0;net6.0;winforms;windows;tsf;imm32 - ImeSharp - https://github.com/ryancheung/ImeSharp - https://github.com/ryancheung/ImeSharp - ryancheung - MIT - - - - ..\Artifacts - ImeSharp - ImeSharp - 5 - - - - - - - - diff --git a/Libraries/ImeSharp/Imm32Manager.cs b/Libraries/ImeSharp/Imm32Manager.cs deleted file mode 100644 index 9cf0c2768..000000000 --- a/Libraries/ImeSharp/Imm32Manager.cs +++ /dev/null @@ -1,349 +0,0 @@ -using System; -using System.Buffers; -using System.Runtime.InteropServices; -using System.Globalization; -using System.Diagnostics; -using ImeSharp.Native; - -namespace ImeSharp -{ - internal class Imm32Manager - { - - // If the system is IMM enabled, this is true. - private static bool _immEnabled = SafeSystemMetrics.IsImmEnabled; - - public static bool ImmEnabled { get { return _immEnabled; } } - - public const int LANG_CHINESE = 0x04; - public const int LANG_KOREAN = 0x12; - public const int LANG_JAPANESE = 0x11; - - public static int PRIMARYLANGID(int lgid) - { - return ((ushort)(lgid) & 0x3ff); - } - - static Imm32Manager() - { - SetCurrentCulture(); - } - - /// - /// return true if the current keyboard layout is a real IMM32-IME. - /// - public static bool IsImm32ImeCurrent() - { - if (!_immEnabled) - return false; - - IntPtr hkl = NativeMethods.GetKeyboardLayout(0); - - return IsImm32Ime(hkl); - } - - /// - /// return true if the keyboard layout is a real IMM32-IME. - /// - public static bool IsImm32Ime(IntPtr hkl) - { - if (hkl == IntPtr.Zero) - return false; - - return ((NativeMethods.IntPtrToInt32(hkl) & 0xf0000000) == 0xe0000000); - } - - private static int _inputLanguageId; - - internal static void SetCurrentCulture() - { - var hkl = NativeMethods.GetKeyboardLayout(0); - _inputLanguageId = NativeMethods.IntPtrToInt32(hkl) & 0xFFFF; - } - - private IntPtr _windowHandle; - - private IntPtr _defaultImc; - private IntPtr DefaultImc - { - get - { - if (_defaultImc == IntPtr.Zero) - { - IntPtr himc = NativeMethods.ImmCreateContext(); - - // Store the default imc to _defaultImc. - _defaultImc = himc; - } - return _defaultImc; - } - } - - private static ImmCompositionStringHandler _compositionStringHandler; - private static ImmCompositionIntHandler _compositionCursorHandler; - - private bool _lastImmOpenStatus; - - public Imm32Manager(IntPtr windowHandle) - { - _windowHandle = windowHandle; - - _compositionStringHandler = new ImmCompositionStringHandler(DefaultImc, NativeMethods.GCS_COMPSTR); - _compositionCursorHandler = new ImmCompositionIntHandler(DefaultImc, NativeMethods.GCS_CURSORPOS); - } - - public static Imm32Manager Current - { - get - { - var defaultImm32Manager = InputMethod.DefaultImm32Manager; - - if (defaultImm32Manager == null) - { - defaultImm32Manager = new Imm32Manager(InputMethod.WindowHandle); - InputMethod.DefaultImm32Manager = defaultImm32Manager; - } - - return defaultImm32Manager; - } - } - - public void Enable() - { - if (DefaultImc != IntPtr.Zero) - { - // Create a temporary system caret - NativeMethods.CreateCaret(_windowHandle, IntPtr.Zero, 2, 10); - NativeMethods.ImmAssociateContext(_windowHandle, _defaultImc); - } - } - - public void Disable() - { - NativeMethods.ImmAssociateContext(_windowHandle, IntPtr.Zero); - NativeMethods.DestroyCaret(); - } - - const int kCaretMargin = 1; - - // Set candidate window position. - // Borrowed from https://github.com/chromium/chromium/blob/master/ui/base/ime/win/imm32_manager.cc - public void SetCandidateWindow(TsfSharp.Rect caretRect) - { - int x = caretRect.Left; - int y = caretRect.Top; - - if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE) - { - // Chinese IMEs ignore function calls to ::ImmSetCandidateWindow() - // when a user disables TSF (Text Service Framework) and CUAS (Cicero - // Unaware Application Support). - // On the other hand, when a user enables TSF and CUAS, Chinese IMEs - // ignore the position of the current system caret and uses the - // parameters given to ::ImmSetCandidateWindow() with its 'dwStyle' - // parameter CFS_CANDIDATEPOS. - // Therefore, we do not only call ::ImmSetCandidateWindow() but also - // set the positions of the temporary system caret. - var candidateForm = new NativeMethods.CANDIDATEFORM(); - candidateForm.dwStyle = NativeMethods.CFS_CANDIDATEPOS; - candidateForm.ptCurrentPos.X = x; - candidateForm.ptCurrentPos.Y = y; - NativeMethods.ImmSetCandidateWindow(_defaultImc, ref candidateForm); - } - - if (PRIMARYLANGID(_inputLanguageId) == LANG_JAPANESE) - NativeMethods.SetCaretPos(x, caretRect.Bottom); - else - NativeMethods.SetCaretPos(x, y); - - // Set composition window position also to ensure move the candidate window position. - var compositionForm = new NativeMethods.COMPOSITIONFORM(); - compositionForm.dwStyle = NativeMethods.CFS_POINT; - compositionForm.ptCurrentPos.X = x; - compositionForm.ptCurrentPos.Y = y; - NativeMethods.ImmSetCompositionWindow(_defaultImc, ref compositionForm); - - if (PRIMARYLANGID(_inputLanguageId) == LANG_KOREAN) - { - // Chinese IMEs and Japanese IMEs require the upper-left corner of - // the caret to move the position of their candidate windows. - // On the other hand, Korean IMEs require the lower-left corner of the - // caret to move their candidate windows. - y += kCaretMargin; - } - - // Need to return here since some Chinese IMEs would stuck if set - // candidate window position with CFS_EXCLUDE style. - if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE) return; - - // Japanese IMEs and Korean IMEs also use the rectangle given to - // ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE - // to move their candidate windows when a user disables TSF and CUAS. - // Therefore, we also set this parameter here. - var excludeRectangle = new NativeMethods.CANDIDATEFORM(); - compositionForm.dwStyle = NativeMethods.CFS_EXCLUDE; - compositionForm.ptCurrentPos.X = x; - compositionForm.ptCurrentPos.Y = y; - compositionForm.rcArea.Left = x; - compositionForm.rcArea.Top = y; - compositionForm.rcArea.Right = caretRect.Right; - compositionForm.rcArea.Bottom = caretRect.Bottom; - NativeMethods.ImmSetCandidateWindow(_defaultImc, ref excludeRectangle); - } - - internal bool ProcessMessage(IntPtr hWnd, uint msg, ref IntPtr wParam, ref IntPtr lParam) - { - switch (msg) - { - case NativeMethods.WM_INPUTLANGCHANGE: - SetCurrentCulture(); - break; - case NativeMethods.WM_IME_SETCONTEXT: - if (wParam.ToInt32() == 1 && InputMethod.Enabled) - { - // Must re-associate ime context or things won't work. - NativeMethods.ImmAssociateContext(_windowHandle, DefaultImc); - - if (_lastImmOpenStatus) - NativeMethods.ImmSetOpenStatus(DefaultImc, true); - - var lParam64 = lParam.ToInt64(); - if (!InputMethod.ShowOSImeWindow) - lParam64 &= ~NativeMethods.ISC_SHOWUICANDIDATEWINDOW; - else - lParam64 &= ~NativeMethods.ISC_SHOWUICOMPOSITIONWINDOW; - lParam = (IntPtr)(int)lParam64; - } - break; - case NativeMethods.WM_KILLFOCUS: - _lastImmOpenStatus = NativeMethods.ImmGetOpenStatus(DefaultImc); - break; - case NativeMethods.WM_IME_NOTIFY: - IMENotify(wParam.ToInt32()); - if (!InputMethod.ShowOSImeWindow) - return true; - break; - case NativeMethods.WM_IME_STARTCOMPOSITION: - //Debug.WriteLine("NativeMethods.WM_IME_STARTCOMPOSITION"); - IMEStartComposion(lParam.ToInt32()); - // Force to not show composition window, `lParam64 &= ~ISC_SHOWUICOMPOSITIONWINDOW` don't work sometime. - return true; - case NativeMethods.WM_IME_COMPOSITION: - //Debug.WriteLine("NativeMethods.WM_IME_COMPOSITION"); - IMEComposition(lParam.ToInt32()); - break; - case NativeMethods.WM_IME_ENDCOMPOSITION: - //Debug.WriteLine("NativeMethods.WM_IME_ENDCOMPOSITION"); - IMEEndComposition(lParam.ToInt32()); - if (!InputMethod.ShowOSImeWindow) - return true; - break; - } - - return false; - } - - private void IMENotify(int WParam) - { - switch (WParam) - { - case NativeMethods.IMN_OPENCANDIDATE: - case NativeMethods.IMN_CHANGECANDIDATE: - IMEChangeCandidate(); - break; - case NativeMethods.IMN_CLOSECANDIDATE: - InputMethod.ClearCandidates(); - break; - default: - break; - } - } - - private void IMEChangeCandidate() - { - if (TextServicesLoader.ServicesInstalled) // TSF is enabled - { - if (!TextStore.Current.SupportUIElement) // But active IME not support UIElement - UpdateCandidates(); // We have to fetch candidate list here. - - return; - } - - // Normal candidate list fetch in IMM32 - UpdateCandidates(); - // Send event on candidate updates - InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value); - - if (InputMethod.CandidateList != null) - ArrayPool.Shared.Return(InputMethod.CandidateList); - } - - private unsafe void UpdateCandidates() - { - uint length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, IntPtr.Zero, 0); - if (length > 0) - { - IntPtr pointer = Marshal.AllocHGlobal((int)length); - length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, pointer, length); - NativeMethods.CANDIDATELIST* cList = (NativeMethods.CANDIDATELIST*)pointer; - - var selection = (int)cList->dwSelection; - var pageStart = (int)cList->dwPageStart; - var pageSize = (int)cList->dwPageSize; - - selection -= pageStart; - - IMEString[] candidates = ArrayPool.Shared.Rent(pageSize); - - int i, j; - for (i = pageStart, j = 0; i < cList->dwCount && j < pageSize; i++, j++) - { - int sOffset = Marshal.ReadInt32(pointer, 24 + 4 * i); - candidates[j] = new IMEString(pointer + sOffset); - } - - //Debug.WriteLine("IMM========IMM"); - //Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, candidates:", pageStart, pageSize, selection); - //for (int k = 0; k < candidates.Length; k++) - // Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : ""); - //Debug.WriteLine("IMM++++++++IMM"); - - InputMethod.CandidatePageStart = pageStart; - InputMethod.CandidatePageSize = pageSize; - InputMethod.CandidateSelection = selection; - InputMethod.CandidateList = candidates; - - Marshal.FreeHGlobal(pointer); - } - } - - private void ClearComposition() - { - _compositionStringHandler.Clear(); - } - - private void IMEStartComposion(int lParam) - { - InputMethod.OnTextCompositionStarted(this); - ClearComposition(); - } - - private void IMEComposition(int lParam) - { - if (_compositionStringHandler.Update(lParam)) - { - _compositionCursorHandler.Update(); - - InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value); - } - } - - private void IMEEndComposition(int lParam) - { - InputMethod.ClearCandidates(); - ClearComposition(); - - InputMethod.OnTextCompositionEnded(this); - } - } -} diff --git a/Libraries/ImeSharp/ImmCompositionResultHandler.cs b/Libraries/ImeSharp/ImmCompositionResultHandler.cs deleted file mode 100644 index 69d3d651b..000000000 --- a/Libraries/ImeSharp/ImmCompositionResultHandler.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Text; -using System.Runtime.InteropServices; -using ImeSharp.Native; - -namespace ImeSharp -{ - internal abstract class ImmCompositionResultHandler - { - protected IntPtr _imeContext; - - public int Flag { get; private set; } - - internal ImmCompositionResultHandler(IntPtr imeContext, int flag) - { - this.Flag = flag; - _imeContext = imeContext; - } - - internal virtual void Update() { } - - internal bool Update(int lParam) - { - if ((lParam & Flag) == Flag) - { - Update(); - return true; - } - return false; - } - } - - internal class ImmCompositionStringHandler : ImmCompositionResultHandler - { - internal const int BufferSize = 1024; - private byte[] _byteBuffer = new byte[BufferSize]; - private int _byteCount; - - private char[] _charBuffer = new char[BufferSize / 2]; - private int _charCount; - - public char[] Values { get { return _charBuffer; } } - public int Count { get { return _charCount; } } - - public char this[int index] - { - get - { - if (index >= _charCount || index < 0) - throw new ArgumentOutOfRangeException("index"); - - return _charBuffer[index]; - } - } - - internal ImmCompositionStringHandler(IntPtr imeContext, int flag) : base(imeContext, flag) - { - } - - public override string ToString() - { - if (_charCount <= 0) - return string.Empty; - - return new string(_charBuffer, 0, _charCount); - } - - internal void Clear() - { - Array.Clear(_byteBuffer, 0, _byteCount); - _byteCount = 0; - - Array.Clear(_charBuffer, 0, _charCount); - _charCount = 0; - } - - internal override void Update() - { - _byteCount = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0); - IntPtr pointer = Marshal.AllocHGlobal(_byteCount); - - try - { - Array.Clear(_byteBuffer, 0, _byteCount); - - if (_byteCount > 0) - { - NativeMethods.ImmGetCompositionString(_imeContext, Flag, pointer, _byteCount); - - Marshal.Copy(pointer, _byteBuffer, 0, _byteCount); - - Array.Clear(_charBuffer, 0, _charCount); - _charCount = Encoding.Unicode.GetChars(_byteBuffer, 0, _byteCount, _charBuffer, 0); - } - } - finally - { - Marshal.FreeHGlobal(pointer); - } - } - } - - internal class ImmCompositionIntHandler : ImmCompositionResultHandler - { - public int Value { get; private set; } - - internal ImmCompositionIntHandler(IntPtr imeContext, int flag) : base(imeContext, flag) { } - - public override string ToString() - { - return Value.ToString(); - } - - internal override void Update() - { - Value = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0); - } - } -} diff --git a/Libraries/ImeSharp/InputMethod.cs b/Libraries/ImeSharp/InputMethod.cs deleted file mode 100644 index fa178cbd7..000000000 --- a/Libraries/ImeSharp/InputMethod.cs +++ /dev/null @@ -1,246 +0,0 @@ -using System; -using System.Globalization; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using ImeSharp.Native; - -namespace ImeSharp -{ - public static class InputMethod - { - private static IntPtr _windowHandle; - public static IntPtr WindowHandle { get { return _windowHandle; } } - - private static IntPtr _prevWndProc; - private static NativeMethods.WndProcDelegate _wndProcDelegate; - - private static TextServicesContext _textServicesContext; - internal static TextServicesContext TextServicesContext - { - get { return _textServicesContext; } - set { _textServicesContext = value; } - } - - private static TextStore _defaultTextStore; - internal static TextStore DefaultTextStore - { - get { return _defaultTextStore; } - set { _defaultTextStore = value; } - } - - private static Imm32Manager _defaultImm32Manager; - internal static Imm32Manager DefaultImm32Manager - { - get { return _defaultImm32Manager; } - set { _defaultImm32Manager = value; } - } - - private static bool _enabled; - public static bool Enabled - { - get { return _enabled; } - set - { - if (_enabled == value) return; - - _enabled = value; - - EnableOrDisableInputMethod(_enabled); - } - } - - internal static TsfSharp.Rect TextInputRect; - - /// - /// Set the position of the candidate window rendered by the OS. - /// Let the OS render the candidate window by set param "showOSImeWindow" to true on . - /// - public static void SetTextInputRect(int x, int y, int width, int height) - { - if (!_showOSImeWindow) return; - - TextInputRect.Left = x; - TextInputRect.Top = y; - TextInputRect.Right = x + width; - TextInputRect.Bottom = y + height; - - if (Imm32Manager.ImmEnabled) - Imm32Manager.Current.SetCandidateWindow(TextInputRect); - } - - private static bool _showOSImeWindow = false; - - /// - /// Return if let OS render IME Candidate window or not. - /// - public static bool ShowOSImeWindow { get { return _showOSImeWindow; } } - - internal static int CandidatePageStart; - internal static int CandidatePageSize; - internal static int CandidateSelection; - internal static IMEString[] CandidateList; - - internal static void ClearCandidates() - { - CandidateList = null; - CandidatePageStart = 0; - CandidatePageSize = 0; - CandidateSelection = 0; - } - - public static event EventHandler TextComposition; - public static event EventHandler TextInput; - public static event EventHandler CommitTextComposition; - - public static TextInputCallback TextInputCallback { get; set; } - public static TextCompositionCallback TextCompositionCallback { get; set; } - public static CommitTextCompositionCallback CommitTextCompositionCallback { get; set; } - - /// - /// Initialize InputMethod with a Window Handle. - /// Let the OS render the candidate window by set to true. - /// - public static void Initialize(IntPtr windowHandle, bool showOSImeWindow = true) - { - if (_windowHandle != IntPtr.Zero) - throw new InvalidOperationException("InputMethod can only be initialized once!"); - - _windowHandle = windowHandle; - _showOSImeWindow = showOSImeWindow; - - _wndProcDelegate = new NativeMethods.WndProcDelegate(WndProc); - _prevWndProc = (IntPtr)NativeMethods.SetWindowLongPtr(_windowHandle, NativeMethods.GWL_WNDPROC, - Marshal.GetFunctionPointerForDelegate(_wndProcDelegate)); - } - - internal static void OnTextInput(object sender, char character) - { - if (TextInput != null) - TextInput.Invoke(sender, new IMETextInputEventArgs(character)); - - if (TextInputCallback != null) - TextInputCallback(character); - } - - // Some Chinese IME only send composition start event but no composition update event. - // We need this to ensure candidate window position can be set in time. - internal static void OnTextCompositionStarted(object sender) - { - if (TextComposition != null) - TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0)); - - if (TextCompositionCallback != null) - TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0); - } - - // On text composition update. - internal static void OnTextComposition(object sender, IMEString compositionText, int cursorPos) - { - if (compositionText.Count == 0) // Crash guard - cursorPos = 0; - - if (cursorPos > compositionText.Count) // Another crash guard - cursorPos = compositionText.Count; - - if (TextComposition != null) - { - TextComposition.Invoke(sender, - new IMETextCompositionEventArgs(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection)); - } - - if (TextCompositionCallback != null) - TextCompositionCallback(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection); - } - - internal static void OnTextCompositionResult(object sender, string compositionResult) - { - if (CommitTextComposition != null) - CommitTextComposition.Invoke(sender, compositionResult); - - if (CommitTextCompositionCallback != null) - CommitTextCompositionCallback(compositionResult); - } - - internal static void OnTextCompositionEnded(object sender) - { - if (TextComposition != null) - TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0)); - - if (TextCompositionCallback != null) - TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0); - } - - private static void EnableOrDisableInputMethod(bool bEnabled) - { - // InputMethod enable/disabled status was changed on the current focus Element. - if (TextServicesLoader.ServicesInstalled) - { - if (bEnabled) - TextServicesContext.Current.SetFocusOnDefaultTextStore(); - else - TextServicesContext.Current.SetFocusOnEmptyDim(); - } - - // Under IMM32 enabled system, we associate default hIMC or null hIMC. - if (Imm32Manager.ImmEnabled) - { - if (bEnabled) - Imm32Manager.Current.Enable(); - else - Imm32Manager.Current.Disable(); - } - } - - private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) - { - if (Imm32Manager.ImmEnabled) - { - if (Imm32Manager.Current.ProcessMessage(hWnd, msg, ref wParam, ref lParam)) - return IntPtr.Zero; - } - - switch (msg) - { - case NativeMethods.WM_DESTROY: - TextServicesContext.Current.Uninitialize(true); - break; - case NativeMethods.WM_CHAR: - { - if (InputMethod.Enabled) - InputMethod.OnTextInput(null, (char)wParam.ToInt32()); - - break; - } - } - - return NativeMethods.CallWindowProc(_prevWndProc, hWnd, msg, wParam, lParam); - } - - /// - /// Custom windows message pumping to fix frame stuck issue. - /// Normally, you need call this method in handler. - /// - public static void PumpMessage() - { - if (!Enabled) return; - if (!TextServicesLoader.ServicesInstalled) return; - - bool result; - var msg = new NativeMethods.NativeMessage(); - - do - { - result = NativeMethods.PeekMessage(out msg, _windowHandle, 0, 0, NativeMethods.PM_REMOVE); - - if (result) - { - NativeMethods.TranslateMessage(ref msg); - NativeMethods.DispatchMessage(ref msg); - } - } while (result); - - NativeMethods.PostMessage(_windowHandle, NativeMethods.WM_NULL, IntPtr.Zero, IntPtr.Zero); - } - } -} diff --git a/Libraries/ImeSharp/Native/NativeMethods.cs b/Libraries/ImeSharp/Native/NativeMethods.cs deleted file mode 100644 index 1e3d6f901..000000000 --- a/Libraries/ImeSharp/Native/NativeMethods.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Text; -using System.Runtime.InteropServices; - -namespace ImeSharp.Native -{ - public partial class NativeMethods - { - #region Constants - - public const int S_OK = 0x00000000; - public const int S_FALSE = 0x00000001; - public const int E_FAIL = unchecked((int)0x80004005); - public const int E_INVALIDARG = unchecked((int)0x80070057); - public const int E_NOTIMPL = unchecked((int)0x80004001); - - public const int WM_KEYFIRST = 0x0100; - public const int WM_KEYDOWN = 0x0100; - public const int WM_KEYUP = 0x0101; - public const int WM_CHAR = 0x0102; - public const int WM_DEADCHAR = 0x0103; - public const int WM_SYSKEYDOWN = 0x0104; - public const int WM_SYSKEYUP = 0x0105; - public const int WM_SYSCHAR = 0x0106; - public const int WM_SYSDEADCHAR = 0x0107; - public const int WM_UNICHAR = 0x0109; - public const int WM_KEYLAST = 0x0109; - public const int UNICODE_NOCHAR = 0xFFFF; - - public const int WM_NOTIFY = 0x004E; - public const int WM_INPUTLANGCHANGEREQUEST = 0x0050; - public const int WM_INPUTLANGCHANGE = 0x0051; - public const int WM_TCARD = 0x0052; - public const int WM_HELP = 0x0053; - public const int WM_USERCHANGED = 0x0054; - public const int WM_NOTIFYFORMAT = 0x0055; - - public const int GWL_WNDPROC = -4; - - public const int WM_ACTIVATE = 0x0006; - // WM_ACTIVATE state values - public const int WA_INACTIVE = 0; - public const int WA_ACTIVE = 1; - public const int WA_CLICKACTIVE = 2; - - public const int WM_SETFOCUS = 0x0007; - public const int WM_KILLFOCUS = 0x0008; - - public const int WM_DESTROY = 0x0002; - public const int WM_NULL = 0x0000; - public const int WM_QUIT = 0x0012; - - public const int CLSCTX_INPROC_SERVER = 0x1; - - public const int PM_NOREMOVE = 0x0000; - public const int PM_REMOVE = 0x0001; - public const int PM_NOYIELD = 0x0002; - - #endregion Constants - - #region Structs - - [StructLayout(LayoutKind.Sequential)] - public struct NativeMessage - { - public IntPtr handle; - public uint msg; - public IntPtr wParam; - public IntPtr lParam; - public uint time; - public int ptX; - public int ptY; - } - - #endregion - - [DllImport("user32.dll")] - public static extern int GetSystemMetrics(SM nIndex); - - // We have this wrapper because casting IntPtr to int may - // generate OverflowException when one of high 32 bits is set. - public static int IntPtrToInt32(IntPtr intPtr) - { - return unchecked((int)intPtr.ToInt64()); - } - - [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)] - public static extern IntPtr GetKeyboardLayout(int dwLayout); - - public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - - [DllImport("user32.dll")] - public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); - - // This static method is required because legacy OSes do not support - public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong) - { - if (IntPtr.Size == 8) - return SetWindowLongPtr64(hWnd, nIndex, dwNewLong); - else - return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32())); - } - - [DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Unicode)] - private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong); - - [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Unicode)] - private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong); - - [DllImport("user32.dll", SetLastError = true)] - public static extern bool GetWindowRect(IntPtr hwnd, out TsfSharp.Rect lpRect); - - [DllImport("user32", ExactSpelling = true, SetLastError = true)] - public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, ref TsfSharp.Rect rect, [MarshalAs(UnmanagedType.U4)] int cPoints); - - - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, StringBuilder lParam); - - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam); - - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam); - - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref IntPtr lParam); - - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam); - - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); - - [DllImport("user32.dll")] - public static extern bool TranslateMessage(ref NativeMessage lpMsg); - - [DllImport("user32.dll")] - public static extern IntPtr DispatchMessage(ref NativeMessage lpmsg); - - [DllImport("User32.dll", CharSet = CharSet.Unicode)] - public static extern bool PeekMessage(out NativeMessage msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags); - - - [DllImport("ole32.dll", ExactSpelling = true, EntryPoint = "CoCreateInstance", PreserveSig = true)] - public static extern int CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pUnkOuter, int dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); - - } -} diff --git a/Libraries/ImeSharp/Native/NativeMethodsIMM32.cs b/Libraries/ImeSharp/Native/NativeMethodsIMM32.cs deleted file mode 100644 index 701d6aff2..000000000 --- a/Libraries/ImeSharp/Native/NativeMethodsIMM32.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ImeSharp.Native -{ - public partial class NativeMethods - { - #region Constants - - public const int WM_IME_SETCONTEXT = 0x0281; - public const int WM_IME_NOTIFY = 0x0282; - public const int WM_IME_CONTROL = 0x0283; - public const int WM_IME_COMPOSITIONFULL = 0x0284; - public const int WM_IME_SELECT = 0x0285; - public const int WM_IME_CHAR = 0x0286; - public const int WM_IME_REQUEST = 0x0288; - public const int WM_IME_KEYDOWN = 0x0290; - public const int WM_IME_KEYUP = 0x0291; - public const int WM_IME_STARTCOMPOSITION = 0x010D; - public const int WM_IME_ENDCOMPOSITION = 0x010E; - public const int WM_IME_COMPOSITION = 0x010F; - public const int WM_IME_KEYLAST = 0x010F; - - // wParam of report message WM_IME_NOTIFY - public const int IMN_CLOSESTATUSWINDOW = 0x0001; - public const int IMN_OPENSTATUSWINDOW = 0x0002; - public const int IMN_CHANGECANDIDATE = 0x0003; - public const int IMN_CLOSECANDIDATE = 0x0004; - public const int IMN_OPENCANDIDATE = 0x0005; - public const int IMN_SETCONVERSIONMODE = 0x0006; - public const int IMN_SETSENTENCEMODE = 0x0007; - public const int IMN_SETOPENSTATUS = 0x0008; - public const int IMN_SETCANDIDATEPOS = 0x0009; - public const int IMN_SETCOMPOSITIONFONT = 0x000A; - public const int IMN_SETCOMPOSITIONWINDOW = 0x000B; - public const int IMN_SETSTATUSWINDOWPOS = 0x000C; - public const int IMN_GUIDELINE = 0x000D; - public const int IMN_PRIVATE = 0x000E; - - // wParam of report message WM_IME_REQUEST - public const int IMR_COMPOSITIONWINDOW = 0x0001; - public const int IMR_CANDIDATEWINDOW = 0x0002; - public const int IMR_COMPOSITIONFONT = 0x0003; - public const int IMR_RECONVERTSTRING = 0x0004; - public const int IMR_CONFIRMRECONVERTSTRING = 0x0005; - public const int IMR_QUERYCHARPOSITION = 0x0006; - public const int IMR_DOCUMENTFEED = 0x0007; - - // parameter of ImmGetCompositionString - public const int GCS_COMPREADSTR = 0x0001; - public const int GCS_COMPREADATTR = 0x0002; - public const int GCS_COMPREADCLAUSE = 0x0004; - public const int GCS_COMPSTR = 0x0008; - public const int GCS_COMPATTR = 0x0010; - public const int GCS_COMPCLAUSE = 0x0020; - public const int GCS_CURSORPOS = 0x0080; - public const int GCS_DELTASTART = 0x0100; - public const int GCS_RESULTREADSTR = 0x0200; - public const int GCS_RESULTREADCLAUSE = 0x0400; - public const int GCS_RESULTSTR = 0x0800; - public const int GCS_RESULTCLAUSE = 0x1000; - - public const int GCS_COMP = (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE); - public const int GCS_COMPREAD = (GCS_COMPREADSTR | GCS_COMPREADATTR | GCS_COMPREADCLAUSE); - public const int GCS_RESULT = (GCS_RESULTSTR | GCS_RESULTCLAUSE); - public const int GCS_RESULTREAD = (GCS_RESULTREADSTR | GCS_RESULTREADCLAUSE); - - public const int CFS_CANDIDATEPOS = 0x0040; - public const int CFS_POINT = 0x0002; - public const int CFS_EXCLUDE = 0x0080; - - // lParam for WM_IME_SETCONTEXT - public const long ISC_SHOWUICANDIDATEWINDOW = 0x00000001; - public const long ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000; - public const long ISC_SHOWUIGUIDELINE = 0x40000000; - public const long ISC_SHOWUIALLCANDIDATEWINDOW = 0x0000000F; - public const long ISC_SHOWUIALL = 0xC000000F; - - #endregion Constants - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct CANDIDATELIST - { - public uint dwSize; - public uint dwStyle; - public uint dwCount; - public uint dwSelection; - public uint dwPageStart; - public uint dwPageSize; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.U4)] - public fixed uint dwOffset[1]; - } - - // CANDIDATEFORM structures - [StructLayout(LayoutKind.Sequential)] - public struct CANDIDATEFORM - { - public int dwIndex; - public int dwStyle; - public TsfSharp.Point ptCurrentPos; - public TsfSharp.Rect rcArea; - } - - // COMPOSITIONFORM structures - [StructLayout(LayoutKind.Sequential)] - public struct COMPOSITIONFORM - { - public int dwStyle; - public TsfSharp.Point ptCurrentPos; - public TsfSharp.Rect rcArea; - } - - [DllImport("imm32.dll", SetLastError = true)] - public static extern IntPtr ImmCreateContext(); - - [DllImport("imm32.dll", SetLastError = true)] - public static extern bool ImmDestroyContext(IntPtr hIMC); - - [DllImport("imm32.dll", SetLastError = true)] - public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC); - - [DllImport("imm32.dll", SetLastError = true)] - public static extern IntPtr ImmReleaseContext(IntPtr hWnd, IntPtr hIMC); - - [DllImport("imm32.dll", CharSet = CharSet.Unicode)] - public static extern uint ImmGetCandidateList(IntPtr hIMC, uint deIndex, IntPtr candidateList, uint dwBufLen); - - [DllImport("imm32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - public static extern int ImmGetCompositionString(IntPtr hIMC, int CompositionStringFlag, IntPtr buffer, int bufferLength); - - [DllImport("imm32.dll", SetLastError = true)] - public static extern IntPtr ImmGetContext(IntPtr hWnd); - - [DllImport("Imm32.dll", SetLastError = true)] - public static extern bool ImmGetOpenStatus(IntPtr hIMC); - - [DllImport("Imm32.dll", SetLastError = true)] - public static extern bool ImmSetOpenStatus(IntPtr hIMC, bool open); - - [DllImport("imm32.dll", SetLastError = true)] - public static extern bool ImmSetCandidateWindow(IntPtr hIMC, ref CANDIDATEFORM candidateForm); - - [DllImport("imm32.dll", SetLastError = true)] - public static extern int ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM compForm); - - - [DllImport("user32.dll", SetLastError = true)] - public static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight); - - [DllImport("user32.dll", SetLastError = true)] - public static extern bool DestroyCaret(); - - [DllImport("user32.dll", SetLastError = true)] - public static extern bool SetCaretPos(int x, int y); - } -} diff --git a/Libraries/ImeSharp/Native/NativeValues.cs b/Libraries/ImeSharp/Native/NativeValues.cs deleted file mode 100644 index 1c0dd9a36..000000000 --- a/Libraries/ImeSharp/Native/NativeValues.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; - -namespace ImeSharp.Native -{ - /// - /// SystemMetrics. SM_* - /// - public enum SM - { - CXSCREEN = 0, - CYSCREEN = 1, - CXVSCROLL = 2, - CYHSCROLL = 3, - CYCAPTION = 4, - CXBORDER = 5, - CYBORDER = 6, - CXFIXEDFRAME = 7, - CYFIXEDFRAME = 8, - CYVTHUMB = 9, - CXHTHUMB = 10, - CXICON = 11, - CYICON = 12, - CXCURSOR = 13, - CYCURSOR = 14, - CYMENU = 15, - CXFULLSCREEN = 16, - CYFULLSCREEN = 17, - CYKANJIWINDOW = 18, - MOUSEPRESENT = 19, - CYVSCROLL = 20, - CXHSCROLL = 21, - DEBUG = 22, - SWAPBUTTON = 23, - CXMIN = 28, - CYMIN = 29, - CXSIZE = 30, - CYSIZE = 31, - CXFRAME = 32, - CXSIZEFRAME = CXFRAME, - CYFRAME = 33, - CYSIZEFRAME = CYFRAME, - CXMINTRACK = 34, - CYMINTRACK = 35, - CXDOUBLECLK = 36, - CYDOUBLECLK = 37, - CXICONSPACING = 38, - CYICONSPACING = 39, - MENUDROPALIGNMENT = 40, - PENWINDOWS = 41, - DBCSENABLED = 42, - CMOUSEBUTTONS = 43, - SECURE = 44, - CXEDGE = 45, - CYEDGE = 46, - CXMINSPACING = 47, - CYMINSPACING = 48, - CXSMICON = 49, - CYSMICON = 50, - CYSMCAPTION = 51, - CXSMSIZE = 52, - CYSMSIZE = 53, - CXMENUSIZE = 54, - CYMENUSIZE = 55, - ARRANGE = 56, - CXMINIMIZED = 57, - CYMINIMIZED = 58, - CXMAXTRACK = 59, - CYMAXTRACK = 60, - CXMAXIMIZED = 61, - CYMAXIMIZED = 62, - NETWORK = 63, - CLEANBOOT = 67, - CXDRAG = 68, - CYDRAG = 69, - SHOWSOUNDS = 70, - CXMENUCHECK = 71, - CYMENUCHECK = 72, - SLOWMACHINE = 73, - MIDEASTENABLED = 74, - MOUSEWHEELPRESENT = 75, - XVIRTUALSCREEN = 76, - YVIRTUALSCREEN = 77, - CXVIRTUALSCREEN = 78, - CYVIRTUALSCREEN = 79, - CMONITORS = 80, - SAMEDISPLAYFORMAT = 81, - IMMENABLED = 82, - CXFOCUSBORDER = 83, - CYFOCUSBORDER = 84, - TABLETPC = 86, - MEDIACENTER = 87, - REMOTESESSION = 0x1000, - REMOTECONTROL = 0x2001, - } -} diff --git a/Libraries/ImeSharp/SafeSystemMetrics.cs b/Libraries/ImeSharp/SafeSystemMetrics.cs deleted file mode 100644 index a2e76f5b2..000000000 --- a/Libraries/ImeSharp/SafeSystemMetrics.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using ImeSharp.Native; - -namespace ImeSharp -{ - /// - /// Contains properties that are queries into the system's various settings. - /// - internal sealed class SafeSystemMetrics - { - - private SafeSystemMetrics() - { - } - - /// - /// Maps to SM_CXDOUBLECLK - /// - public static int DoubleClickDeltaX - { - get { return NativeMethods.GetSystemMetrics(SM.CXDOUBLECLK); } - } - - /// - /// Maps to SM_CYDOUBLECLK - /// - public static int DoubleClickDeltaY - { - get { return NativeMethods.GetSystemMetrics(SM.CYDOUBLECLK); } - } - - - /// - /// Maps to SM_CXDRAG - /// - public static int DragDeltaX - { - get { return NativeMethods.GetSystemMetrics(SM.CXDRAG); } - } - - /// - /// Maps to SM_CYDRAG - /// - public static int DragDeltaY - { - get { return NativeMethods.GetSystemMetrics(SM.CYDRAG); } - } - - /// - /// Is an IMM enabled ? Maps to SM_IMMENABLED - /// - public static bool IsImmEnabled - { - get { return (NativeMethods.GetSystemMetrics(SM.IMMENABLED) != 0); } - } - - } -} diff --git a/Libraries/ImeSharp/TextInputCallbacks.cs b/Libraries/ImeSharp/TextInputCallbacks.cs deleted file mode 100644 index c37837927..000000000 --- a/Libraries/ImeSharp/TextInputCallbacks.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ImeSharp -{ - public delegate void TextInputCallback(char character); - public delegate void TextCompositionCallback(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection); - public delegate void CommitTextCompositionCallback(string text); -} diff --git a/Libraries/ImeSharp/TextServicesContext.cs b/Libraries/ImeSharp/TextServicesContext.cs deleted file mode 100644 index 6ce846528..000000000 --- a/Libraries/ImeSharp/TextServicesContext.cs +++ /dev/null @@ -1,374 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading; -using System.Diagnostics; -using ImeSharp.Native; -using TsfSharp; - -namespace ImeSharp -{ - //------------------------------------------------------ - // - // TextServicesContext class - // - //------------------------------------------------------ - - /// - /// This class manages the ITfThreadMgr, EmptyDim and the reference to - /// the default TextStore. - /// - /// - /// - internal class TextServicesContext - { - public const int TF_POPF_ALL = 0x0001; - public const int TF_INVALID_COOKIE = -1; - public static readonly Guid IID_ITfUIElementSink = new Guid(0xea1ea136, 0x19df, 0x11d7, 0xa6, 0xd2, 0x00, 0x06, 0x5b, 0x84, 0x43, 0x5c); - public static readonly Guid IID_ITfTextEditSink = new Guid(0x8127d409, 0xccd3, 0x4683, 0x96, 0x7a, 0xb4, 0x3d, 0x5b, 0x48, 0x2b, 0xf7); - - - public static TextServicesContext Current - { - get - { - if (InputMethod.TextServicesContext == null) - InputMethod.TextServicesContext = new TextServicesContext(); - - return InputMethod.TextServicesContext; - } - } - - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - /// - /// Instantiates a TextServicesContext. - /// - private TextServicesContext() - { - if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) - Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!"); - } - - #endregion Constructors - - //------------------------------------------------------ - // - // public Methods - // - //------------------------------------------------------ - - #region public Methods - - /// - /// Releases all unmanaged resources allocated by the - /// TextServicesContext. - /// - /// - /// if appDomainShutdown == false, this method must be called on the - /// Dispatcher thread. Otherwise, the caller is an AppDomain.Shutdown - /// listener, and is calling from a worker thread. - /// - public void Uninitialize(bool appDomainShutdown) - { - // Unregister DefaultTextStore. - if (_defaultTextStore != null) - { - UnadviseSinks(); - if (_defaultTextStore.DocumentManager != null) - { - _defaultTextStore.DocumentManager.Pop(TF_POPF_ALL); - _defaultTextStore.DocumentManager.Dispose(); - _defaultTextStore.DocumentManager = null; - } - - _defaultTextStore = null; - } - - // Free up any remaining textstores. - if (_istimactivated == true) - { - // Shut down the thread manager when the last TextStore goes away. - // On XP, if we're called on a worker thread (during AppDomain shutdown) - // we can't call call any methods on _threadManager. The problem is - // that there's no proxy registered for ITfThreadMgr on OS versions - // previous to Vista. Not calling Deactivate will leak the IMEs, but - // in practice (1) they're singletons, so it's not unbounded; and (2) - // most applications will share the thread with other AppDomains that - // have a UI, in which case the IME won't be released until the process - // shuts down in any case. In theory we could also work around this - // problem by creating our own XP proxy/stub implementation, which would - // be added to WPF setup.... - if (!appDomainShutdown || System.Environment.OSVersion.Version.Major >= 6) - { - _threadManager.Deactivate(); - } - _istimactivated = false; - } - - // Release the empty dim. - if (_dimEmpty != null) - { - if (_dimEmpty != null) - { - _dimEmpty.Dispose(); - } - _dimEmpty = null; - } - - // Release the ThreadManager. - // We don't do this in UnregisterTextStore because someone may have - // called get_ThreadManager after the last TextStore was unregistered. - if (_threadManager != null) - { - if (_threadManager != null) - { - _threadManager.Dispose(); - } - _threadManager = null; - } - } - - // Called by framework's TextStore class. This method registers a - // document with TSF. The TextServicesContext must maintain this list - // to ensure all native resources are released after gc or uninitialization. - public void RegisterTextStore(TextStore defaultTextStore) - { - _defaultTextStore = defaultTextStore; - - ITfThreadMgrEx threadManager = ThreadManager; - - if (threadManager != null) - { - ITfDocumentMgr doc; - int editCookie = TF_INVALID_COOKIE; - - // Activate TSF on this thread if this is the first TextStore. - if (_istimactivated == false) - { - //temp variable created to retrieve the value - // which is then stored in the critical data. - if (InputMethod.ShowOSImeWindow) - _clientId = threadManager.Activate(); - else - _clientId = threadManager.ActivateEx(TfTmaeFlags.Uielementenabledonly); - - _istimactivated = true; - } - - // Create a TSF document. - doc = threadManager.CreateDocumentMgr(); - _defaultTextStore.DocumentManager = doc; - - doc.CreateContext(_clientId, 0 /* flags */, _defaultTextStore, out _editContext, out editCookie); - _defaultTextStore.EditCookie = editCookie; - _contextOwnerServices = _editContext.QueryInterface(); - - doc.Push(_editContext); - - AdviseSinks(); - } - } - - - public void SetFocusOnDefaultTextStore() - { - SetFocusOnDim(TextStore.Current.DocumentManager); - } - - public void SetFocusOnEmptyDim() - { - SetFocusOnDim(EmptyDocumentManager); - } - - - #endregion public Methods - - //------------------------------------------------------ - // - // public Properties - // - //------------------------------------------------------ - - /// - /// The default ITfThreadMgrEx object. - /// - public ITfThreadMgrEx ThreadManager - { - // The ITfThreadMgr for this thread. - get - { - if (_threadManager == null) - { - ITfThreadMgr threadMgr = null; - try - { - // This might fail in CoreRT - threadMgr = Tsf.GetThreadMgr(); - } - catch (SharpGen.Runtime.SharpGenException) - { - threadMgr = null; - } - - // Dispose previous ITfThreadMgr in case something weird happens - if (threadMgr != null) - { - if (threadMgr.IsThreadFocus) - threadMgr.Deactivate(); - threadMgr.Dispose(); - } - - _threadManager = TextServicesLoader.Load(); - - _uiElementMgr = _threadManager.QueryInterface(); - } - - return _threadManager; - } - } - - /// - /// Return the created ITfContext object. - /// - public ITfContext EditContext - { - get { return _editContext; } - } - - /// - /// Return the created ITfUIElementMgr object. - /// - public ITfUIElementMgr UIElementMgr - { - get { return _uiElementMgr; } - } - - /// - /// Return the created ITfContextOwnerServices object. - /// - public ITfContextOwnerServices ContextOwnerServices - { - get { return _contextOwnerServices; } - } - - //------------------------------------------------------ - // - // public Events - // - //------------------------------------------------------ - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - private void SetFocusOnDim(ITfDocumentMgr dim) - { - ITfThreadMgrEx threadmgr = ThreadManager; - - if (threadmgr != null) - { - ITfDocumentMgr prevDocMgr = threadmgr.AssociateFocus(InputMethod.WindowHandle, dim); - } - } - - private void AdviseSinks() - { - var source = _uiElementMgr.QueryInterface(); - var guid = IID_ITfUIElementSink; - int sinkCookie = source.AdviseSink(guid, _defaultTextStore); - _defaultTextStore.UIElementSinkCookie = sinkCookie; - source.Dispose(); - - source = _editContext.QueryInterface(); - guid = IID_ITfTextEditSink; - sinkCookie = source.AdviseSink(guid, _defaultTextStore); - _defaultTextStore.TextEditSinkCookie = sinkCookie; - source.Dispose(); - } - - private void UnadviseSinks() - { - var source = _uiElementMgr.QueryInterface(); - - if (_defaultTextStore.UIElementSinkCookie != TF_INVALID_COOKIE) - { - source.UnadviseSink(_defaultTextStore.UIElementSinkCookie); - _defaultTextStore.UIElementSinkCookie = TF_INVALID_COOKIE; - } - source.Dispose(); - - source = _editContext.QueryInterface(); - if (_defaultTextStore.TextEditSinkCookie != TF_INVALID_COOKIE) - { - source.UnadviseSink(_defaultTextStore.TextEditSinkCookie); - _defaultTextStore.TextEditSinkCookie = TF_INVALID_COOKIE; - } - source.Dispose(); - } - - //------------------------------------------------------ - // - // Private Properties - // - //------------------------------------------------------ - - // Create an empty dim on demand. - private ITfDocumentMgr EmptyDocumentManager - { - get - { - if (_dimEmpty == null) - { - ITfThreadMgrEx threadManager = ThreadManager; - if (threadManager == null) - { - return null; - } - - ITfDocumentMgr dimEmptyTemp; - // Create a TSF document. - dimEmptyTemp = threadManager.CreateDocumentMgr(); - _dimEmpty = dimEmptyTemp; - } - return _dimEmpty; - } - } - - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields - - private TextStore _defaultTextStore; - - private ITfContext _editContext; - private ITfUIElementMgr _uiElementMgr; - private ITfContextOwnerServices _contextOwnerServices; - - // This is true if thread manager is activated. - private bool _istimactivated; - - // The root TSF object, created on demand. - private ITfThreadMgrEx _threadManager; - - // TSF ClientId from Activate call. - private int _clientId; - - // The empty dim for this thread. Created on demand. - private ITfDocumentMgr _dimEmpty; - - #endregion Private Fields - } -} diff --git a/Libraries/ImeSharp/TextServicesLoader.cs b/Libraries/ImeSharp/TextServicesLoader.cs deleted file mode 100644 index ff2037b57..000000000 --- a/Libraries/ImeSharp/TextServicesLoader.cs +++ /dev/null @@ -1,338 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Diagnostics; -using System.Threading; -using Microsoft.Win32; -using ImeSharp.Native; -using TsfSharp; - -namespace ImeSharp -{ - // Creates ITfThreadMgr instances, the root object of the Text Services - // Framework. - internal class TextServicesLoader - { - public static readonly Guid CLSID_TF_ThreadMgr = new Guid("529a9e6b-6587-4f23-ab9e-9c7d683e3c50"); - public static readonly Guid IID_ITfThreadMgr = new Guid("aa80e801-2021-11d2-93e0-0060b067b86e"); - public static readonly Guid IID_ITfThreadMgrEx = new Guid("3e90ade3-7594-4cb0-bb58-69628f5f458c"); - public static readonly Guid IID_ITfThreadMgr2 = new Guid("0AB198EF-6477-4EE8-8812-6780EDB82D5E"); - - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - // Private ctor to prevent anyone from instantiating this static class. - private TextServicesLoader() { } - - #endregion Constructors - - //------------------------------------------------------ - // - // public Properties - // - //------------------------------------------------------ - - #region public Properties - - /// - /// Loads an instance of the Text Services Framework. - /// - /// - /// May return null if no text services are available. - /// - public static ITfThreadMgrEx Load() - { - if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) - Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!"); - - if (ServicesInstalled) - { - // NB: a COMException here means something went wrong initialzing Cicero. - // Cicero will throw an exception if it doesn't think it should have been - // loaded (no TIPs to run), you can check that in msctf.dll's NoTipsInstalled - // which lives in nt\windows\advcore\ctf\lib\immxutil.cpp. If that's the - // problem, ServicesInstalled is out of sync with Cicero's thinking. - IntPtr ret; - var hr = NativeMethods.CoCreateInstance(CLSID_TF_ThreadMgr, - IntPtr.Zero, - NativeMethods.CLSCTX_INPROC_SERVER, - IID_ITfThreadMgrEx, out ret); - - if (hr == NativeMethods.S_OK) - return new ITfThreadMgrEx(ret); - } - - return null; - } - - /// - /// return true if current OS version is Windows 7 or below. - /// - public static bool IsWindows7OrBelow() - { - if (Environment.OSVersion.Version.Major <= 5) - return true; - - if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor <= 1) - return true; - - return false; - } - - /// - /// Informs the caller if text services are installed for the current user. - /// - /// - /// true if one or more text services are installed for the current user, otherwise false. - /// - /// - /// If this method returns false, TextServicesLoader.Load is guarenteed to return null. - /// Callers can use this information to avoid overhead that would otherwise be - /// required to support text services. - /// - public static bool ServicesInstalled - { - get - { - lock (s_servicesInstalledLock) - { - if (s_servicesInstalled == InstallState.Unknown) - { - s_servicesInstalled = TIPsWantToRun() ? InstallState.Installed : InstallState.NotInstalled; - } - } - - return (s_servicesInstalled == InstallState.Installed); - } - } - - #endregion public Properties - - //------------------------------------------------------ - // - // public Events - // - //------------------------------------------------------ - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - // - // This method tries to stop Avalon from loading Cicero when there are no TIPs to run. - // The perf tradeoff is a typically small number of registry checks versus loading and - // initializing cicero. - // - // The Algorithm: - // - // Do a quick check vs. the global disable flag, return false if it is set. - // For each key under HKLM\SOFTWARE\Microsoft\CTF\TIP (a TIP or category clsid) - // If the the key has a LanguageProfile subkey (it's a TIP clsid) - // Iterate under the matching TIP entry in HKCU. - // For each key under the LanguageProfile (a particular LANGID) - // For each key under the LANGID (an assembly GUID) - // Try to read the Enable value. - // If the value is set non-zero, then stop all processing and return true. - // If the value is set zero, continue. - // If the value does not exist, continue (default is disabled). - // If any Enable values were found under HKCU for the TIP, then stop all processing and return false. - // Else, no Enable values have been found thus far and we keep going to investigate HKLM. - // Iterate under the TIP entry in HKLM. - // For each key under the LanguageProfile (a particular LANGID) - // For each key under the LANGID (an assembly GUID) - // Try to read the Enable value. - // If the value is set non-zero, then stop all processing and return true. - // If the value does not exist, then stop all processing and return true (default is enabled). - // If the value is set zero, continue. - // If we finish iterating all entries under HKLM without returning true, return false. - // - - private static bool TIPsWantToRun() - { - object obj; - RegistryKey key; - bool tipsWantToRun = false; - - key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\CTF", false); - - // Is cicero disabled completely for the current user? - if (key != null) - { - obj = key.GetValue("Disable Thread Input Manager"); - - if (obj is int && (int)obj != 0) - return false; - } - - // Loop through all the TIP entries for machine and current user. - tipsWantToRun = IterateSubKeys(Registry.LocalMachine, "SOFTWARE\\Microsoft\\CTF\\TIP", new IterateHandler(SingleTIPWantsToRun), true) == EnableState.Enabled; - - return tipsWantToRun; - } - - // Returns EnableState.Enabled if one or more TIPs are installed and - // enabled for the current user. - private static EnableState SingleTIPWantsToRun(RegistryKey keyLocalMachine, string subKeyName, bool localMachine) - { - EnableState result; - - if (subKeyName.Length != CLSIDLength) - return EnableState.Disabled; - - // We want subkey\LanguageProfile key. - // Loop through all the langid entries for TIP. - - // First, check current user. - result = IterateSubKeys(Registry.CurrentUser, "SOFTWARE\\Microsoft\\CTF\\TIP\\" + subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), false); - - // Any explicit value short circuits the process. - // Otherwise check local machine. - if (result == EnableState.None || result == EnableState.Error) - { - result = IterateSubKeys(keyLocalMachine, subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), true); - - if (result == EnableState.None) - { - result = EnableState.Enabled; - } - } - - return result; - } - - // Returns EnableState.Enabled if the supplied subkey is a valid LANGID key with enabled - // cicero assembly. - private static EnableState IsLangidEnabled(RegistryKey key, string subKeyName, bool localMachine) - { - if (subKeyName.Length != LANGIDLength) - return EnableState.Error; - - // Loop through all the assembly entries for the langid - return IterateSubKeys(key, subKeyName, new IterateHandler(IsAssemblyEnabled), localMachine); - } - - // Returns EnableState.Enabled if the supplied assembly key is enabled. - private static EnableState IsAssemblyEnabled(RegistryKey key, string subKeyName, bool localMachine) - { - RegistryKey subKey; - object obj; - - if (subKeyName.Length != CLSIDLength) - return EnableState.Error; - - // Open the local machine assembly key. - subKey = key.OpenSubKey(subKeyName); - - if (subKey == null) - return EnableState.Error; - - // Try to read the "Enable" value. - obj = subKey.GetValue("Enable"); - - if (obj is int) - { - return ((int)obj == 0) ? EnableState.Disabled : EnableState.Enabled; - } - - return EnableState.None; - } - - // Calls the supplied delegate on each of the children of keyBase. - private static EnableState IterateSubKeys(RegistryKey keyBase, string subKey, IterateHandler handler, bool localMachine) - { - RegistryKey key; - string[] subKeyNames; - EnableState state; - - key = keyBase.OpenSubKey(subKey, false); - - if (key == null) - return EnableState.Error; - - subKeyNames = key.GetSubKeyNames(); - state = EnableState.Error; - - foreach (string name in subKeyNames) - { - switch (handler(key, name, localMachine)) - { - case EnableState.Error: - break; - case EnableState.None: - if (localMachine) // For lm, want to return here right away. - return EnableState.None; - - // For current user, remember that we found no Enable value. - if (state == EnableState.Error) - { - state = EnableState.None; - } - break; - case EnableState.Disabled: - state = EnableState.Disabled; - break; - case EnableState.Enabled: - return EnableState.Enabled; - } - } - - return state; - } - - #endregion Private Methods - - //------------------------------------------------------ - // - // Private Properties - // - //------------------------------------------------------ - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields - - // String consts used to validate registry entires. - private const int CLSIDLength = 38; // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} - private const int LANGIDLength = 10; // 0x12345678 - - // Status of a TIP assembly. - private enum EnableState - { - Error, // Invalid entry. - None, // No explicit Enable entry on the assembly. - Enabled, // Assembly is enabled. - Disabled // Assembly is disabled. - }; - - // Callback delegate for the IterateSubKeys method. - private delegate EnableState IterateHandler(RegistryKey key, string subKeyName, bool localMachine); - - // Install state. - private enum InstallState - { - Unknown, // Haven't checked to see if any TIPs are installed yet. - Installed, // Checked and installed. - NotInstalled // Checked and not installed. - } - - // Cached install state value. - // Writes are not thread safe, but we don't mind the neglible perf hit - // of potentially writing it twice. - private static InstallState s_servicesInstalled = InstallState.Unknown; - private static object s_servicesInstalledLock = new object(); - - #endregion Private Fields - } -} diff --git a/Libraries/ImeSharp/TextStore.cs b/Libraries/ImeSharp/TextStore.cs deleted file mode 100644 index b7bf9cf87..000000000 --- a/Libraries/ImeSharp/TextStore.cs +++ /dev/null @@ -1,963 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using ImeSharp.Native; -using SharpGen.Runtime; -using SharpGen.Runtime.Win32; -using TsfSharp; - -namespace ImeSharp -{ - internal class TextStore : CallbackBase, - ITextStoreACP, - ITfContextOwnerCompositionSink, - ITfTextEditSink, - ITfUIElementSink - { - public static readonly Guid IID_ITextStoreACPSink = new Guid(0x22d44c94, 0xa419, 0x4542, 0xa2, 0x72, 0xae, 0x26, 0x09, 0x3e, 0xce, 0xcf); - public static readonly Guid GUID_PROP_COMPOSING = new Guid("e12ac060-af15-11d2-afc5-00105a2799b5"); - - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - // Creates a TextStore instance. - public TextStore(IntPtr windowHandle) - { - _windowHandle = windowHandle; - - - _viewCookie = Environment.TickCount; - - _editCookie = Tsf.TF_INVALID_COOKIE; - _uiElementSinkCookie = Tsf.TF_INVALID_COOKIE; - _textEditSinkCookie = Tsf.TF_INVALID_COOKIE; - - _IMEStringPool = ArrayPool.Shared; - } - - #endregion Constructors - - //------------------------------------------------------ - // - // Methods - ITextStoreACP - // - //------------------------------------------------------ - - #region ITextStoreACP - - public void AdviseSink(Guid riid, IUnknown obj, int flags) - { - ITextStoreACPSink sink; - - if (riid != IID_ITextStoreACPSink) - throw new COMException("TextStore_CONNECT_E_CANNOTCONNECT"); - - sink = (obj as ComObject).QueryInterface(); - - if (sink == null) - throw new COMException("TextStore_E_NOINTERFACE"); - - // It's legal to replace existing sink. - if (_sink != null) - _sink.Dispose(); - - (obj as ComObject).Dispose(); - - _sink = sink; - } - - public void UnadviseSink(IUnknown obj) - { - var sink = (obj as ComObject).QueryInterface(); - if (sink.NativePointer != _sink.NativePointer) - throw new COMException("TextStore_CONNECT_E_NOCONNECTION"); - - _sink.Release(); - _sink = null; - } - - private bool _LockDocument(TsfSharp.TsLfFlags dwLockFlags) - { - if (_locked) - return false; - - _locked = true; - _lockFlags = dwLockFlags; - - return true; - } - - private void ResetIfRequired() - { - if (!_commited) - return; - - _commited = false; - - TsTextchange textChange; - textChange.AcpStart = 0; - textChange.AcpOldEnd = _inputBuffer.Count; - textChange.AcpNewEnd = 0; - _inputBuffer.Clear(); - - _sink.OnTextChange(0, textChange); - - _acpStart = _acpEnd = 0; - _sink.OnSelectionChange(); - _commitStart = _commitLength = 0; - - //Debug.WriteLine("TextStore reset!!!"); - } - - private void _UnlockDocument() - { - Result hr; - _locked = false; - _lockFlags = 0; - - ResetIfRequired(); - - //if there is a queued lock, grant it - if (_lockRequestQueue.Count > 0) - { - hr = RequestLock(_lockRequestQueue.Dequeue()); - } - - //if any layout changes occurred during the lock, notify the manager - if (_layoutChanged) - { - _layoutChanged = false; - _sink.OnLayoutChange(TsLayoutCode.TsLcChange, _viewCookie); - } - } - - private bool _IsLocked(TsfSharp.TsLfFlags dwLockType) - { - return _locked && (_lockFlags & dwLockType) != 0; - } - - public Result RequestLock(TsfSharp.TsLfFlags dwLockFlags) - { - Result hrSession; - - if (_sink == null) - throw new COMException("TextStore_NoSink"); - - if (dwLockFlags == 0) - throw new COMException("TextStore_BadLockFlags"); - - hrSession = Result.Fail; - - if (_locked) - { - //the document is locked - - if ((dwLockFlags & TsfSharp.TsLfFlags.Sync) == TsfSharp.TsLfFlags.Sync) - { - /* - The caller wants an immediate lock, but this cannot be granted because - the document is already locked. - */ - hrSession = (int)TsErrors.TsESynchronous; - } - else - { - //the request is asynchronous - - //Queue the lock request - _lockRequestQueue.Enqueue(dwLockFlags); - hrSession = (int)TsErrors.TsSAsync; - } - - return hrSession; - } - - //lock the document - _LockDocument(dwLockFlags); - - //call OnLockGranted - hrSession = _sink.OnLockGranted(dwLockFlags); - - //unlock the document - _UnlockDocument(); - - return hrSession; - } - - public TsStatus GetStatus() - { - TsStatus status = new TsStatus(); - status.DynamicFlags = 0; - status.StaticFlags = 0; - - return status; - } - - public void QueryInsert(int acpTestStart, int acpTestEnd, uint cch, out int acpResultStart, out int acpResultEnd) - { - acpResultStart = acpResultEnd = 0; - - // Fix possible crash - if (_inputBuffer.Count == 0) - return; - - //Queryins - if (acpTestStart > _inputBuffer.Count || acpTestEnd > _inputBuffer.Count) - throw new COMException("", Result.InvalidArg.Code); - - //Microsoft Pinyin seems does not init the result value, so we set the test value here, in case crash - acpResultStart = acpTestStart; - acpResultEnd = acpTestEnd; - } - - public uint GetSelection(uint index, ref TsSelectionAcp selection) - { - //does the caller have a lock - if (!_IsLocked(TsLfFlags.Read)) - { - //the caller doesn't have a lock - //return NativeMethods.TS_E_NOLOCK; - throw new COMException("", (int)TsErrors.TsENolock); - } - - //check the requested index - if (-1 == (int)index) - { - index = 0; - } - else if (index > 1) - { - /* - The index is too high. This app only supports one selection. - */ - throw new COMException("", Result.InvalidArg.Code); - } - - selection.AcpStart = _acpStart; - selection.AcpEnd = _acpEnd; - selection.Style.InterimCharFlag = _interimChar; - - if (_interimChar) - { - /* - fInterimChar will be set when an intermediate character has been - set. One example of when this will happen is when an IME is being - used to enter characters and a character has been set, but the IME - is still active. - */ - selection.Style.Ase = TsActiveSelEnd.TsAeNone; - } - else - { - selection.Style.Ase = _activeSelectionEnd; - } - - return 1; - } - - public void SetSelection(uint count, ref TsSelectionAcp selections) - { - //this implementaiton only supports a single selection - if (count != 1) - throw new COMException("", Result.InvalidArg.Code); - - //does the caller have a lock - if (!_IsLocked(TsLfFlags.Readwrite)) - { - //the caller doesn't have a lock - //return NativeMethods.TS_E_NOLOCK; - throw new COMException("", (int)TsErrors.TsENolock); - } - - _acpStart = selections.AcpStart; - _acpEnd = selections.AcpEnd; - _interimChar = selections.Style.InterimCharFlag; - - if (_interimChar) - { - /* - fInterimChar will be set when an intermediate character has been - set. One example of when this will happen is when an IME is being - used to enter characters and a character has been set, but the IME - is still active. - */ - _activeSelectionEnd = TsActiveSelEnd.TsAeNone; - } - else - { - _activeSelectionEnd = selections.Style.Ase; - } - - //if the selection end is at the start of the selection, reverse the parameters - int lStart = _acpStart; - int lEnd = _acpEnd; - - if (TsActiveSelEnd.TsAeStart == _activeSelectionEnd) - { - lStart = _acpEnd; - lEnd = _acpStart; - } - } - - - public void GetText(int acpStart, int acpEnd, System.IntPtr pchPlain, uint cchPlainReq, out uint cchPlainRet, - ref TsfSharp.TsRuninfo rgRunInfo, uint cRunInfoReq, out uint cRunInfoRet, out int acpNext) - { - cchPlainRet = 0; - cRunInfoRet = 0; - acpNext = 0; - - //does the caller have a lock - if (!_IsLocked(TsLfFlags.Read)) - { - //the caller doesn't have a lock - throw new COMException("", (int)TsErrors.TsENolock); - } - - bool fDoText = cchPlainReq > 0; - bool fDoRunInfo = cRunInfoReq > 0; - int cchTotal; - - cchPlainRet = 0; - acpNext = acpStart; - - cchTotal = _inputBuffer.Count; - - //validate the start pos - if ((acpStart < 0) || (acpStart > cchTotal)) - { - throw new COMException("", Result.InvalidArg.Code); - } - else - { - //are we at the end of the document - if (acpStart == cchTotal) - { - return; - } - else - { - int cchReq; - - /* - acpEnd will be -1 if all of the text up to the end is being requested. - */ - - if (acpEnd >= acpStart) - { - cchReq = acpEnd - acpStart; - } - else - { - cchReq = cchTotal - acpStart; - } - - if (fDoText) - { - if (cchReq > cchPlainReq) - { - cchReq = (int)cchPlainReq; - } - - //extract the specified text range - if (pchPlain != IntPtr.Zero && cchPlainReq > 0) - { - //_inputBuffer.CopyTo(acpStart, pchPlain, 0, cchReq); - - unsafe - { - var ptr = (char*)pchPlain; - - for (int i = acpStart; i < cchReq; i++) - { - *ptr = _inputBuffer[i]; - ptr++; - } - } - } - } - - //it is possible that only the length of the text is being requested - cchPlainRet = (uint)cchReq; - - if (fDoRunInfo) - { - /* - Runs are used to separate text characters from formatting characters. - - In this example, sequences inside and including the <> are treated as - control sequences and are not displayed. - - Plain text = "Text formatting." - Actual text = "Text formatting." - - If all of this text were requested, the run sequence would look like this: - - prgRunInfo[0].type = TS_RT_PLAIN; //"Text " - prgRunInfo[0].uCount = 5; - - prgRunInfo[1].type = TS_RT_HIDDEN; // - prgRunInfo[1].uCount = 6; - - prgRunInfo[2].type = TS_RT_PLAIN; //"formatting" - prgRunInfo[2].uCount = 10; - - prgRunInfo[3].type = TS_RT_HIDDEN; // - prgRunInfo[3].uCount = 8; - - prgRunInfo[4].type = TS_RT_PLAIN; //"." - prgRunInfo[4].uCount = 1; - - TS_RT_OPAQUE is used to indicate characters or character sequences - that are in the document, but are used privately by the application - and do not map to text. Runs of text tagged with TS_RT_OPAQUE should - NOT be included in the pchPlain or cchPlainOut [out] parameters. - */ - - /* - This implementation is plain text, so the text only consists of one run. - If there were multiple runs, it would be an error to have consecuative runs - of the same type. - */ - rgRunInfo.Type = TsRunType.TsRtPlain; - rgRunInfo.Count = (uint)cchReq; - } - - acpNext = acpStart + cchReq; - } - } - } - - public TsTextchange SetText(int dwFlags, int acpStart, int acpEnd, string pchText, uint cch) - { - /* - dwFlags can be: - TS_ST_CORRECTION - */ - TsTextchange change = new TsTextchange(); - - //set the selection to the specified range - TsSelectionAcp tsa = new TsSelectionAcp(); - tsa.AcpStart = acpStart; - tsa.AcpEnd = acpEnd; - tsa.Style.Ase = TsActiveSelEnd.TsAeStart; - tsa.Style.InterimCharFlag = false; - - SetSelection(1, ref tsa); - - int start, end; - InsertTextAtSelection(TsIasFlags.Noquery, pchText, cch, out start, out end, out change); - - return change; - } - - public IDataObject GetFormattedText(int startIndex, int endIndex) - { - throw new COMException("", Result.NotImplemented.Code); - } - - public IUnknown GetEmbedded(int index, Guid guidService, Guid riid) - { - throw new COMException("", Result.NotImplemented.Code); - } - - public RawBool QueryInsertEmbedded(Guid guidService, ref Formatetc formatEtc) - { - throw new COMException("", Result.NotImplemented.Code); - } - - public TsTextchange InsertEmbedded(int flags, int startIndex, int endIndex, TsfSharp.IDataObject dataObjectRef) - { - throw new COMException("", Result.NotImplemented.Code); - } - - public void InsertTextAtSelection(TsfSharp.TsIasFlags dwFlags, string pchText, uint cch, out int pacpStart, out int pacpEnd, out TsfSharp.TsTextchange pChange) - { - pacpStart = pacpEnd = 0; - pChange = new TsTextchange(); - - //does the caller have a lock - if (!_IsLocked(TsLfFlags.Readwrite)) - { - //the caller doesn't have a lock - throw new COMException("", (int)TsErrors.TsENolock); - } - - int acpStart; - int acpOldEnd; - int acpNewEnd; - - acpOldEnd = _acpEnd; - - //set the start point after the insertion - acpStart = _acpStart; - - //set the end point after the insertion - acpNewEnd = _acpStart + (int)cch; - - if ((dwFlags & TsIasFlags.Queryonly) == TsIasFlags.Queryonly) - { - pacpStart = acpStart; - pacpEnd = acpOldEnd; - return; - } - - //insert the text - _inputBuffer.RemoveRange(acpStart, acpOldEnd - acpStart); - _inputBuffer.InsertRange(acpStart, pchText); - - //set the selection - _acpStart = acpStart; - _acpEnd = acpNewEnd; - - if ((dwFlags & TsIasFlags.Noquery) != TsIasFlags.Noquery) - { - pacpStart = acpStart; - pacpEnd = acpNewEnd; - } - - //set the TS_TEXTCHANGE members - pChange.AcpStart = acpStart; - pChange.AcpOldEnd = acpOldEnd; - pChange.AcpNewEnd = acpNewEnd; - - //defer the layout change notification until the document is unlocked - _layoutChanged = true; - } - - public void InsertEmbeddedAtSelection(int flags, IDataObject obj, out int startIndex, out int endIndex, out TsTextchange change) - { - startIndex = endIndex = 0; - change = new TsTextchange(); - throw new COMException("", Result.NotImplemented.Code); - } - - public void RequestSupportedAttrs(int flags, uint cFilterAttrs, ref Guid filterAttributes) - { - } - - public void RequestAttrsAtPosition(int index, uint cFilterAttrs, ref Guid filterAttributes, int flags) - { - throw new COMException("", Result.NotImplemented.Code); - } - - - public void RequestAttrsTransitioningAtPosition(int position, uint cFilterAttrs, ref Guid filterAttributes, int flags) - { - throw new COMException("", Result.NotImplemented.Code); - } - - public void FindNextAttrTransition(int startIndex, int haltIndex, uint cFilterAttrs, ref Guid filterAttributes, int flags, out int acpNext, out RawBool found, out int foundOffset) - { - acpNext = 0; - found = false; - foundOffset = 0; - } - - public uint RetrieveRequestedAttrs(uint ulCount, ref TsfSharp.TsAttrval aAttrValsRef) - { - return 0; - } - - public int GetEndACP() - { - int acp = 0; - //does the caller have a lock - if (!_IsLocked(TsLfFlags.Read)) - { - //the caller doesn't have a lock - throw new COMException("", (int)TsErrors.TsENolock); - } - - acp = _inputBuffer.Count; - - return acp; - } - - public int GetActiveView() - { - return _viewCookie; - } - - public int GetACPFromPoint(int viewCookie, TsfSharp.Point tsfPoint, int dwFlags) - { - throw new COMException("", Result.NotImplemented.Code); - } - - public void GetTextExt(int viewCookie, int acpStart, int acpEnd, out Rect rect, out RawBool clipped) - { - clipped = false; - rect = InputMethod.TextInputRect; - - if (_viewCookie != viewCookie) - throw new COMException("", Result.InvalidArg.Code); - - //does the caller have a lock - if (!_IsLocked(TsLfFlags.Read)) - { - //the caller doesn't have a lock - throw new COMException("", (int)TsErrors.TsENolock); - } - - //According to Microsoft's doc, an ime should not make empty request, - //but some ime draw comp text themseleves, when empty req will be make - //Check empty request - //if (acpStart == acpEnd) { - // return E_INVALIDARG; - //} - - NativeMethods.MapWindowPoints(_windowHandle, IntPtr.Zero, ref rect, 2); - } - - public Rect GetScreenExt(int viewCookie) - { - Rect rect = new Rect(); - - if (_viewCookie != viewCookie) - throw new COMException("", Result.InvalidArg.Code); - - NativeMethods.GetWindowRect(_windowHandle, out rect); - - return rect; - } - - public IntPtr GetWnd(int viewCookie) - { - if (viewCookie != _viewCookie) - { - throw new COMException("", Result.False.Code); - } - - return _windowHandle; - } - - #endregion ITextStoreACP2 - - - //------------------------------------------------------ - // - // Public Methods - ITfContextOwnerCompositionSink - // - //------------------------------------------------------ - - #region ITfContextOwnerCompositionSink - - public RawBool OnStartComposition(ITfCompositionView view) - { - // Return true in ok to start the composition. - RawBool ok = true; - _compositionStart = _compositionLength = 0; - _currentComposition.Clear(); - - InputMethod.OnTextCompositionStarted(this); - _compViews.Add(view); - - return ok; - } - - public void OnUpdateComposition(ITfCompositionView view, ITfRange rangeNew) - { - var range = view.Range; - var rangeacp = range.QueryInterface(); - - rangeacp.GetExtent(out _compositionStart, out _compositionLength); - rangeacp.Dispose(); - range.Dispose(); - _compViews.Add(view); - } - - public void OnEndComposition(ITfCompositionView view) - { - var range = view.Range; - var rangeacp = range.QueryInterface(); - - rangeacp.GetExtent(out _commitStart, out _commitLength); - rangeacp.Dispose(); - range.Dispose(); - - // Ensure composition string reset - _compositionStart = _compositionLength = 0; - _currentComposition.Clear(); - - InputMethod.ClearCandidates(); - InputMethod.OnTextCompositionEnded(this); - view.Dispose(); - foreach(var item in _compViews) - item.Dispose(); - _compViews.Clear(); - } - - #endregion ITfContextOwnerCompositionSink - - #region ITfTextEditSink - - public void OnEndEdit(ITfContext context, int ecReadOnly, ITfEditRecord editRecord) - { - ITfProperty property = context.GetProperty(GUID_PROP_COMPOSING); - - ITfRangeACP rangeACP = TextServicesContext.Current.ContextOwnerServices.CreateRange(_compositionStart, _compositionStart + _compositionLength); - Variant val = property.GetValue(ecReadOnly, rangeACP); - property.Dispose(); - rangeACP.Dispose(); - if (val.Value == null || (int)val.Value == 0) - { - if (_commitLength == 0 || _inputBuffer.Count == 0) - return; - - //Debug.WriteLine("Composition result: {0}", new object[] { new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray()) }); - - _commited = true; - for (int i = 0; i < _commitLength; i++) - InputMethod.OnTextCompositionResult(this, new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray())); - } - - if (_commited) - return; - - if (_inputBuffer.Count == 0 && _compositionLength > 0) // Composition just ended - return; - - _currentComposition.Clear(); - for (int i = 0; i < _compositionLength; i++) - _currentComposition.Add(_inputBuffer[_compositionStart + i]); - - InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd); - - //var compStr = new string(_currentComposition.ToArray()); - //compStr = compStr.Insert(_acpEnd, "|"); - //Debug.WriteLine("Composition string: {0}, cursor pos: {1}", compStr, _acpEnd); - } - - #endregion ITfTextEditSink - - //------------------------------------------------------ - // - // Public Methods - ITfUIElementSink - // - //------------------------------------------------------ - - #region ITfUIElementSink - - public RawBool BeginUIElement(int dwUIElementId) - { - // Hide OS rendered Candidate list Window - RawBool pbShow = InputMethod.ShowOSImeWindow; - - OnUIElement(dwUIElementId, true); - - return pbShow; - } - - public void UpdateUIElement(int dwUIElementId) - { - OnUIElement(dwUIElementId, false); - } - - public void EndUIElement(int dwUIElementId) - { - } - - private void OnUIElement(int uiElementId, bool onStart) - { - if (InputMethod.ShowOSImeWindow || !_supportUIElement) return; - - ITfUIElement uiElement = TextServicesContext.Current.UIElementMgr.GetUIElement(uiElementId); - - ITfCandidateListUIElementBehavior candList; - - try - { - candList = uiElement.QueryInterface(); - } - catch (SharpGenException) - { - _supportUIElement = false; - return; - } - finally - { - uiElement.Dispose(); - } - - uint selection = 0; - uint currentPage = 0; - uint count = 0; - uint pageCount = 0; - uint pageStart = 0; - uint pageSize = 0; - uint i, j; - - selection = candList.GetSelection(); - currentPage = candList.GetCurrentPage(); - - count = candList.GetCount(); - - pageCount = candList.GetPageIndex(null, 0); - - if (pageCount > 0) - { - uint[] pageStartIndexes = ArrayPool.Shared.Rent((int)pageCount); - pageCount = candList.GetPageIndex(pageStartIndexes, pageCount); - pageStart = pageStartIndexes[currentPage]; - - if (pageStart >= count - 1) - { - candList.Abort(); - ArrayPool.Shared.Return(pageStartIndexes); - return; - } - - if (currentPage < pageCount - 1) - pageSize = Math.Min(count, pageStartIndexes[currentPage + 1]) - pageStart; - else - pageSize = count - pageStart; - - ArrayPool.Shared.Return(pageStartIndexes); - } - - selection -= pageStart; - - IMEString[] candidates = _IMEStringPool.Rent((int)pageSize); - - IntPtr bStrPtr; - for (i = pageStart, j = 0; i < count && j < pageSize; i++, j++) - { - bStrPtr = candList.GetString(i); - candidates[j] = new IMEString(bStrPtr); - } - - //Debug.WriteLine("TSF========TSF"); - //Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, currentPage: {3} candidates:", pageStart, pageSize, selection, currentPage); - //for (int k = 0; k < candidates.Length; k++) - // Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : ""); - //Debug.WriteLine("TSF++++++++TSF"); - - InputMethod.CandidatePageStart = (int)pageStart; - InputMethod.CandidatePageSize = (int)pageSize; - InputMethod.CandidateSelection = (int)selection; - InputMethod.CandidateList = candidates; - - if (_currentComposition != null) - { - InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd); - _IMEStringPool.Return(candidates); - } - - candList.Dispose(); - } - - #endregion ITfUIElementSink - - //------------------------------------------------------ - // - // Public Properties - // - //------------------------------------------------------ - - public static TextStore Current - { - get - { - TextStore defaultTextStore = InputMethod.DefaultTextStore; - if (defaultTextStore == null) - { - defaultTextStore = InputMethod.DefaultTextStore = new TextStore(InputMethod.WindowHandle); - - defaultTextStore.Register(); - } - - return defaultTextStore; - } - } - - public ITfDocumentMgr DocumentManager - { - get { return _documentMgr; } - set { _documentMgr = value; } - } - - // EditCookie for ITfContext. - public int EditCookie - { - // get { return _editCookie; } - set { _editCookie = value; } - } - - public int UIElementSinkCookie - { - get { return _uiElementSinkCookie; } - set { _uiElementSinkCookie = value; } - } - - public int TextEditSinkCookie - { - get { return _textEditSinkCookie; } - set { _textEditSinkCookie = value; } - } - - public bool SupportUIElement { get { return _supportUIElement; } } - - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - // This function calls TextServicesContext to create TSF document and start transitory extension. - private void Register() - { - // Create TSF document and advise the sink to it. - TextServicesContext.Current.RegisterTextStore(this); - } - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - // The TSF document object. This is a native resource. - private ITfDocumentMgr _documentMgr; - - private int _viewCookie; - - // The edit cookie TSF returns from CreateContext. - private int _editCookie; - private int _uiElementSinkCookie; - private int _textEditSinkCookie; - - private ITextStoreACPSink _sink; - private IntPtr _windowHandle; - private int _acpStart; - private int _acpEnd; - private bool _interimChar; - private TsActiveSelEnd _activeSelectionEnd; - private List _inputBuffer = new List(); - - private bool _locked; - private TsLfFlags _lockFlags; - private Queue _lockRequestQueue = new Queue(); - private bool _layoutChanged; - - private List _currentComposition = new List(); - private int _compositionStart; - private int _compositionLength; - private int _commitStart; - private int _commitLength; - private bool _commited; - - private bool _supportUIElement = true; - private List _compViews = new List(); - - private ArrayPool _IMEStringPool; - - } -} diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/GameWindow.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/GameWindow.cs index 8b166be16..3e73f5980 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/GameWindow.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/GameWindow.cs @@ -111,6 +111,11 @@ namespace Microsoft.Xna.Framework { /// Used for displaying uncommitted IME text. /// public event EventHandler TextEditing; + + /// + /// Used for when a key is pressed, including modifiers. + /// + public event EventHandler KeyDown; #endif #endregion Events @@ -158,6 +163,11 @@ namespace Microsoft.Xna.Framework { EventHelpers.Raise(this, TextInput, e); } + protected void OnKeyDown(object sender, TextInputEventArgs e) + { + EventHelpers.Raise(this, KeyDown, e); + } + protected void OnTextEditing(object sender, TextEditingEventArgs e) { EventHelpers.Raise(this, TextEditing, e); diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDL2.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDL2.cs index 8b4672ef6..43f2d7f32 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDL2.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDL2.cs @@ -75,6 +75,8 @@ internal static class Sdl TextEditing = 0x302, TextInput = 0x303, + TextEditingExt = 0x305, + MouseMotion = 0x400, MouseButtonDown = 0x401, MouseButtonup = 0x402, @@ -139,6 +141,8 @@ internal static class Sdl [FieldOffset(0)] public Keyboard.TextEditingEvent Edit; [FieldOffset(0)] + public Keyboard.TextEditingExtEvent EditExt; + [FieldOffset(0)] public Keyboard.TextInputEvent Text; [FieldOffset(0)] public Mouse.WheelEvent Wheel; @@ -241,8 +245,8 @@ internal static class Sdl } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool d_sdl_istextinputactive(); - public static d_sdl_istextinputactive SDL_IsTextInputActive = FuncLoader.LoadFunction(NativeLibrary, "SDL_IsTextInputActive"); + public delegate bool d_sdl_istextinputshown(); + public static d_sdl_istextinputshown SDL_IsTextInputShown = FuncLoader.LoadFunction(NativeLibrary, "SDL_IsTextInputShown"); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void d_sdl_starttextinput(); @@ -841,6 +845,17 @@ internal static class Sdl public int Length; } + [StructLayout(LayoutKind.Sequential)] + public unsafe struct TextEditingExtEvent + { + public EventType Type; + public uint Timestamp; + public uint WindowId; + public byte* Text; + public int Start; + public int Length; + } + [StructLayout(LayoutKind.Sequential)] public unsafe struct TextInputEvent { diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGamePlatform.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGamePlatform.cs index fb2f05833..6f3a4f18b 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGamePlatform.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGamePlatform.cs @@ -147,14 +147,7 @@ namespace Microsoft.Xna.Framework character = '\0'; } - if (char.IsControl(character) || - key == Keys.Left || - key == Keys.Right || - key == Keys.Up || - key == Keys.Down) - { - _view.CallTextInput(character, key); - } + _view.CallKeyDown(character, key); } else if (ev.Type == Sdl.EventType.KeyUp) { @@ -164,10 +157,24 @@ namespace Microsoft.Xna.Framework else if (ev.Type == Sdl.EventType.TextEditing) { string text; - unsafe { text = ReadString(ev.Text.Text); } + unsafe + { + text = ReadString(ev.Edit.Text); + } _view.CallTextEditing(text, ev.Edit.Start, ev.Edit.Length); } + else if (ev.Type == Sdl.EventType.TextEditingExt) + { + string text; + unsafe + { + text = ReadString(ev.EditExt.Text); + Sdl.Free((IntPtr)ev.EditExt.Text); + } + + _view.CallTextEditing(text, ev.EditExt.Start, ev.EditExt.Length); + } else if (ev.Type == Sdl.EventType.TextInput) { string text; diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGameWindow.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGameWindow.cs index 91eab7e6a..5da9dc065 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGameWindow.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/SDL/SDLGameWindow.cs @@ -117,7 +117,8 @@ namespace Microsoft.Xna.Framework * By default SDL2 will hide IME popups since it probably assumes the game will implement their own suggestions box. * We don't want that, so this hint will allow the system native IME popups to show up when typing in the game. */ - Sdl.SetHint("SDL_HINT_IME_SHOW_UI", "1"); + Sdl.SetHint("SDL_IME_SHOW_UI", "1"); + Sdl.SetHint("SDL_IME_SUPPORT_EXTENDED_TEXT", "1"); // when running NUnit tests entry assembly can be null if (Assembly.GetEntryAssembly() != null) @@ -339,6 +340,11 @@ namespace Microsoft.Xna.Framework OnTextInput(this, new TextInputEventArgs(c, key)); } + public void CallKeyDown(char c, Keys key = Keys.None) + { + OnKeyDown(this, new TextInputEventArgs(c, key)); + } + public void CallTextEditing(string text, int start, int length) { OnTextEditing(this, new TextEditingEventArgs(text, start, length)); diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/TextInput.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/TextInput.cs index df18c7bba..631097ac4 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/TextInput.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/TextInput.cs @@ -4,6 +4,11 @@ namespace Microsoft.Xna.Framework { public static class TextInput { + public static bool IsTextInputShown() + { + return Sdl.SDL_IsTextInputShown(); + } + public static void StartTextInput() { Sdl.SDL_StartTextInput(); diff --git a/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Linux/x64/libSDL2-2.0.so.0 b/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Linux/x64/libSDL2-2.0.so.0 index 846fdc9a0..72cda07f7 100755 Binary files a/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Linux/x64/libSDL2-2.0.so.0 and b/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Linux/x64/libSDL2-2.0.so.0 differ diff --git a/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x64/SDL2.dll b/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x64/SDL2.dll index 6cf858caa..8c6230e6f 100644 Binary files a/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x64/SDL2.dll and b/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x64/SDL2.dll differ diff --git a/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x86/SDL2.dll b/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x86/SDL2.dll index a25b05a06..0de0af520 100644 Binary files a/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x86/SDL2.dll and b/Libraries/MonoGame.Framework/Src/ThirdParty/Dependencies/SDL/Windows/x86/SDL2.dll differ diff --git a/WindowsSolution.sln b/WindowsSolution.sln index d66f0eaf5..cff6db096 100644 --- a/WindowsSolution.sln +++ b/WindowsSolution.sln @@ -43,8 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsTest", "Barotrauma\B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeployAll", "Deploy\DeployAll\DeployAll.csproj", "{C98FE0D0-BC7D-4806-B592-734B53016FD8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImeSharp", "Libraries\ImeSharp\ImeSharp.csproj", "{D42D8E48-A687-445D-AAF1-9E7E6EBF1DE6}" -EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Libraries\GameAnalytics\GA-SDK-MONO-SHARED\GA-SDK-MONO-SHARED.projitems*{95c4d59d-9be4-4278-b4f8-46c0ba1a3916}*SharedItemsImports = 5