using System; using System.Drawing; using OpenTK.Input; using System.Windows.Forms; namespace ClassicalSharp { public sealed class TextInputWidget : Widget { const int len = 64 * 3; const int lines = 3; public TextInputWidget( Game game, Font font, Font boldFont ) : base( game ) { HorizontalAnchor = Anchor.LeftOrTop; VerticalAnchor = Anchor.BottomOrRight; typingLogPos = game.Chat.InputLog.Count; // Index of newest entry + 1. this.font = font; this.boldFont = boldFont; chatInputText = new StringBuffer( len ); } Texture chatInputTexture, caretTexture; FastColour backColour = new FastColour( 60, 60, 60, 200 ); int caretPos = -1; int typingLogPos = 0; public int YOffset; internal StringBuffer chatInputText; readonly Font font, boldFont; static FastColour normalCaretCol = FastColour.White, nextCaretCol = FastColour.Yellow; FastColour caretCol; public override void Render( double delta ) { chatInputTexture.Render( graphicsApi ); caretTexture.Render( graphicsApi, caretCol ); } string[] parts = new string[lines]; int[] partLens = new int[lines]; Size[] sizes = new Size[lines]; int maxWidth = 0; public override void Init() { X = 10; DrawTextArgs args = new DrawTextArgs( "_", boldFont, false ); caretTexture = game.Drawer2D.UseBitmappedChat ? game.Drawer2D.MakeBitmappedTextTexture( ref args, 0, 0 ) : game.Drawer2D.MakeTextTexture( ref args, 0, 0 ); chatInputText.WordWrap( ref parts, ref partLens, 64 ); maxWidth = 0; args = new DrawTextArgs( null, font, false ); for( int i = 0; i < lines; i++ ) { args.Text = parts[i]; sizes[i] = game.Drawer2D.MeasureChatSize( ref args ); maxWidth = Math.Max( maxWidth, sizes[i].Width ); } int realIndex = caretPos; if( chatInputText.Empty || caretPos == -1 || caretPos >= chatInputText.Length ) { caretPos = -1; realIndex = 500000; } int sum = 0, indexX = -1, indexY = 0; for( int i = 0; i < lines; i++ ) { if( partLens[i] == 0 ) break; indexY = i; if( realIndex < sum + partLens[i] ) { indexX = realIndex - sum; break; } sum += partLens[i]; } if( indexX == -1 ) indexX = partLens[indexY]; if( indexX == 64 ) { caretTexture.X1 = 10 + sizes[indexY].Width; sizes[indexY].Width += caretTexture.Width; maxWidth = Math.Max( maxWidth, sizes[indexY].Width ); caretTexture.Y1 = sizes[0].Height * indexY; caretCol = nextCaretCol; } else { args.Text = parts[indexY].Substring( 0, indexX ); Size trimmedSize = game.Drawer2D.MeasureChatSize( ref args ); caretTexture.X1 = 10 + trimmedSize.Width; args.Text = new String( parts[indexY][indexX], 1 ); Size charSize = game.Drawer2D.MeasureChatSize( ref args ); caretTexture.Width = charSize.Width; caretTexture.Y1 = sizes[0].Height * indexY; caretCol = normalCaretCol; } DrawString(); } void DrawString() { int totalHeight = 0; for( int i = 0; i < lines; i++ ) { totalHeight += sizes[i].Height; } Size size = new Size( maxWidth, totalHeight ); int y = game.Height - size.Height - YOffset; using( Bitmap bmp = IDrawer2D.CreatePow2Bitmap( size ) ) { using( IDrawer2D drawer = game.Drawer2D ) { drawer.SetBitmap( bmp ); DrawTextArgs args = new DrawTextArgs( null, font, false ); int yyy = 0; for( int i = 0; i < parts.Length; i++ ) { if( parts[i] == null ) break; args.Text = parts[i]; drawer.Clear( backColour, 0, yyy, sizes[i].Width, sizes[i].Height ); drawer.DrawChatText( ref args, 0, yyy ); yyy += sizes[i].Height; } chatInputTexture = drawer.Make2DTexture( bmp, size, 10, y ); } } caretTexture.Y1 = y + caretTexture.Y1; Y = y; Width = size.Width; Height = size.Height; } public override void Dispose() { graphicsApi.DeleteTexture( ref caretTexture ); graphicsApi.DeleteTexture( ref chatInputTexture ); } public override void MoveTo( int newX, int newY ) { int deltaX = newX - X; int deltaY = newY - Y; X = newX; Y = newY; caretTexture.Y1 += deltaY; chatInputTexture.Y1 += deltaY; } static bool IsInvalidChar( char c ) { // Make sure we're in the printable text range from 0x20 to 0x7E return c < ' ' || c == '&' || c > '~'; } static char[] trimChars = { ' ' }; public void SendTextInBufferAndReset() { int packetsCount = 0; for( int i = 0; i < parts.Length; i++ ) { if( parts[i] == null ) break; packetsCount++; } // split up into both partial and final packet. if( packetsCount > 0 ) { for( int i = 0; i < packetsCount - 1; i++ ) { game.Chat.Send( parts[i].TrimEnd( trimChars ), true ); } game.Chat.Send( parts[packetsCount - 1].TrimEnd( trimChars ), false ); } typingLogPos = game.Chat.InputLog.Count; // Index of newest entry + 1. chatInputText.Clear(); caretPos = -1; Dispose(); Height = 0; } public void Clear() { chatInputText.Clear(); for( int i = 0; i < parts.Length; i++ ) { parts[i] = null; } } public void AppendText( string text ) { if( chatInputText.Length + text.Length > len ) { text = text.Substring( 0, len - chatInputText.Length ); } if( caretPos == -1 ) { chatInputText.Append( chatInputText.Length, text ); } else { chatInputText.Append( caretPos, text ); caretPos += text.Length; if( caretPos >= chatInputText.Length ) caretPos = -1; } Dispose(); Init(); } #region Input handling public override bool HandlesKeyPress( char key ) { if( chatInputText.Length < len && !IsInvalidChar( key ) ) { if( caretPos == -1 ) { chatInputText.Append( chatInputText.Length, key ); } else { chatInputText.InsertAt( caretPos, key ); caretPos++; } Dispose(); Init(); return true; } return false; } public override bool HandlesKeyDown( Key key ) { if( key == Key.Down ) DownKey(); else if( key == Key.Up ) UpKey(); else if( key == Key.Left ) LeftKey(); else if( key == Key.Right ) RightKey(); else if( key == Key.BackSpace ) BackspaceKey(); else if( key == Key.Delete ) DeleteKey(); else if( !OtherKey( key ) ) return false; return true; } void BackspaceKey() { if( !chatInputText.Empty && caretPos != 0 ) { if( caretPos == -1 ) { chatInputText.DeleteAt( chatInputText.Length - 1 ); } else { caretPos--; chatInputText.DeleteAt( caretPos ); } Dispose(); Init(); } } void DeleteKey() { if( !chatInputText.Empty && caretPos != -1 ) { chatInputText.DeleteAt( caretPos ); if( caretPos >= chatInputText.Length ) caretPos = -1; Dispose(); Init(); } } void RightKey() { if( !chatInputText.Empty && caretPos != -1 ) { caretPos++; if( caretPos >= chatInputText.Length ) caretPos = -1; Dispose(); Init(); } } void LeftKey() { if( !chatInputText.Empty ) { if( caretPos == -1 ) caretPos = chatInputText.Length; caretPos--; if( caretPos < 0 ) caretPos = 0; Dispose(); Init(); } } void UpKey() { if( game.Chat.InputLog.Count > 0 ) { typingLogPos--; if( typingLogPos < 0 ) typingLogPos = 0; chatInputText.Clear(); chatInputText.Append( 0, game.Chat.InputLog[typingLogPos] ); caretPos = -1; Dispose(); Init(); } } void DownKey() { if( game.Chat.InputLog.Count > 0 ) { typingLogPos++; chatInputText.Clear(); if( typingLogPos >= game.Chat.InputLog.Count ) { typingLogPos = game.Chat.InputLog.Count; } else { chatInputText.Append( 0, game.Chat.InputLog[typingLogPos] ); } caretPos = -1; Dispose(); Init(); } } bool OtherKey( Key key ) { bool controlDown = game.IsKeyDown( Key.ControlLeft ) || game.IsKeyDown( Key.ControlRight ); if( key == Key.V && controlDown && chatInputText.Length < len ) { string text = Clipboard.GetText(); if( String.IsNullOrEmpty( text ) ) return true; for( int i = 0; i < text.Length; i++ ) { if( IsInvalidChar( text[i] ) ) { game.Chat.Add( "&eClipboard contained characters that can't be sent." ); return true; } } AppendText( text ); return true; } else if( key == Key.C && controlDown ) { if( !chatInputText.Empty ) { Clipboard.SetText( chatInputText.ToString() ); } return true; } return false; } #endregion } }