diff --git a/ClassicalSharp/2D/Widgets/Chat/TextGroupWidget.cs b/ClassicalSharp/2D/Widgets/Chat/TextGroupWidget.cs index 3e26710d0..b7b9f78d9 100644 --- a/ClassicalSharp/2D/Widgets/Chat/TextGroupWidget.cs +++ b/ClassicalSharp/2D/Widgets/Chat/TextGroupWidget.cs @@ -244,18 +244,18 @@ namespace ClassicalSharp.Gui.Widgets { for (; i < total; i++) { if (!(chars[i] == 'h' || chars[i] == '&')) continue; int left = total - i; - if (left < prefixLen) return -1; + if (left < prefixLen) return total; // colour codes at start of URL int start = i; while (left >= 2 && chars[i] == '&') { left -= 2; i += 2; } if (left < prefixLen) continue; - // Starts with "http" ? + // Starts with "http" if (chars[i] != 'h' || chars[i + 1] != 't' || chars[i + 2] != 't' || chars[i + 3] != 'p') continue; left -= 4; i += 4; - // And then with "s://" or "://" ? + // And then with "s://" or "://" if (chars[i] == 's') { left--; i++; } if (left >= 3 && chars[i] == ':' && chars[i + 1] == '/' && chars[i + 2] == '/') return start; } @@ -265,7 +265,7 @@ namespace ClassicalSharp.Gui.Widgets { unsafe int UrlEnd(char* chars, int total, int* begs, int i) { int start = i; for (; i < total && chars[i] != ' '; i++) { - // Is this character the start of a line? + // Is this character the start of a line bool isBeg = false; for (int j = 0; j < lines.Length; i++) { if (j == begs[j]) { isBeg = true; break; } @@ -275,7 +275,7 @@ namespace ClassicalSharp.Gui.Widgets { if (!isBeg || i == start) continue; if (chars[i] != '>') break; - // Does this line start with "> ", making it a multiline ? + // Does this line start with "> ", making it a multiline int next = i + 1, left = total - next; while (left >= 2 && chars[next] == '&') { left -= 2; next += 2; } if (left == 0 || chars[next] != ' ') break; @@ -324,22 +324,21 @@ namespace ClassicalSharp.Gui.Widgets { total += line.Length; ends[i] = total; } - int urlEnd = 0; + int end = 0; Portion bit = default(Portion); for (;;) { - int nextUrlStart = NextUrl(chars, urlEnd, total); + int nextStart = NextUrl(chars, end, total); // add normal portion between urls - Portion bit = default(Portion); bit.Beg = urlEnd; - bit.Len = nextUrlStart - urlEnd; + bit.Beg = end; + bit.Len = nextStart - end; Output(bit, begs[target], ends[target], ref portions); - if (nextUrlStart == total) break; - urlEnd = UrlEnd(chars, total, begs, nextUrlStart); + if (nextStart == total) break; + end = UrlEnd(chars, total, begs, nextStart); // add this url portion - bit = default(Portion); - bit.Beg = nextUrlStart; - bit.Len = (urlEnd - nextUrlStart) | 0x8000; + bit.Beg = nextStart; + bit.Len = (end - nextStart) | 0x8000; Output(bit, begs[target], ends[target], ref portions); } return (int)(portions - start); diff --git a/src/Client/Screens.c b/src/Client/Screens.c index cefe4b3da..254b1c33e 100644 --- a/src/Client/Screens.c +++ b/src/Client/Screens.c @@ -1008,12 +1008,8 @@ static bool ChatScreen_HandlesMouseDown(struct GuiElem* elem, Int32 x, Int32 y, TextGroupWidget_GetSelected(&screen->Chat, &text, x, y); if (text.length == 0) return false; - UChar urlBuffer[String_BufferSize(TEXTGROUPWIDGET_LEN)]; - String url = String_InitAndClearArray(urlBuffer); - String_AppendColorless(&url, &text); - - if (Utils_IsUrlPrefix(&url, 0)) { - struct Screen* overlay = UrlWarningOverlay_MakeInstance(&url); + if (Utils_IsUrlPrefix(&text, 0)) { + struct Screen* overlay = UrlWarningOverlay_MakeInstance(&text); Gui_ShowOverlay(overlay, false); } else if (Game_ClickableChat) { InputWidget_AppendString(&screen->Input.Base, &text); diff --git a/src/Client/Widgets.c b/src/Client/Widgets.c index ea888cf34..75b3063b1 100644 --- a/src/Client/Widgets.c +++ b/src/Client/Widgets.c @@ -2315,6 +2315,111 @@ static void TextGroupWidget_UpdateDimensions(struct TextGroupWidget* widget) { Widget_Reposition(widget); } +Int32 TextGroupWidget_NextUrl(UChar* chars, Int32 i, Int32 total) { + for (; i < total; i++) { + if (!(chars[i] == 'h' || chars[i] == '&')) continue; + Int32 left = total - i; + if (left < prefixLen) return total; + + /* colour codes at start of URL */ + Int32 start = i; + while (left >= 2 && chars[i] == '&') { left -= 2; i += 2; } + if (left < prefixLen) continue; + + /* Starts with "http" */ + if (chars[i] != 'h' || chars[i + 1] != 't' || chars[i + 2] != 't' || chars[i + 3] != 'p') continue; + left -= 4; i += 4; + + /* And then with "s://" or "://" */ + if (chars[i] == 's') { left--; i++; } + if (left >= 3 && chars[i] == ':' && chars[i + 1] == '/' && chars[i + 2] == '/') return start; + } + return total; +} + +Int32 TextGroupWidget_UrlEnd(UChar* chars, Int32 total, Int32* begs, Int32 i) { + Int32 start = i, j; + for (; i < total && chars[i] != ' '; i++) { + /* Is this character the start of a line */ + bool isBeg = false; + for (j = 0; j < lines.Length; i++) { + if (j == begs[j]) { isBeg = true; break; } + } + + /* Definitely not a multilined URL */ + if (!isBeg || i == start) continue; + if (chars[i] != '>') break; + + /* Does this line start with "> ", making it a multiline */ + Int32 next = i + 1, left = total - next; + while (left >= 2 && chars[next] == '&') { left -= 2; next += 2; } + if (left == 0 || chars[next] != ' ') break; + + i = next; + } + return i; +} + +void TextGroupWidget_Output(struct Portion bit, Int32 lineBeg, Int32 lineEnd, struct Portion** portions) { + if (bit.Beg >= lineEnd || bit.Len == 0) return; + bit.LineBeg = bit.Beg; + bit.LineLen = bit.Len & 0x7FFF; + + /* Adjust this portion to be within this line */ + if (bit.Beg >= lineBeg) { + } else if (bit.Beg + bit.LineLen > lineBeg) { + /* Adjust start of portion to be within this line */ + Int32 underBy = lineBeg - bit.Beg; + bit.LineBeg += underBy; bit.LineLen -= underBy; + } else { return; } + + /* Limit length of portion to be within this line */ + Int32 overBy = (bit.LineBeg + bit.LineLen) - lineEnd; + if (overBy > 0) bit.LineLen -= overBy; + + bit.LineBeg -= lineBeg; + if (bit.LineLen == 0) return; + + struct Portion* cur = *portions; *cur++ = bit; *portions = cur; +} + +struct Portion { Int16 Beg, Len, LineBeg, LineLen; }; +Int32 TextGroupWidget_Reduce(UChar* chars, Int32 target, struct Portion* portions) { + struct Portion* start = portions; + Int32 total = 0, i, j; + Int32 begs[TEXTGROUPWIDGET_MAX_LINES]; + Int32 ends[TEXTGROUPWIDGET_MAX_LINES]; + + for (i = 0; i < lines.Length; i++) { + string line = lines[i]; + begs[i] = -1; ends[i] = -1; + if (line == null) continue; + + begs[i] = total; + for (j = 0; j < line.Length; j++) { chars[total + j] = line[j]; } + total += line.Length; ends[i] = total; + } + + Int32 end = 0; struct Portion bit; + for (;;) { + Int32 nextStart = NextUrl(chars, end, total); + + /* add normal portion between urls */ + bit.Beg = end; + bit.Len = nextStart - end; + Output(bit, begs[target], ends[target], &portions); + + if (nextStart == total) break; + end = UrlEnd(chars, total, begs, nextStart); + + /* add this url portion */ + bit.Beg = nextStart; + bit.Len = (end - nextStart) | 0x8000; + Output(bit, begs[target], ends[target], &portions); + } + return (Int32)(portions - start); +} + String TextGroupWidget_UNSAFE_Get(struct TextGroupWidget* widget, Int32 i) { UChar* buffer = widget->Buffer + i * TEXTGROUPWIDGET_LEN; UInt16 length = widget->LineLengths[i];