From ff746a4c5fcd9de2fd63a80a7bf463d01348f176 Mon Sep 17 00:00:00 2001
From: terencehill <piuntn@gmail.com>
Date: Tue, 5 Jan 2021 00:03:15 +0100
Subject: [PATCH] Menu, player name editing: make changing an existing color
 much easier by detecting a color code even if the cursor is in the middle of
 it

---
 qcsrc/common/util.qc              |  8 -----
 qcsrc/lib/string.qh               | 60 +++++++++++++++++++++++++++++++
 qcsrc/menu/xonotic/colorpicker.qc | 52 +++++++--------------------
 3 files changed, 72 insertions(+), 48 deletions(-)

diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc
index d823eeec2..8f37802ae 100644
--- a/qcsrc/common/util.qc
+++ b/qcsrc/common/util.qc
@@ -770,14 +770,6 @@ int cvar_settemp_restore()
 	return j;
 }
 
-bool isCaretEscaped(string theText, float pos)
-{
-	int i = 0;
-	while(pos - i >= 1 && substring(theText, pos - i - 1, 1) == "^")
-		++i;
-	return (i & 1);
-}
-
 int skipIncompleteTag(string theText, float pos, int len)
 {
 	int tag_start = -1;
diff --git a/qcsrc/lib/string.qh b/qcsrc/lib/string.qh
index 946438ca5..69ecc7b4a 100644
--- a/qcsrc/lib/string.qh
+++ b/qcsrc/lib/string.qh
@@ -436,3 +436,63 @@ const string HEXDIGITS = "0123456789ABCDEF0123456789abcdef";
 
 const string DIGITS = "0123456789";
 #define IS_DIGIT(d) (strstrofs(DIGITS, (d), 0) >= 0)
+
+// returns true if the caret at position pos is escaped
+ERASEABLE
+bool isCaretEscaped(string theText, float pos)
+{
+	// count all the previous carets
+	int carets = 0;
+	while(pos - carets >= 1 && substring(theText, pos - carets - 1, 1) == "^")
+		++carets;
+	// if number of previous carets is odd then this carets is escaped
+	return (carets & 1);
+}
+
+ERASEABLE
+bool isValidColorCodeValue(string theText, int cc_len, int tag_start)
+{
+	if (cc_len == 2)
+		return IS_DIGIT(substring(theText, tag_start + 1, 1));
+	if (cc_len == 5)
+		return (IS_HEXDIGIT(substring(theText, tag_start + 2, 1))
+			&& IS_HEXDIGIT(substring(theText, tag_start + 3, 1))
+			&& IS_HEXDIGIT(substring(theText, tag_start + 4, 1)));
+	return false;
+}
+
+// it returns 0 if pos is NOT in the middle or at the end of a color code
+// otherwise it returns a 2-digit number with cc_len as the first digit
+// and the offset from '^' position to pos as the second digit
+// e.g.:
+// "a^2xy" | returns 0 if pos == 0 or 1 or 4
+//    ^^   | returns 21 or 22 if pos == 2 or 3
+ERASEABLE
+int checkColorCode(string theText, int pos)
+{
+	int text_len = strlen(theText);
+	string tag_type = "^";
+	int cc_len = 2;
+	int tag_len = 1;
+
+	LABEL(check_color_tag)
+
+	for (int ofs = cc_len; ofs >= 1; ofs--)
+	{
+		if (!(pos >= ofs && text_len >= pos + (cc_len - ofs)))
+			continue;
+		if(substring(theText, pos - ofs, tag_len) == tag_type)
+		{
+			if (!isCaretEscaped(theText, pos - ofs) && isValidColorCodeValue(theText, cc_len, pos - ofs))
+				return cc_len * 10 + ofs;
+		}
+	}
+	if (cc_len == 2)
+	{
+		tag_type = "^x";
+		cc_len = 5;
+		tag_len = 2;
+		goto check_color_tag;
+	}
+	return 0;
+}
diff --git a/qcsrc/menu/xonotic/colorpicker.qc b/qcsrc/menu/xonotic/colorpicker.qc
index d8aedf695..c4dfa2b6b 100644
--- a/qcsrc/menu/xonotic/colorpicker.qc
+++ b/qcsrc/menu/xonotic/colorpicker.qc
@@ -58,57 +58,29 @@ vector color_hslimage(vector v, vector margin)
 
 float XonoticColorpicker_mouseDrag(entity me, vector coords)
 {
-	float i, carets;
+	int i;
 	for (;;)
 	{
 		i = me.controlledTextbox.cursorPos;
-		if(i >= 2)
-		{
-			if(substring(me.controlledTextbox.text, i-2, 1) == "^")
-			{
-				carets = 1;
-				while (i - 2 - carets >= 0 && substring(me.controlledTextbox.text, i - 2 - carets, 1) == "^")
-					++carets;
-				if (carets & 1)
-					if(IS_DIGIT(substring(me.controlledTextbox.text, i-1, 1)))
-					{
-						me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
-						me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
-						continue;
-					}
-			}
-		}
 
-		if(i >= 5)
+		int res = checkColorCode(me.controlledTextbox.text, i);
+		if (res)
 		{
-			if(substring(me.controlledTextbox.text, i-5, 2) == "^x")
-			{
-				carets = 1;
-				while (i - 5 - carets >= 0 && substring(me.controlledTextbox.text, i - 5 - carets, 1) == "^")
-					++carets;
-				if (carets & 1)
-					if(IS_HEXDIGIT(substring(me.controlledTextbox.text, i - 3, 1)))
-						if(IS_HEXDIGIT(substring(me.controlledTextbox.text, i - 2, 1)))
-							if(IS_HEXDIGIT(substring(me.controlledTextbox.text, i - 1, 1)))
-							{
-								me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
-								me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
-								me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
-								me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
-								me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
-								continue;
-							}
-			}
+			int tag_length = floor(res / 10);
+			int ofs = res % 10;
+			for (int j = tag_length - ofs; j > 0; j--)
+				me.controlledTextbox.keyDown(me.controlledTextbox, K_RIGHTARROW, 8, 0);
+			for (int j = tag_length; j > 0; j--)
+				me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+			continue;
 		}
+
 		break;
 	}
 
 	if(substring(me.controlledTextbox.text, i-1, 1) == "^")
 	{
-		carets = 1;
-		while (i - 1 - carets >= 0 && substring(me.controlledTextbox.text, i - 1 - carets, 1) == "^")
-			++carets;
-		if (carets & 1)
+		if(!isCaretEscaped(me.controlledTextbox.text, i-1))
 			me.controlledTextbox.enterText(me.controlledTextbox, "^"); // escape previous caret
 	}
 
-- 
2.39.5