mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-17 11:35:08 -04:00
Vastly simplify line wrapping code, and fix it in C port too
This commit is contained in:
parent
c252baa696
commit
1b5276bae5
@ -48,9 +48,11 @@ namespace ClassicalSharp.Gui.Widgets {
|
||||
|
||||
|
||||
public override void EnterInput() {
|
||||
if (!Text.Empty) {
|
||||
// Don't want trailing spaces in output message
|
||||
string text = new String(Text.value, 0, Text.TextLength);
|
||||
int length = Text.Length;
|
||||
while (length > 0 && Text.value[length - 1] == ' ') { length--; }
|
||||
if (length > 0) {
|
||||
string text = new String(Text.value, 0, length);
|
||||
game.Chat.Send(text);
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ namespace ClassicalSharp.Gui.Widgets {
|
||||
public abstract class InputWidget : Widget {
|
||||
|
||||
public InputWidget(Game game, Font font, string prefix, int maxLines) : base(game) {
|
||||
Text = new WrappableStringBuffer(Utils.StringLength * maxLines);
|
||||
Text = new StringBuffer(Utils.StringLength * maxLines);
|
||||
lines = new string[maxLines];
|
||||
lineSizes = new Size[maxLines];
|
||||
this.font = font;
|
||||
@ -43,7 +43,7 @@ namespace ClassicalSharp.Gui.Widgets {
|
||||
|
||||
/// <summary> The raw text entered. </summary>
|
||||
/// <remarks> You should Append() to add more text, as that also updates the caret position and texture. </remarks>
|
||||
public WrappableStringBuffer Text;
|
||||
public StringBuffer Text;
|
||||
|
||||
/// <summary> The maximum number of lines that may be entered. </summary>
|
||||
public abstract int UsedLines { get; }
|
||||
|
@ -295,7 +295,6 @@
|
||||
<Compile Include="Utils\Utils.cs" />
|
||||
<Compile Include="Utils\Utils.Math.cs" />
|
||||
<Compile Include="Utils\Vector3I.cs" />
|
||||
<Compile Include="Utils\WrappableStringBuffer.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\credits.txt">
|
||||
|
@ -33,23 +33,18 @@ namespace ClassicalSharp.Model {
|
||||
.RotOrigin(0, 6, 2));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float NameYOffset { get { return 1.7f; } }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetEyeY(Entity entity) { return 22/16f; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Vector3 CollisionSize {
|
||||
get { return new Vector3(8/16f, 26/16f, 8/16f); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AABB PickingBounds {
|
||||
get { return new AABB(-4/16f, 0, -6/16f, 4/16f, 26/16f, 6/16f); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DrawModel(Entity p) {
|
||||
game.Graphics.BindTexture(GetTexture(p));
|
||||
DrawRotate(-p.HeadXRadians, 0, 0, Head, true);
|
||||
|
@ -10,7 +10,6 @@ namespace ClassicalSharp.Model {
|
||||
|
||||
public PigModel(Game window) : base(window) { SurivalScore = 10; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CreateParts() {
|
||||
vertices = new ModelVertex[boxVertices * 6];
|
||||
Head = BuildBox(MakeBoxBounds(-4, 8, -14, 4, 16, -6)
|
||||
|
@ -66,23 +66,18 @@ namespace ClassicalSharp.Model {
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float NameYOffset { get { return Fur ? 1.48125f: 1.075f; } }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetEyeY(Entity entity) { return 20/16f; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Vector3 CollisionSize {
|
||||
get { return new Vector3(10/16f, 20/16f, 10/16f); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AABB PickingBounds {
|
||||
get { return new AABB(-6/16f, 0, -13/16f, 6/16f, 23/16f, 10/16f); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DrawModel(Entity p) {
|
||||
IGraphicsApi gfx = game.Graphics;
|
||||
gfx.BindTexture(GetTexture(p));
|
||||
|
@ -28,18 +28,14 @@ namespace ClassicalSharp.Model {
|
||||
.RotOrigin(3, 8, 0));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float NameYOffset { get { return 1.0125f; } }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetEyeY(Entity entity) { return 8/16f; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Vector3 CollisionSize {
|
||||
get { return new Vector3(15/16f, 12/16f, 15/16f); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AABB PickingBounds {
|
||||
get { return new AABB(-5/16f, 0, -11/16f, 5/16f, 12/16f, 15/16f); }
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ namespace ClassicalSharp.Map {
|
||||
if (Env.CloudHeight == -1) Env.CloudHeight = height + 2;
|
||||
}
|
||||
|
||||
/// <summary> Sets the block at the given world coordinates without bounds checking. </summary>
|
||||
public void SetBlock(int x, int y, int z, BlockID blockId) {
|
||||
int i = (y * Length + z) * Width + x;
|
||||
blocks[i] = (BlockRaw)blockId;
|
||||
@ -76,7 +75,6 @@ namespace ClassicalSharp.Map {
|
||||
blocks2[i] = (BlockRaw)(blockId >> 8);
|
||||
}
|
||||
|
||||
/// <summary> Returns the block at the given world coordinates without bounds checking. </summary>
|
||||
public BlockID GetBlock(int x, int y, int z) {
|
||||
int i = (y * Length + z) * Width + x;
|
||||
#if !ONLY_8BIT
|
||||
@ -86,7 +84,6 @@ namespace ClassicalSharp.Map {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Returns the block at the given world coordinates without bounds checking. </summary>
|
||||
public BlockID GetBlock(Vector3I p) {
|
||||
int i = (p.Y * Length + p.Z) * Width + p.X;
|
||||
#if !ONLY_8BIT
|
||||
@ -96,21 +93,15 @@ namespace ClassicalSharp.Map {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Returns the block at the given world coordinates with bounds checking,
|
||||
/// returning 0 is the coordinates were outside the map. </summary>
|
||||
public BlockID SafeGetBlock(Vector3I p) {
|
||||
return IsValidPos(p.X, p.Y, p.Z) ? GetBlock(p) : Block.Air;
|
||||
}
|
||||
|
||||
/// <summary> Returns whether the given world coordinates are contained
|
||||
/// within the dimensions of the map. </summary>
|
||||
public bool IsValidPos(int x, int y, int z) {
|
||||
return x >= 0 && y >= 0 && z >= 0 &&
|
||||
x < Width && y < Height && z < Length;
|
||||
}
|
||||
|
||||
/// <summary> Returns whether the given world coordinates are contained
|
||||
/// within the dimensions of the map. </summary>
|
||||
public bool IsValidPos(Vector3I p) {
|
||||
return p.X >= 0 && p.Y >= 0 && p.Z >= 0 &&
|
||||
p.X < Width && p.Y < Height && p.Z < Length;
|
||||
|
@ -7,6 +7,7 @@ namespace ClassicalSharp {
|
||||
|
||||
public char[] value;
|
||||
public int Capacity, Length;
|
||||
public bool Empty { get { return Length == 0; } }
|
||||
|
||||
public StringBuffer(int capacity) {
|
||||
this.Capacity = capacity;
|
||||
@ -61,6 +62,31 @@ namespace ClassicalSharp {
|
||||
return this;
|
||||
}
|
||||
|
||||
public StringBuffer DeleteAt(int index) {
|
||||
for (int i = index; i < Length - 1; i++) {
|
||||
value[i] = value[i + 1];
|
||||
}
|
||||
|
||||
value[Length - 1] = '\0';
|
||||
Length--;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StringBuffer InsertAt(int index, char c) {
|
||||
for (int i = Length - 1; i > index; i--) {
|
||||
value[i] = value[i - 1];
|
||||
}
|
||||
|
||||
value[index] = c;
|
||||
Length++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StringBuffer Set(string s) {
|
||||
for (int i = 0; i < s.Length; i++) value[i] = s[i];
|
||||
return this;
|
||||
}
|
||||
|
||||
public StringBuffer Clear() {
|
||||
Length = 0;
|
||||
return this;
|
||||
@ -69,5 +95,111 @@ namespace ClassicalSharp {
|
||||
public override string ToString() {
|
||||
return new String(value, 0, Length);
|
||||
}
|
||||
|
||||
bool IsWrapper(char c) {
|
||||
return c == '\0' || c == ' ' || c == '-' || c == '>'
|
||||
|| c == '<' || c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
unsafe string Substring(int offset, int len) {
|
||||
if (len == 0) return "";
|
||||
char* tmp = stackalloc char[len];
|
||||
|
||||
// convert %0-f to &0-f for colour preview.
|
||||
for (int i = 0; i < len; i++) {
|
||||
tmp[i] = value[offset + i];
|
||||
if (tmp[i] != '%' || (i + 1) >= len) continue;
|
||||
if (IDrawer2D.ValidColCode(tmp[i + 1])) tmp[i] = '&';
|
||||
}
|
||||
return new String(tmp, 0, len);
|
||||
}
|
||||
|
||||
public void WordWrap(IDrawer2D drawer, string[] lines, int numLines, int lineLen) {
|
||||
for (int i = 0; i < numLines; i++) { lines[i] = null; }
|
||||
|
||||
int lineStart = 0, lineEnd;
|
||||
for (int i = 0; i < numLines; i++) {
|
||||
int nextLineStart = lineStart + lineLen;
|
||||
// No more text to wrap
|
||||
if (nextLineStart >= Length) {
|
||||
lines[i] = Substring(lineStart, Length - lineStart); return;
|
||||
}
|
||||
|
||||
// Find beginning of last word on current line
|
||||
for (lineEnd = nextLineStart; lineEnd >= lineStart; lineEnd--) {
|
||||
if (IsWrapper(value[lineEnd])) break;
|
||||
}
|
||||
lineEnd++; // move after wrapper char (i.e. beginning of last word)
|
||||
|
||||
if (lineEnd <= lineStart || lineEnd >= nextLineStart) {
|
||||
// Three special cases handled by this:
|
||||
// - Entire line is filled with a single word
|
||||
// - Last character(s) on current line are wrapper characters
|
||||
// - First character on next line is a wrapper character (last word ends at current line end)
|
||||
lines[i] = Substring(lineStart, lineLen);
|
||||
lineStart += lineLen;
|
||||
} else {
|
||||
// Last word in current line does not end in current line (extends onto next line)
|
||||
// Trim current line to end at beginning of last word
|
||||
// Set next line to start at beginning of last word
|
||||
lines[i] = Substring(lineStart, lineEnd - lineStart);
|
||||
lineStart = lineEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Calculates where the given raw index is located in the wrapped lines. </summary>
|
||||
public void GetCoords(int index, string[] lines, out int coordX, out int coordY) {
|
||||
if (index == -1) index = Int32.MaxValue;
|
||||
int total = 0; coordX = -1; coordY = 0;
|
||||
|
||||
for (int y = 0; y < lines.Length; y++) {
|
||||
int lineLength = LineLength(lines[y]);
|
||||
if (lineLength == 0) break;
|
||||
|
||||
coordY = y;
|
||||
if (index < total + lineLength) {
|
||||
coordX = index - total; break;
|
||||
}
|
||||
total += lineLength;
|
||||
}
|
||||
if (coordX == -1) coordX = LineLength(lines[coordY]);
|
||||
}
|
||||
|
||||
static int LineLength(string line) { return line == null ? 0 : line.Length; }
|
||||
|
||||
public int GetBackLength(int index) {
|
||||
if (index <= 0) return 0;
|
||||
int start = index;
|
||||
|
||||
bool lookingSpace = value[index] == ' ';
|
||||
// go back to the end of the previous word
|
||||
if (lookingSpace) {
|
||||
while (index > 0 && value[index] == ' ')
|
||||
index--;
|
||||
}
|
||||
|
||||
// go back to the start of the current word
|
||||
while (index > 0 && value[index] != ' ')
|
||||
index--;
|
||||
return (start - index);
|
||||
}
|
||||
|
||||
public int GetForwardLength(int index) {
|
||||
if (index == -1) return 0;
|
||||
int start = index;
|
||||
|
||||
bool lookingLetter = value[index] != ' ';
|
||||
// go forward to the end of the current word
|
||||
if (lookingLetter) {
|
||||
while (index < Length && value[index] != ' ')
|
||||
index++;
|
||||
}
|
||||
|
||||
// go forward to the start of the next word
|
||||
while (index < Length && value[index] == ' ')
|
||||
index++;
|
||||
return index - start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,212 +0,0 @@
|
||||
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
||||
using System;
|
||||
|
||||
namespace ClassicalSharp {
|
||||
|
||||
public unsafe sealed class WrappableStringBuffer {
|
||||
|
||||
public int Capacity;
|
||||
public char[] value;
|
||||
char[] wrap;
|
||||
|
||||
public WrappableStringBuffer(int capacity) {
|
||||
this.Capacity = capacity;
|
||||
value = new char[capacity];
|
||||
wrap = new char[capacity];
|
||||
}
|
||||
|
||||
public void DeleteAt(int index) {
|
||||
for (int i = index; i < Capacity - 1; i++)
|
||||
value[i] = value[i + 1];
|
||||
value[Capacity - 1] = '\0';
|
||||
}
|
||||
|
||||
public void InsertAt(int index, char c) {
|
||||
for (int i = Capacity - 1; i > index; i--)
|
||||
value[i] = value[i - 1];
|
||||
value[index] = c;
|
||||
}
|
||||
|
||||
public void Set(string s) {
|
||||
for (int i = 0; i < s.Length; i++) value[i] = s[i];
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
for (int i = 0; i < Capacity; i++) value[i] = '\0';
|
||||
}
|
||||
|
||||
public bool Empty {
|
||||
get {
|
||||
for (int i = 0; i < Capacity; i++) {
|
||||
if (value[i] != '\0') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public int Length {
|
||||
get {
|
||||
int len = Capacity;
|
||||
for (int i = Capacity - 1; i >= 0; i--) {
|
||||
if (value[i] != '\0') break;
|
||||
len--;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
public int TextLength {
|
||||
get {
|
||||
int len = Capacity;
|
||||
for (int i = Capacity - 1; i >= 0; i--) {
|
||||
if (value[i] != '\0' && value[i] != ' ') break;
|
||||
len--;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return new String(value, 0, Length);
|
||||
}
|
||||
|
||||
|
||||
public void WordWrap(IDrawer2D drawer, string[] lines, int maxLines, int maxPerLine) {
|
||||
int len = Length;
|
||||
int* lineLens = stackalloc int[lines.Length];
|
||||
for (int i = 0; i < lines.Length; i++) {
|
||||
lines[i] = null;
|
||||
lineLens[i] = 0;
|
||||
}
|
||||
|
||||
// Need to make a copy because we mutate the characters.
|
||||
char[] realText = value;
|
||||
MakeWrapCopy();
|
||||
|
||||
int usedLines = 0, totalChars = maxPerLine * maxLines;
|
||||
for (int index = 0; index < totalChars; index += maxPerLine) {
|
||||
if (value[index] == '\0') break;
|
||||
|
||||
int lineEnd = index + (maxPerLine - 1), nextStart = lineEnd + 1;
|
||||
usedLines++;
|
||||
|
||||
// Do we need word wrapping?
|
||||
bool needWrap = !IsWrapper(value[lineEnd])
|
||||
&& nextStart < totalChars && !IsWrapper(value[nextStart]);
|
||||
int wrappedLen = needWrap ? WrapLine(index, maxPerLine) : maxPerLine;
|
||||
|
||||
// Calculate the maximum size of this line
|
||||
int lineLen = maxPerLine;
|
||||
for (int i = lineEnd; i >= index; i--) {
|
||||
if (value[i] != '\0') break;
|
||||
lineLen--;
|
||||
}
|
||||
lineLens[index / maxPerLine] = Math.Min(lineLen, wrappedLen);
|
||||
}
|
||||
|
||||
// Output the used lines
|
||||
OutputLines(drawer, lines, lineLens, usedLines, maxLines, maxPerLine);
|
||||
value = realText;
|
||||
}
|
||||
|
||||
void MakeWrapCopy() {
|
||||
int len = Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
wrap[i] = value[i];
|
||||
|
||||
for (int i = len; i < Capacity; i++)
|
||||
wrap[i] = '\0';
|
||||
value = wrap;
|
||||
}
|
||||
|
||||
void OutputLines(IDrawer2D drawer, string[] lines, int* lineLens, int usedLines, int maxLines, int charsPerLine) {
|
||||
int totalChars = charsPerLine * maxLines;
|
||||
for (int i = 0; i < totalChars; i++) {
|
||||
if (value[i] == '\0') value[i] = ' ';
|
||||
}
|
||||
// convert %0-f to &0-f for colour preview.
|
||||
for (int i = 0; i < totalChars - 1; i++) {
|
||||
if (value[i] == '%' && IDrawer2D.ValidColCode(value[i + 1]))
|
||||
value[i] = '&';
|
||||
}
|
||||
|
||||
usedLines = Math.Max(1, usedLines);
|
||||
for (int i = 0; i < usedLines; i++)
|
||||
lines[i] = new String(value, i * charsPerLine, lineLens[i]);
|
||||
}
|
||||
|
||||
int WrapLine(int index, int lineSize) {
|
||||
int lineEnd = index + (lineSize - 1);
|
||||
// wrap - but we don't want to wrap if the entire line is filled.
|
||||
for (int i = lineEnd; i >= index + 1; i--) {
|
||||
if (IsWrapper(value[i])) {
|
||||
for (int j = lineEnd; j >= i + 1; j--) {
|
||||
InsertAt(index + lineSize, value[j]);
|
||||
value[j] = ' ';
|
||||
}
|
||||
return (i + 1) - index;
|
||||
}
|
||||
}
|
||||
return lineSize;
|
||||
}
|
||||
|
||||
bool IsWrapper(char c) {
|
||||
return c == '\0' || c == ' ' || c == '-' || c == '>'
|
||||
|| c == '<' || c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
/// <summary> Calculates where the given raw index is located in the wrapped lines. </summary>
|
||||
public void GetCoords(int index, string[] lines, out int coordX, out int coordY) {
|
||||
if (index == -1) index = Int32.MaxValue;
|
||||
int total = 0; coordX = -1; coordY = 0;
|
||||
|
||||
for (int y = 0; y < lines.Length; y++) {
|
||||
int lineLength = LineLength(lines[y]);
|
||||
if (lineLength == 0) break;
|
||||
|
||||
coordY = y;
|
||||
if (index < total + lineLength) {
|
||||
coordX = index - total; break;
|
||||
}
|
||||
total += lineLength;
|
||||
}
|
||||
if (coordX == -1) coordX = LineLength(lines[coordY]);
|
||||
}
|
||||
|
||||
static int LineLength(string line) { return line == null ? 0 : line.Length; }
|
||||
|
||||
public int GetBackLength(int index) {
|
||||
if (index <= 0) return 0;
|
||||
int start = index;
|
||||
|
||||
bool lookingSpace = value[index] == ' ';
|
||||
// go back to the end of the previous word
|
||||
if (lookingSpace) {
|
||||
while (index > 0 && value[index] == ' ')
|
||||
index--;
|
||||
}
|
||||
|
||||
// go back to the start of the current word
|
||||
while (index > 0 && value[index] != ' ')
|
||||
index--;
|
||||
return (start - index);
|
||||
}
|
||||
|
||||
public int GetForwardLength(int index) {
|
||||
if (index == -1) return 0;
|
||||
int start = index;
|
||||
|
||||
bool lookingLetter = value[index] != ' ';
|
||||
// go forward to the end of the current word
|
||||
if (lookingLetter) {
|
||||
while (index < Length && value[index] != ' ')
|
||||
index++;
|
||||
}
|
||||
|
||||
// go forward to the start of the next word
|
||||
while (index < Length && value[index] == ' ')
|
||||
index++;
|
||||
return index - start;
|
||||
}
|
||||
}
|
||||
}
|
@ -250,7 +250,6 @@
|
||||
<ClInclude Include="VertexStructs.h" />
|
||||
<ClInclude Include="Widgets.h" />
|
||||
<ClInclude Include="Window.h" />
|
||||
<ClInclude Include="WordWrap.h" />
|
||||
<ClInclude Include="World.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -319,7 +318,6 @@
|
||||
<ClCompile Include="WinErrorHandler.c" />
|
||||
<ClCompile Include="WinPlatform.c" />
|
||||
<ClCompile Include="WinWindow.c" />
|
||||
<ClCompile Include="WordWrap.c" />
|
||||
<ClCompile Include="World.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
|
@ -315,9 +315,6 @@
|
||||
<ClInclude Include="Widgets.h">
|
||||
<Filter>Header Files\2D</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WordWrap.h">
|
||||
<Filter>Header Files\Utils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Inventory.h">
|
||||
<Filter>Header Files\Game</Filter>
|
||||
</ClInclude>
|
||||
@ -521,9 +518,6 @@
|
||||
<ClCompile Include="Widgets.c">
|
||||
<Filter>Source Files\2D</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WordWrap.c">
|
||||
<Filter>Source Files\Utils</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Inventory.c">
|
||||
<Filter>Source Files\Game</Filter>
|
||||
</ClCompile>
|
||||
|
@ -11,6 +11,46 @@
|
||||
#include "Game.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
String text1 = String_FromConst("abcd");
|
||||
String lines1[3] = { 0 };
|
||||
WordWrap_Do(&text1, lines1, 3, 4);
|
||||
|
||||
String text2 = String_FromConst("abcde/fgh");
|
||||
String lines2[3] = { 0 };
|
||||
WordWrap_Do(&text2, lines2, 3, 4);
|
||||
|
||||
String text3 = String_FromConst("abc/defg");
|
||||
String lines3[3] = { 0 };
|
||||
WordWrap_Do(&text3, lines3, 3, 4);
|
||||
|
||||
String text4 = String_FromConst("ab/cdef");
|
||||
String lines4[3] = { 0 };
|
||||
WordWrap_Do(&text4, lines4, 3, 4);
|
||||
|
||||
String text5 = String_FromConst("abcd/efg");
|
||||
String lines5[3] = { 0 };
|
||||
WordWrap_Do(&text5, lines5, 3, 4);
|
||||
|
||||
String text6 = String_FromConst("abc/efg/hij/");
|
||||
String lines6[3] = { 0 };
|
||||
WordWrap_Do(&text6, lines6, 3, 4);
|
||||
|
||||
String text7 = String_FromConst("ab cde fgh");
|
||||
String lines7[3] = { 0 };
|
||||
WordWrap_Do(&text7, lines7, 3, 4);
|
||||
|
||||
String text8 = String_FromConst("ab//cd");
|
||||
String lines8[3] = { 0 };
|
||||
WordWrap_Do(&text8, lines8, 3, 4);
|
||||
|
||||
String text9 = String_FromConst("a///b");
|
||||
String lines9[3] = { 0 };
|
||||
WordWrap_Do(&text9, lines9, 3, 4);
|
||||
|
||||
String text10 = String_FromConst("/aaab");
|
||||
String lines10[3] = { 0 };
|
||||
WordWrap_Do(&text10, lines10, 3, 4);
|
||||
|
||||
ErrorHandler_Init("client.log");
|
||||
Platform_Init();
|
||||
|
||||
|
@ -685,3 +685,98 @@ Int32 StringsBuffer_Compare(StringsBuffer* buffer, UInt32 idxA, UInt32 idxB) {
|
||||
String strB = StringsBuffer_UNSAFE_Get(buffer, idxB);
|
||||
return String_Compare(&strA, &strB);
|
||||
}
|
||||
|
||||
|
||||
bool WordWrap_IsWrapper(UInt8 c) {
|
||||
return c == NULL || c == ' ' || c == '-' || c == '>' || c == '<' || c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
void WordWrap_Do(STRING_REF String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 lineLen) {
|
||||
Int32 i;
|
||||
for (i = 0; i < numLines; i++) { lines[i] = String_MakeNull(); }
|
||||
|
||||
Int32 lineStart = 0, lineEnd;
|
||||
for (i = 0; i < numLines; i++) {
|
||||
Int32 nextLineStart = lineStart + lineLen;
|
||||
/* No more text to wrap */
|
||||
if (nextLineStart >= text->length) {
|
||||
lines[i] = String_UNSAFE_SubstringAt(text, lineStart); return;
|
||||
}
|
||||
|
||||
/* Find beginning of last word on current line */
|
||||
for (lineEnd = nextLineStart; lineEnd >= lineStart; lineEnd--) {
|
||||
if (WordWrap_IsWrapper(text->buffer[lineEnd])) break;
|
||||
}
|
||||
lineEnd++; /* move after wrapper char (i.e. beginning of last word)*/
|
||||
|
||||
if (lineEnd <= lineStart || lineEnd >= nextLineStart) {
|
||||
/* Three special cases handled by this: */
|
||||
/* - Entire line is filled with a single word */
|
||||
/* - Last character(s) on current line are wrapper characters */
|
||||
/* - First character on next line is a wrapper character (last word ends at current line end) */
|
||||
lines[i] = String_UNSAFE_Substring(text, lineStart, lineLen);
|
||||
lineStart += lineLen;
|
||||
} else {
|
||||
/* Last word in current line does not end in current line (extends onto next line) */
|
||||
/* Trim current line to end at beginning of last word */
|
||||
/* Set next line to start at beginning of last word */
|
||||
lines[i] = String_UNSAFE_Substring(text, lineStart, lineEnd - lineStart);
|
||||
lineStart = lineEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculates where the given raw index is located in the wrapped lines. */
|
||||
void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY) {
|
||||
if (index == -1) index = Int32_MaxValue;
|
||||
Int32 offset = 0; *coordX = -1; *coordY = 0;
|
||||
|
||||
Int32 y;
|
||||
for (y = 0; y < numLines; y++) {
|
||||
Int32 lineLength = lines[y].length;
|
||||
if (lineLength == 0) break;
|
||||
|
||||
*coordY = y;
|
||||
if (index < offset + lineLength) {
|
||||
*coordX = index - offset; break;
|
||||
}
|
||||
offset += lineLength;
|
||||
}
|
||||
if (*coordX == -1) *coordX = lines[*coordY].length;
|
||||
}
|
||||
|
||||
Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index) {
|
||||
if (index <= 0) return 0;
|
||||
if (index >= text->length) {
|
||||
ErrorHandler_Fail("WordWrap_GetBackLength - index past end of string");
|
||||
}
|
||||
|
||||
Int32 start = index;
|
||||
bool lookingSpace = text->buffer[index] == ' ';
|
||||
/* go back to the end of the previous word */
|
||||
if (lookingSpace) {
|
||||
while (index > 0 && text->buffer[index] == ' ') { index--; }
|
||||
}
|
||||
|
||||
/* go back to the start of the current word */
|
||||
while (index > 0 && text->buffer[index] != ' ') { index--; }
|
||||
return start - index;
|
||||
}
|
||||
|
||||
Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index) {
|
||||
if (index == -1) return 0;
|
||||
if (index >= text->length) {
|
||||
ErrorHandler_Fail("WordWrap_GetForwardLength - index past end of string");
|
||||
}
|
||||
|
||||
Int32 start = index, length = text->length;
|
||||
bool lookingLetter = text->buffer[index] != ' ';
|
||||
/* go forward to the end of the current word */
|
||||
if (lookingLetter) {
|
||||
while (index < length && text->buffer[index] != ' ') { index++; }
|
||||
}
|
||||
|
||||
/* go forward to the start of the next word */
|
||||
while (index < length && text->buffer[index] == ' ') { index++; }
|
||||
return index - start;
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
/* Implements operations for a string.
|
||||
Also implements conversions betweens strings and numbers.
|
||||
Also implements converting code page 437 indices to/from unicode.
|
||||
Also implements wrapping a single line of text into multiple lines.
|
||||
Copyright 2017 ClassicalSharp | Licensed under BSD-3
|
||||
*/
|
||||
|
||||
@ -120,4 +121,9 @@ void StringsBuffer_Resize(void** buffer, UInt32* elems, UInt32 elemSize, UInt32
|
||||
void StringsBuffer_Add(StringsBuffer* buffer, STRING_PURE String* text);
|
||||
void StringsBuffer_Remove(StringsBuffer* buffer, UInt32 index);
|
||||
Int32 StringsBuffer_Compare(StringsBuffer* buffer, UInt32 idxA, UInt32 idxB);
|
||||
|
||||
void WordWrap_Do(STRING_REF String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 lineLen);
|
||||
void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY);
|
||||
Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index);
|
||||
Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index);
|
||||
#endif
|
@ -11,7 +11,6 @@
|
||||
#include "ModelCache.h"
|
||||
#include "Screens.h"
|
||||
#include "Platform.h"
|
||||
#include "WordWrap.h"
|
||||
#include "ServerConnection.h"
|
||||
#include "Event.h"
|
||||
#include "Chat.h"
|
||||
@ -1211,7 +1210,6 @@ void InputWidget_Init(GuiElement* elem) {
|
||||
InputWidget* widget = (InputWidget*)elem;
|
||||
Int32 lines = widget->GetMaxLines();
|
||||
if (lines > 1) {
|
||||
/* TODO: Actually make this work */
|
||||
WordWrap_Do(&widget->Text, widget->Lines, lines, INPUTWIDGET_LEN);
|
||||
} else {
|
||||
widget->Lines[0] = widget->Text;
|
||||
|
@ -1,141 +0,0 @@
|
||||
#include "WordWrap.h"
|
||||
#include "Drawer2D.h"
|
||||
#include "Funcs.h"
|
||||
#include "Platform.h"
|
||||
#include "ErrorHandler.h"
|
||||
|
||||
#define WORDWRAP_MAX_LINES_TO_WRAP 128
|
||||
#define WORDWRAP_MAX_BUFFER_SIZE 2048
|
||||
|
||||
void WordWrap_OutputLines(String* text, String* lines, Int32* lineLens, Int32 numLines, Int32 usedLines, Int32 charsPerLine) {
|
||||
Int32 totalChars = charsPerLine * numLines, i, j;
|
||||
for (i = 0; i < totalChars; i++) {
|
||||
if (text->buffer[i] == NULL) text->buffer[i] = ' ';
|
||||
}
|
||||
|
||||
/* convert %0-f to &0-f for colour preview. */
|
||||
for (i = 0; i < totalChars - 1; i++) {
|
||||
if (text->buffer[i] == '%' && Drawer2D_ValidColCode(text->buffer[i + 1])) {
|
||||
text->buffer[i] = '&';
|
||||
}
|
||||
}
|
||||
|
||||
usedLines = max(1, usedLines);
|
||||
for (i = 0; i < usedLines; i++) {
|
||||
String* dst = &lines[i];
|
||||
UInt8* src = &text->buffer[i * charsPerLine];
|
||||
for (j = 0; j < lineLens[i]; j++) { String_Append(dst, src[j]); }
|
||||
}
|
||||
}
|
||||
|
||||
bool WordWrap_IsWrapper(UInt8 c) {
|
||||
return c == NULL || c == ' ' || c == '-' || c == '>'
|
||||
|| c == '<' || c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
Int32 WordWrap_WrapLine(String* text, Int32 index, Int32 lineSize) {
|
||||
Int32 lineEnd = index + (lineSize - 1), i, j;
|
||||
/* wrap - but we don't want to wrap if the entire line is filled. */
|
||||
for (i = lineEnd; i >= index + 1; i--) {
|
||||
if (!WordWrap_IsWrapper(text->buffer[i])) continue;
|
||||
|
||||
for (j = lineEnd; j >= i + 1; j--) {
|
||||
UInt8 c = text->buffer[j];
|
||||
String_InsertAt(text, index + lineSize, c);
|
||||
text->buffer[j] = ' ';
|
||||
}
|
||||
return (i + 1) - index;
|
||||
}
|
||||
return lineSize;
|
||||
}
|
||||
|
||||
void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 maxPerLine) {
|
||||
Int32 len = text->length, i;
|
||||
Int32 lineLens[WORDWRAP_MAX_LINES_TO_WRAP];
|
||||
for (i = 0; i < numLines; i++) {
|
||||
String_Clear(&lines[i]);
|
||||
lineLens[i] = 0;
|
||||
}
|
||||
|
||||
/* Need to make a copy because we mutate the characters. */
|
||||
UInt8 copyBuffer[String_BufferSize(WORDWRAP_MAX_BUFFER_SIZE)];
|
||||
String copy = String_InitAndClearArray(copyBuffer);
|
||||
String_AppendString(©, text);
|
||||
|
||||
Int32 usedLines = 0, index, totalChars = maxPerLine * numLines;
|
||||
for (index = 0; index < totalChars; index += maxPerLine) {
|
||||
if (copy.buffer[index] == NULL) break;
|
||||
usedLines++;
|
||||
Int32 lineEnd = index + (maxPerLine - 1), nextStart = lineEnd + 1;
|
||||
|
||||
/* Do we need word wrapping? */
|
||||
bool needWrap = !WordWrap_IsWrapper(copy.buffer[lineEnd])
|
||||
&& nextStart < totalChars && !WordWrap_IsWrapper(copy.buffer[nextStart]);
|
||||
Int32 wrappedLen = needWrap ? WordWrap_WrapLine(©, index, maxPerLine) : maxPerLine;
|
||||
|
||||
/* Calculate the maximum size of this line */
|
||||
Int32 lineLen = maxPerLine, i;
|
||||
for (i = lineEnd; i >= index; i--) {
|
||||
if (copy.buffer[i] != NULL) break;
|
||||
lineLen--;
|
||||
}
|
||||
lineLens[index / maxPerLine] = min(lineLen, wrappedLen);
|
||||
}
|
||||
|
||||
/* Output the used lines */
|
||||
WordWrap_OutputLines(©, lines, lineLens, numLines, usedLines, maxPerLine);
|
||||
}
|
||||
|
||||
/* Calculates where the given raw index is located in the wrapped lines. */
|
||||
void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY) {
|
||||
if (index == -1) index = Int32_MaxValue;
|
||||
Int32 offset = 0; *coordX = -1; *coordY = 0;
|
||||
|
||||
for (Int32 y = 0; y < numLines; y++) {
|
||||
Int32 lineLength = lines[y].length;
|
||||
if (lineLength == 0) break;
|
||||
|
||||
*coordY = y;
|
||||
if (index < offset + lineLength) {
|
||||
*coordX = index - offset; break;
|
||||
}
|
||||
offset += lineLength;
|
||||
}
|
||||
if (*coordX == -1) *coordX = lines[*coordY].length;
|
||||
}
|
||||
|
||||
Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index) {
|
||||
if (index <= 0) return 0;
|
||||
if (index >= text->length) {
|
||||
ErrorHandler_Fail("WordWrap_GetBackLength - index past end of string");
|
||||
}
|
||||
|
||||
Int32 start = index;
|
||||
bool lookingSpace = text->buffer[index] == ' ';
|
||||
/* go back to the end of the previous word */
|
||||
if (lookingSpace) {
|
||||
while (index > 0 && text->buffer[index] == ' ') { index--; }
|
||||
}
|
||||
|
||||
/* go back to the start of the current word */
|
||||
while (index > 0 && text->buffer[index] != ' ') { index--; }
|
||||
return start - index;
|
||||
}
|
||||
|
||||
Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index) {
|
||||
if (index == -1) return 0;
|
||||
if (index >= text->length) {
|
||||
ErrorHandler_Fail("WordWrap_GetForwardLength - index past end of string");
|
||||
}
|
||||
|
||||
Int32 start = index, length = text->length;
|
||||
bool lookingLetter = text->buffer[index] != ' ';
|
||||
/* go forward to the end of the current word */
|
||||
if (lookingLetter) {
|
||||
while (index < length && text->buffer[index] != ' ') { index++; }
|
||||
}
|
||||
|
||||
/* go forward to the start of the next word */
|
||||
while (index < length && text->buffer[index] == ' ') { index++; }
|
||||
return index - start;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#ifndef CC_WORDWRAP_H
|
||||
#define CC_WORDWRAP_H
|
||||
#include "String.h"
|
||||
/* Allows wrapping a single line of text into multiple lines.
|
||||
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
||||
*/
|
||||
|
||||
void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 maxPerLine);
|
||||
/* Calculates where the given raw index is located in the wrapped lines. */
|
||||
void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY);
|
||||
Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index);
|
||||
Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index);
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user