// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using System.Collections.Generic; using System.Drawing; using ClassicalSharp.Entities; using OpenTK.Input; #if ANDROID using Android.Graphics; #endif namespace ClassicalSharp.Gui.Widgets { public sealed class ChatInputWidget : InputWidget { public ChatInputWidget(Game game, Font font) : base(game, font) { typingLogPos = game.Chat.InputLog.Count; // Index of newest entry + 1. ShowCaret = true; } static FastColour backColour = new FastColour(0, 0, 0, 127); int typingLogPos; string originalText; bool shownWarning; public override int MaxLines { get { return game.ClassicMode ? 1 : 3; } } public override string Prefix { get { return "> "; } } public override int Padding { get { return 5; } } public override int MaxCharsPerLine { get { bool allChars = game.ClassicMode || game.Server.SupportsPartialMessages; return allChars ? 64 : 62; } } public override void Init() { base.Init(); bool supports = game.Server.SupportsPartialMessages; if (Text.Length > MaxCharsPerLine && !shownWarning && !supports) { game.Chat.Add("&eNote: Each line will be sent as a separate packet.", MessageType.ClientStatus6); shownWarning = true; } else if (Text.Length <= MaxCharsPerLine && shownWarning) { game.Chat.Add(null, MessageType.ClientStatus6); shownWarning = false; } } public override void Render(double delta) { gfx.Texturing = false; int y = Y, x = X; for (int i = 0; i < lineSizes.Length; i++) { if (i > 0 && lineSizes[i].Height == 0) break; bool caretAtEnd = (caretRow == i) && (caretCol == MaxCharsPerLine || caret == -1); int drawWidth = lineSizes[i].Width + (caretAtEnd ? caretTex.Width : 0); // Cover whole window width to match original classic behaviour if (game.PureClassic) drawWidth = Math.Max(drawWidth, game.Width - X * 4); gfx.Draw2DQuad(x, y, drawWidth + Padding * 2, prefixHeight, backColour); y += lineSizes[i].Height; } gfx.Texturing = true; inputTex.Render(gfx); RenderCaret(delta); } public override void EnterInput() { SendChat(); originalText = null; typingLogPos = game.Chat.InputLog.Count; // Index of newest entry + 1. game.Chat.Add(null, MessageType.ClientStatus4); game.Chat.Add(null, MessageType.ClientStatus5); game.Chat.Add(null, MessageType.ClientStatus6); base.EnterInput(); } void SendChat() { if (Text.Empty) return; // Don't want trailing spaces in output message string allText = new String(Text.value, 0, Text.TextLength); game.Chat.InputLog.Add(allText); if (game.Server.SupportsPartialMessages) { SendWithPartial(allText); } else { SendNormal(); } } void SendWithPartial(string allText) { // don't automatically word wrap the message. while (allText.Length > Utils.StringLength) { game.Chat.Send(allText.Substring(0, Utils.StringLength), true); allText = allText.Substring(Utils.StringLength); } game.Chat.Send(allText, false); } void SendNormal() { int packetsCount = 0; for (int i = 0; i < lines.Length; i++) { if (lines[i] == null) break; packetsCount++; } // split up into both partial and final packet. for (int i = 0; i < packetsCount - 1; i++) SendNormalText(i, true); SendNormalText(packetsCount - 1, false); } void SendNormalText(int i, bool partial) { string text = lines[i]; char lastCol = GetLastColour(0, i); if (!IDrawer2D.IsWhiteColour(lastCol)) text = "&" + lastCol + text; game.Chat.Send(text, partial); } #region Input handling public override bool HandlesKeyDown(Key key) { if (game.HideGui) return key < Key.F1 || key > Key.F35; bool controlDown = ControlDown(); if (key == Key.Tab) { TabKey(); return true; } if (key == Key.Up) { UpKey(controlDown); return true; } if (key == Key.Down) { DownKey(controlDown); return true; } return base.HandlesKeyDown(key); } void UpKey(bool controlDown) { if (controlDown) { int pos = caret == -1 ? Text.Length : caret; if (pos < MaxCharsPerLine) return; caret = pos - MaxCharsPerLine; UpdateCaret(); return; } if (typingLogPos == game.Chat.InputLog.Count) originalText = Text.ToString(); if (game.Chat.InputLog.Count > 0) { typingLogPos--; if (typingLogPos < 0) typingLogPos = 0; Text.Clear(); Text.Append(0, game.Chat.InputLog[typingLogPos]); caret = -1; Recreate(); } } void DownKey(bool controlDown) { if (controlDown) { if (caret == -1 || caret >= (lines.Length - 1) * MaxCharsPerLine) return; caret += MaxCharsPerLine; UpdateCaret(); return; } if (game.Chat.InputLog.Count > 0) { typingLogPos++; Text.Clear(); if (typingLogPos >= game.Chat.InputLog.Count) { typingLogPos = game.Chat.InputLog.Count; if (originalText != null) Text.Append(0, originalText); } else { Text.Append(0, game.Chat.InputLog[typingLogPos]); } caret = -1; Recreate(); } } void TabKey() { int pos = caret == -1 ? Text.Length - 1 : caret; int start = pos; char[] value = Text.value; while (start >= 0 && IsNameChar(value[start])) start--; start++; if (pos < 0 || start > pos) return; string part = new String(value, start, pos + 1 - start); List matches = new List(); game.Chat.Add(null, MessageType.ClientStatus5); TabListEntry[] entries = game.TabList.Entries; for (int i = 0; i < EntityList.MaxCount; i++) { if (entries[i] == null) continue; string rawName = entries[i].PlayerName; string name = Utils.StripColours(rawName); if (name.StartsWith(part, StringComparison.OrdinalIgnoreCase)) matches.Add(name); } if (matches.Count == 1) { if (caret == -1) pos++; int len = pos - start; for (int i = 0; i < len; i++) Text.DeleteAt(start); if (caret != -1) caret -= len; Append(matches[0]); } else if (matches.Count > 1) { StringBuffer sb = new StringBuffer(Utils.StringLength); int index = 0; sb.Append(ref index, "&e"); sb.AppendNum(ref index, matches.Count); sb.Append(ref index, " matching names: "); for (int i = 0; i < matches.Count; i++) { string match = matches[i]; if ((sb.Length + match.Length + 1) > sb.Capacity) break; sb.Append(ref index, match); sb.Append(ref index, ' '); } game.Chat.Add(sb.ToString(), MessageType.ClientStatus5); } } bool IsNameChar(char c) { return c == '_' || c == '.' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } #endregion } }