Port more of widgets to C.

This commit is contained in:
UnknownShadow200 2017-10-16 12:50:22 +11:00
parent 353be8b037
commit bbc88e252f
20 changed files with 769 additions and 509 deletions

View file

@ -23,7 +23,7 @@ namespace ClassicalSharp.Gui.Screens {
TextGroupWidget status, bottomRight, normalChat, clientStatus;
bool suppressNextPress = true;
int chatIndex;
AltTextInputWidget altText;
SpecialInputWidget altText;
Font chatFont, chatUrlFont, announcementFont;
// needed for lost contexts, to restore chat typed in
@ -51,7 +51,7 @@ namespace ClassicalSharp.Gui.Screens {
void ConstructWidgets() {
input = new ChatInputWidget(game, chatFont)
.SetLocation(Anchor.LeftOrTop, Anchor.BottomOrRight, 5, 5);
altText = new AltTextInputWidget(game, chatFont, input);
altText = new SpecialInputWidget(game, chatFont, input);
altText.Init();
UpdateAltTextY();

View file

@ -1,125 +0,0 @@
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using System.Drawing;
namespace ClassicalSharp.Gui.Widgets {
public sealed partial class AltTextInputWidget : Widget {
Element[] elements;
void InitData() {
elements = new Element[] {
new Element("Colours", 10, 4, GetColourString()),
new Element("Math", 16, 1, "ƒ½¼αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°√ⁿ²"),
new Element("Line/Box", 17, 1, "░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀■"),
new Element("Letters", 17, 1, "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜáíóúñÑ"),
new Element("Other", 16, 1, "☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼⌂¢£¥₧ªº¿⌐¬¡«»∙·"),
};
}
string GetColourString() {
int count = 0;
for (int i = ' '; i <= '~'; i++) {
if (i >= 'A' && i <= 'F') continue;
if (IDrawer2D.Cols[i].A > 0) count++;
}
StringBuffer buffer = new StringBuffer(count * 4);
int index = 0;
for (int i = ' '; i <= '~'; i++) {
if (i >= 'A' && i <= 'F') continue;
if (IDrawer2D.Cols[i].A == 0) continue;
buffer.Append(ref index, '&').Append(ref index, (char)i)
.Append(ref index, '%').Append(ref index, (char)i);
}
return buffer.ToString();
}
struct Element {
public string Title;
public Size TitleSize;
public string Contents;
public int ItemsPerRow;
public int CharsPerItem;
public Element(string title, int itemsPerRow, int charsPerItem, string contents) {
Title = title;
TitleSize = Size.Empty;
Contents = contents;
ItemsPerRow = itemsPerRow;
CharsPerItem = charsPerItem;
}
}
unsafe void MeasureContentSizes(Element e, Font font, Size* sizes) {
string s = new String('\0', e.CharsPerItem);
DrawTextArgs args = new DrawTextArgs(s, font, false);
// avoid allocating temporary strings here
fixed(char* ptr = s) {
for (int i = 0; i < e.Contents.Length; i += e.CharsPerItem) {
for (int j = 0; j < e.CharsPerItem; j++)
ptr[j] = e.Contents[i + j];
sizes[i / e.CharsPerItem] = game.Drawer2D.MeasureSize(ref args);
}
}
}
unsafe Size CalculateContentSize(Element e, Size* sizes, out Size elemSize) {
elemSize = Size.Empty;
for (int i = 0; i < e.Contents.Length; i += e.CharsPerItem)
elemSize.Width = Math.Max(elemSize.Width, sizes[i / e.CharsPerItem].Width);
elemSize.Width += contentSpacing;
elemSize.Height = sizes[0].Height + contentSpacing;
int rows = Utils.CeilDiv(e.Contents.Length / e.CharsPerItem, e.ItemsPerRow);
return new Size(elemSize.Width * e.ItemsPerRow, elemSize.Height * rows);
}
const int titleSpacing = 10, contentSpacing = 5;
int MeasureTitles(Font font) {
int totalWidth = 0;
DrawTextArgs args = new DrawTextArgs(null, font, false);
for (int i = 0; i < elements.Length; i++) {
args.Text = elements[i].Title;
elements[i].TitleSize = game.Drawer2D.MeasureSize(ref args);
elements[i].TitleSize.Width += titleSpacing;
totalWidth += elements[i].TitleSize.Width;
}
return totalWidth;
}
void DrawTitles(IDrawer2D drawer, Font font) {
int x = 0;
DrawTextArgs args = new DrawTextArgs(null, font, false);
for (int i = 0; i < elements.Length; i++) {
args.Text = elements[i].Title;
FastColour col = i == selectedIndex ? new FastColour(30, 30, 30, 200) :
new FastColour(0, 0, 0, 127);;
Size size = elements[i].TitleSize;
drawer.Clear(col, x, 0, size.Width, size.Height);
drawer.DrawText(ref args, x + titleSpacing / 2, 0);
x += size.Width;
}
}
unsafe void DrawContent(IDrawer2D drawer, Font font, Element e, int yOffset) {
string s = new String('\0', e.CharsPerItem);
int wrap = e.ItemsPerRow;
DrawTextArgs args = new DrawTextArgs(s, font, false);
fixed(char* ptr = s) {
for (int i = 0; i < e.Contents.Length; i += e.CharsPerItem) {
for (int j = 0; j < e.CharsPerItem; j++)
ptr[j] = e.Contents[i + j];
int item = i / e.CharsPerItem;
int x = (item % wrap) * elementSize.Width, y = (item / wrap) * elementSize.Height;
y += yOffset;
drawer.DrawText(ref args, x, y);
}
}
}
}
}

View file

@ -1,119 +0,0 @@
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using System.Drawing;
using OpenTK.Input;
#if ANDROID
using Android.Graphics;
#endif
namespace ClassicalSharp.Gui.Widgets {
public sealed partial class AltTextInputWidget : Widget {
public AltTextInputWidget(Game game, Font font, InputWidget input) : base(game) {
HorizontalAnchor = Anchor.LeftOrTop;
VerticalAnchor = Anchor.BottomOrRight;
this.font = font;
this.input = input;
Active = false;
}
public void UpdateColours() {
elements[0] = new Element("Colours", 10, 4, GetColourString());
Redraw();
SetActive(Active);
}
public Texture texture;
readonly Font font;
InputWidget input;
Size elementSize;
public void SetActive(bool active) {
Active = active;
Height = active ? (int)texture.Height : 0;
}
public override void Render(double delta) {
texture.Render(gfx);
}
public override void Init() {
X = 5; Y = 5;
InitData();
Redraw();
SetActive(Active);
}
public void Redraw() {
Make(elements[selectedIndex], font);
Width = texture.Width;
Height = texture.Height;
}
unsafe void Make(Element e, Font font) {
Size* sizes = stackalloc Size[e.Contents.Length / e.CharsPerItem];
MeasureContentSizes(e, font, sizes);
Size bodySize = CalculateContentSize(e, sizes, out elementSize);
int titleWidth = MeasureTitles(font), titleHeight = elements[0].TitleSize.Height;
Size size = new Size(Math.Max(bodySize.Width, titleWidth), bodySize.Height + titleHeight);
game.Graphics.DeleteTexture(ref texture);
using (Bitmap bmp = IDrawer2D.CreatePow2Bitmap(size))
using (IDrawer2D drawer = game.Drawer2D)
{
drawer.SetBitmap(bmp);
DrawTitles(drawer, font);
drawer.Clear(new FastColour(30, 30, 30, 200), 0, titleHeight,
size.Width, bodySize.Height);
DrawContent(drawer, font, e, titleHeight);
texture = drawer.Make2DTexture(bmp, size, X, Y);
}
}
int selectedIndex = 0;
public override bool HandlesMouseClick(int mouseX, int mouseY, MouseButton button) {
mouseX -= X; mouseY -= Y;
if (IntersectsHeader(mouseX, mouseY)) {
Redraw();
} else {
IntersectsBody(mouseX, mouseY);
}
return true;
}
bool IntersectsHeader(int widgetX, int widgetY) {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
for (int i = 0; i < elements.Length; i++) {
Size size = elements[i].TitleSize;
bounds.Width = size.Width; bounds.Height = size.Height;
if (bounds.Contains(widgetX, widgetY)) {
selectedIndex = i;
return true;
}
bounds.X += size.Width;
}
return false;
}
void IntersectsBody(int widgetX, int widgetY) {
widgetY -= elements[0].TitleSize.Height;
widgetX /= elementSize.Width; widgetY /= elementSize.Height;
Element e = elements[selectedIndex];
int index = widgetY * e.ItemsPerRow + widgetX;
if (index * e.CharsPerItem < e.Contents.Length) {
if (selectedIndex == 0) {
// TODO: need to insert characters that don't affect caret index, adjust caret colour
input.Append(e.Contents[index * e.CharsPerItem]);
input.Append(e.Contents[index * e.CharsPerItem + 1]);
} else {
input.Append(e.Contents[index]);
}
}
}
public override void Dispose() {
gfx.DeleteTexture(ref texture);
}
}
}

View file

@ -14,6 +14,7 @@ namespace ClassicalSharp.Gui.Widgets {
public ChatInputWidget(Game game, Font font) : base(game, font) {
typingLogPos = game.Chat.InputLog.Count; // Index of newest entry + 1.
ShowCaret = true;
Padding = 5;
}
static FastColour backColour = new FastColour(0, 0, 0, 127);
@ -23,7 +24,6 @@ namespace ClassicalSharp.Gui.Widgets {
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;

View file

@ -0,0 +1,237 @@
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using System.Drawing;
using OpenTK.Input;
#if ANDROID
using Android.Graphics;
#endif
namespace ClassicalSharp.Gui.Widgets {
public sealed class SpecialInputWidget : Widget {
public SpecialInputWidget(Game game, Font font, InputWidget input) : base(game) {
HorizontalAnchor = Anchor.LeftOrTop;
VerticalAnchor = Anchor.BottomOrRight;
this.font = font;
this.input = input;
Active = false;
}
public void UpdateColours() {
elements[0] = new SpecialInputTab("Colours", 10, 4, GetColourString());
Redraw();
SetActive(Active);
}
public Texture texture;
readonly Font font;
InputWidget input;
Size elementSize;
public void SetActive(bool active) {
Active = active;
Height = active ? (int)texture.Height : 0;
}
public override void Render(double delta) {
texture.Render(gfx);
}
public override void Init() {
X = 5; Y = 5;
InitData();
Redraw();
SetActive(Active);
}
public void Redraw() {
Make(elements[selectedIndex], font);
Width = texture.Width;
Height = texture.Height;
}
unsafe void Make(SpecialInputTab e, Font font) {
Size* sizes = stackalloc Size[e.Contents.Length / e.CharsPerItem];
MeasureContentSizes(e, font, sizes);
Size bodySize = CalculateContentSize(e, sizes, out elementSize);
int titleWidth = MeasureTitles(font), titleHeight = elements[0].TitleSize.Height;
Size size = new Size(Math.Max(bodySize.Width, titleWidth), bodySize.Height + titleHeight);
game.Graphics.DeleteTexture(ref texture);
using (Bitmap bmp = IDrawer2D.CreatePow2Bitmap(size))
using (IDrawer2D drawer = game.Drawer2D)
{
drawer.SetBitmap(bmp);
DrawTitles(drawer, font);
drawer.Clear(new FastColour(30, 30, 30, 200), 0, titleHeight,
size.Width, bodySize.Height);
DrawContent(drawer, font, e, titleHeight);
texture = drawer.Make2DTexture(bmp, size, X, Y);
}
}
int selectedIndex = 0;
public override bool HandlesMouseClick(int mouseX, int mouseY, MouseButton button) {
mouseX -= X; mouseY -= Y;
if (IntersectsHeader(mouseX, mouseY)) {
Redraw();
} else {
IntersectsBody(mouseX, mouseY);
}
return true;
}
bool IntersectsHeader(int widgetX, int widgetY) {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
for (int i = 0; i < elements.Length; i++) {
Size size = elements[i].TitleSize;
bounds.Width = size.Width; bounds.Height = size.Height;
if (bounds.Contains(widgetX, widgetY)) {
selectedIndex = i;
return true;
}
bounds.X += size.Width;
}
return false;
}
void IntersectsBody(int widgetX, int widgetY) {
widgetY -= elements[0].TitleSize.Height;
widgetX /= elementSize.Width; widgetY /= elementSize.Height;
SpecialInputTab e = elements[selectedIndex];
int index = widgetY * e.ItemsPerRow + widgetX;
if (index * e.CharsPerItem < e.Contents.Length) {
if (selectedIndex == 0) {
// TODO: need to insert characters that don't affect caret index, adjust caret colour
input.Append(e.Contents[index * e.CharsPerItem]);
input.Append(e.Contents[index * e.CharsPerItem + 1]);
} else {
input.Append(e.Contents[index]);
}
}
}
public override void Dispose() {
gfx.DeleteTexture(ref texture);
}
struct SpecialInputTab {
public string Title;
public Size TitleSize;
public string Contents;
public int ItemsPerRow;
public int CharsPerItem;
public SpecialInputTab(string title, int itemsPerRow, int charsPerItem, string contents) {
Title = title;
TitleSize = Size.Empty;
Contents = contents;
ItemsPerRow = itemsPerRow;
CharsPerItem = charsPerItem;
}
}
SpecialInputTab[] elements;
void InitData() {
elements = new SpecialInputTab[] {
new SpecialInputTab("Colours", 10, 4, GetColourString()),
new SpecialInputTab("Math", 16, 1, "ƒ½¼αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°√ⁿ²"),
new SpecialInputTab("Line/Box", 17, 1, "░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀■"),
new SpecialInputTab("Letters", 17, 1, "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜáíóúñÑ"),
new SpecialInputTab("Other", 16, 1, "☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼⌂¢£¥₧ªº¿⌐¬¡«»∙·"),
};
}
string GetColourString() {
int count = 0;
for (int i = ' '; i <= '~'; i++) {
if (i >= 'A' && i <= 'F') continue;
if (IDrawer2D.Cols[i].A > 0) count++;
}
StringBuffer buffer = new StringBuffer(count * 4);
int index = 0;
for (int i = ' '; i <= '~'; i++) {
if (i >= 'A' && i <= 'F') continue;
if (IDrawer2D.Cols[i].A == 0) continue;
buffer.Append(ref index, '&').Append(ref index, (char)i)
.Append(ref index, '%').Append(ref index, (char)i);
}
return buffer.ToString();
}
unsafe void MeasureContentSizes(SpecialInputTab e, Font font, Size* sizes) {
string s = new String('\0', e.CharsPerItem);
DrawTextArgs args = new DrawTextArgs(s, font, false);
// avoid allocating temporary strings here
fixed(char* ptr = s) {
for (int i = 0; i < e.Contents.Length; i += e.CharsPerItem) {
for (int j = 0; j < e.CharsPerItem; j++)
ptr[j] = e.Contents[i + j];
sizes[i / e.CharsPerItem] = game.Drawer2D.MeasureSize(ref args);
}
}
}
unsafe Size CalculateContentSize(SpecialInputTab e, Size* sizes, out Size elemSize) {
elemSize = Size.Empty;
for (int i = 0; i < e.Contents.Length; i += e.CharsPerItem)
elemSize.Width = Math.Max(elemSize.Width, sizes[i / e.CharsPerItem].Width);
elemSize.Width += contentSpacing;
elemSize.Height = sizes[0].Height + contentSpacing;
int rows = Utils.CeilDiv(e.Contents.Length / e.CharsPerItem, e.ItemsPerRow);
return new Size(elemSize.Width * e.ItemsPerRow, elemSize.Height * rows);
}
const int titleSpacing = 10, contentSpacing = 5;
int MeasureTitles(Font font) {
int totalWidth = 0;
DrawTextArgs args = new DrawTextArgs(null, font, false);
for (int i = 0; i < elements.Length; i++) {
args.Text = elements[i].Title;
elements[i].TitleSize = game.Drawer2D.MeasureSize(ref args);
elements[i].TitleSize.Width += titleSpacing;
totalWidth += elements[i].TitleSize.Width;
}
return totalWidth;
}
void DrawTitles(IDrawer2D drawer, Font font) {
int x = 0;
DrawTextArgs args = new DrawTextArgs(null, font, false);
for (int i = 0; i < elements.Length; i++) {
args.Text = elements[i].Title;
FastColour col = i == selectedIndex ? new FastColour(30, 30, 30, 200) :
new FastColour(0, 0, 0, 127);;
Size size = elements[i].TitleSize;
drawer.Clear(col, x, 0, size.Width, size.Height);
drawer.DrawText(ref args, x + titleSpacing / 2, 0);
x += size.Width;
}
}
unsafe void DrawContent(IDrawer2D drawer, Font font, SpecialInputTab e, int yOffset) {
string s = new String('\0', e.CharsPerItem);
int wrap = e.ItemsPerRow;
DrawTextArgs args = new DrawTextArgs(s, font, false);
fixed(char* ptr = s) {
for (int i = 0; i < e.Contents.Length; i += e.CharsPerItem) {
for (int j = 0; j < e.CharsPerItem; j++)
ptr[j] = e.Contents[i + j];
int item = i / e.CharsPerItem;
int x = (item % wrap) * elementSize.Width, y = (item / wrap) * elementSize.Height;
y += yOffset;
drawer.DrawText(ref args, x, y);
}
}
}
}
}

View file

@ -1,189 +0,0 @@
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using System.Drawing;
#if ANDROID
using Android.Graphics;
#endif
namespace ClassicalSharp.Gui.Widgets {
public sealed partial class TextGroupWidget : Widget {
public void SetText(int index, string text) {
gfx.DeleteTexture(ref Textures[index]);
DrawTextArgs args = new DrawTextArgs(text, font, true);
linkData[index] = default(LinkData);
LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0;
if (!IDrawer2D.EmptyText(text)) {
Texture tex = NextToken(text, 0, ref prevFlags) == -1 ? DrawSimple(ref args) :
DrawAdvanced(ref args, index, text);
game.Drawer2D.ReducePadding(ref tex, Utils.Floor(args.Font.Size), 3);
tex.X1 = CalcPos(HorizontalAnchor, XOffset, tex.Width, game.Width);
tex.Y1 = CalcY(index, tex.Height);
Textures[index] = tex;
lines[index] = text;
} else {
int height = PlaceholderHeight[index] ? defaultHeight : 0;
int y = CalcY(index, height);
Textures[index] = new Texture(-1, 0, y, 0, height, 0, 0);
lines[index] = null;
}
UpdateDimensions();
}
Texture DrawSimple(ref DrawTextArgs args) {
return game.Drawer2D.MakeTextTexture(ref args, 0, 0);
}
unsafe Texture DrawAdvanced(ref DrawTextArgs args, int index, string text) {
LinkData data = Split(index, text);
Size total = Size.Empty;
Size* partSizes = stackalloc Size[data.parts.Length];
linkData[index] = data;
for (int i = 0; i < data.parts.Length; i++) {
args.Text = data.parts[i];
args.Font = (i & 1) == 0 ? font : underlineFont;
partSizes[i] = game.Drawer2D.MeasureSize(ref args);
total.Height = Math.Max(partSizes[i].Height, total.Height);
total.Width += partSizes[i].Width;
}
using (IDrawer2D drawer = game.Drawer2D)
using (Bitmap bmp = IDrawer2D.CreatePow2Bitmap(total))
{
drawer.SetBitmap(bmp);
int x = 0;
for (int i = 0; i < data.parts.Length; i++) {
args.Text = data.parts[i];
args.Font = (i & 1) == 0 ? font : underlineFont;
Size size = partSizes[i];
drawer.DrawText(ref args, x, 0);
data.bounds[i].X = x;
data.bounds[i].Width = size.Width;
x += size.Width;
}
return drawer.Make2DTexture(bmp, total, 0, 0);
}
}
LinkData Split(int index, string line) {
int start = 0, lastEnd = 0, count = 0;
LinkData data = default(LinkData);
data.parts = new string[GetTokensCount(index, line)];
data.urls = new string[data.parts.Length];
data.bounds = new Rectangle[data.parts.Length];
LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0;
while ((start = NextToken(line, start, ref prevFlags)) >= 0) {
int nextEnd = line.IndexOf(' ', start);
if (nextEnd == -1) {
nextEnd = line.Length;
data.flags |= LinkFlags.Continue;
}
data.AddPart(count, GetPart(line, lastEnd, start)); // word bit
data.AddPart(count + 1, GetPart(line, start, nextEnd)); // url bit
count += 2;
if ((prevFlags & LinkFlags.Append) != 0) {
string url = linkData[index - 1].LastUrl + data.urls[count - 1];
data.urls[count - 1] = url;
data.parts[count - 2] = "";
UpdatePreviousUrls(index - 1, url);
}
if ((prevFlags & LinkFlags.NewLink) != 0)
data.flags |= LinkFlags.NewLink;
start = nextEnd;
lastEnd = nextEnd;
}
if (lastEnd < line.Length)
data.AddPart(count, GetPart(line, lastEnd, line.Length)); // word bit
return data;
}
void UpdatePreviousUrls(int i, string url) {
while (i >= 0 && linkData[i].urls != null && (linkData[i].flags & LinkFlags.Continue) != 0) {
linkData[i].LastUrl = url;
if (linkData[i].urls.Length > 2 || (linkData[i].flags & LinkFlags.NewLink) != 0)
break;
i--;
}
}
string GetPart(string line, int start, int end) {
string part = line.Substring(start, end - start);
int lastCol = line.LastIndexOf('&', start, start);
// We may split up a line into say %e<word><url>
// url and word both need to have %e at the start.
if (lastCol >= 0 && IDrawer2D.ValidColCode(line,lastCol + 1)) {
part = "&" + line[lastCol + 1] + part;
}
return part;
}
int NextToken(string line, int start, ref LinkFlags prevFlags) {
bool isWrapped = start == 0 && line.StartsWith("> ");
if ((prevFlags & LinkFlags.Continue) != 0 && isWrapped) {
prevFlags = 0;
if (!Utils.IsUrlPrefix(Utils.StripColours(line), 2))
prevFlags |= LinkFlags.Append;
else
prevFlags |= LinkFlags.NewLink;
return 2;
}
prevFlags = LinkFlags.NewLink;
int nextHttp = line.IndexOf("http://", start);
int nextHttps = line.IndexOf("https://", start);
return nextHttp == -1 ? nextHttps : nextHttp;
}
int GetTokensCount(int index, string line) {
int start = 0, lastEnd = 0, count = 0;
LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0;
while ((start = NextToken(line, start, ref prevFlags)) >= 0) {
int nextEnd = line.IndexOf(' ', start);
if (nextEnd == -1)
nextEnd = line.Length;
start = nextEnd;
lastEnd = nextEnd;
count += 2;
}
if (lastEnd < line.Length) count++;
return count;
}
struct LinkData {
public Rectangle[] bounds;
public string[] parts, urls;
public LinkFlags flags;
public void AddPart(int index, string part) {
parts[index] = part;
urls[index] = part;
}
public string LastUrl {
get { return urls[parts.Length - 1]; }
set { urls[parts.Length - 1] = value; }
}
}
[Flags]
enum LinkFlags : byte {
Continue = 2, // "part1" "> part2" type urls
Append = 4, // used for internally combining "part2" and "part2"
NewLink = 8, // used to signify that part2 is a separate url from part1
}
}
}

View file

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Drawing;
namespace ClassicalSharp.Gui.Widgets {
public sealed partial class TextGroupWidget : Widget {
public sealed class TextGroupWidget : Widget {
public TextGroupWidget(Game game, int elementsCount, Font font, Font underlineFont) : base(game) {
ElementsCount = elementsCount;
@ -157,5 +157,184 @@ namespace ClassicalSharp.Gui.Widgets {
}
return null;
}
public void SetText(int index, string text) {
gfx.DeleteTexture(ref Textures[index]);
DrawTextArgs args = new DrawTextArgs(text, font, true);
linkData[index] = default(LinkData);
LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0;
if (!IDrawer2D.EmptyText(text)) {
Texture tex = NextToken(text, 0, ref prevFlags) == -1 ? DrawSimple(ref args) :
DrawAdvanced(ref args, index, text);
game.Drawer2D.ReducePadding(ref tex, Utils.Floor(args.Font.Size), 3);
tex.X1 = CalcPos(HorizontalAnchor, XOffset, tex.Width, game.Width);
tex.Y1 = CalcY(index, tex.Height);
Textures[index] = tex;
lines[index] = text;
} else {
int height = PlaceholderHeight[index] ? defaultHeight : 0;
int y = CalcY(index, height);
Textures[index] = new Texture(-1, 0, y, 0, height, 0, 0);
lines[index] = null;
}
UpdateDimensions();
}
Texture DrawSimple(ref DrawTextArgs args) {
return game.Drawer2D.MakeTextTexture(ref args, 0, 0);
}
unsafe Texture DrawAdvanced(ref DrawTextArgs args, int index, string text) {
LinkData data = Split(index, text);
Size total = Size.Empty;
Size* partSizes = stackalloc Size[data.parts.Length];
linkData[index] = data;
for (int i = 0; i < data.parts.Length; i++) {
args.Text = data.parts[i];
args.Font = (i & 1) == 0 ? font : underlineFont;
partSizes[i] = game.Drawer2D.MeasureSize(ref args);
total.Height = Math.Max(partSizes[i].Height, total.Height);
total.Width += partSizes[i].Width;
}
using (IDrawer2D drawer = game.Drawer2D)
using (Bitmap bmp = IDrawer2D.CreatePow2Bitmap(total))
{
drawer.SetBitmap(bmp);
int x = 0;
for (int i = 0; i < data.parts.Length; i++) {
args.Text = data.parts[i];
args.Font = (i & 1) == 0 ? font : underlineFont;
Size size = partSizes[i];
drawer.DrawText(ref args, x, 0);
data.bounds[i].X = x;
data.bounds[i].Width = size.Width;
x += size.Width;
}
return drawer.Make2DTexture(bmp, total, 0, 0);
}
}
LinkData Split(int index, string line) {
int start = 0, lastEnd = 0, count = 0;
LinkData data = default(LinkData);
data.parts = new string[GetTokensCount(index, line)];
data.urls = new string[data.parts.Length];
data.bounds = new Rectangle[data.parts.Length];
LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0;
while ((start = NextToken(line, start, ref prevFlags)) >= 0) {
int nextEnd = line.IndexOf(' ', start);
if (nextEnd == -1) {
nextEnd = line.Length;
data.flags |= LinkFlags.Continue;
}
data.AddPart(count, GetPart(line, lastEnd, start)); // word bit
data.AddPart(count + 1, GetPart(line, start, nextEnd)); // url bit
count += 2;
if ((prevFlags & LinkFlags.Append) != 0) {
string url = linkData[index - 1].LastUrl + data.urls[count - 1];
data.urls[count - 1] = url;
data.parts[count - 2] = "";
UpdatePreviousUrls(index - 1, url);
}
if ((prevFlags & LinkFlags.NewLink) != 0)
data.flags |= LinkFlags.NewLink;
start = nextEnd;
lastEnd = nextEnd;
}
if (lastEnd < line.Length)
data.AddPart(count, GetPart(line, lastEnd, line.Length)); // word bit
return data;
}
void UpdatePreviousUrls(int i, string url) {
while (i >= 0 && linkData[i].urls != null && (linkData[i].flags & LinkFlags.Continue) != 0) {
linkData[i].LastUrl = url;
if (linkData[i].urls.Length > 2 || (linkData[i].flags & LinkFlags.NewLink) != 0)
break;
i--;
}
}
string GetPart(string line, int start, int end) {
string part = line.Substring(start, end - start);
int lastCol = line.LastIndexOf('&', start, start);
// We may split up a line into say %e<word><url>
// url and word both need to have %e at the start.
if (lastCol >= 0 && IDrawer2D.ValidColCode(line,lastCol + 1)) {
part = "&" + line[lastCol + 1] + part;
}
return part;
}
int NextToken(string line, int start, ref LinkFlags prevFlags) {
bool isWrapped = start == 0 && line.StartsWith("> ");
if ((prevFlags & LinkFlags.Continue) != 0 && isWrapped) {
prevFlags = 0;
if (!Utils.IsUrlPrefix(Utils.StripColours(line), 2))
prevFlags |= LinkFlags.Append;
else
prevFlags |= LinkFlags.NewLink;
return 2;
}
prevFlags = LinkFlags.NewLink;
int nextHttp = line.IndexOf("http://", start);
int nextHttps = line.IndexOf("https://", start);
return nextHttp == -1 ? nextHttps : nextHttp;
}
int GetTokensCount(int index, string line) {
int start = 0, lastEnd = 0, count = 0;
LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0;
while ((start = NextToken(line, start, ref prevFlags)) >= 0) {
int nextEnd = line.IndexOf(' ', start);
if (nextEnd == -1)
nextEnd = line.Length;
start = nextEnd;
lastEnd = nextEnd;
count += 2;
}
if (lastEnd < line.Length) count++;
return count;
}
struct LinkData {
public Rectangle[] bounds;
public string[] parts, urls;
public LinkFlags flags;
public void AddPart(int index, string part) {
parts[index] = part;
urls[index] = part;
}
public string LastUrl {
get { return urls[parts.Length - 1]; }
set { urls[parts.Length - 1] = value; }
}
}
[Flags]
enum LinkFlags : byte {
Continue = 2, // "part1" "> part2" type urls
Append = 4, // used for internally combining "part2" and "part2"
NewLink = 8, // used to signify that part2 is a separate url from part1
}
}
}

View file

@ -55,7 +55,7 @@ namespace ClassicalSharp.Gui.Widgets {
/// <summary> The horizontal offset (in pixels) from the start of the box background
/// to the beginning of the input texture. </summary>
public abstract int Padding { get; }
public int Padding;
/// <summary> Whether a caret should be drawn at the position characters
/// are inserted/deleted from the input text. </summary>

View file

@ -17,6 +17,7 @@ namespace ClassicalSharp.Gui.Widgets {
input.MinWidth = width;
input.MinHeight = height;
input.Validator = validator;
input.Padding = 3;
input.Init();
input.Append(text);
@ -29,7 +30,6 @@ namespace ClassicalSharp.Gui.Widgets {
public override int MaxLines { get { return 1; } }
public override string Prefix { get { return null; } }
public override int Padding { get { return 3; } }
public override int MaxCharsPerLine { get { return Utils.StringLength; } }
public override void Render(double delta) {

View file

@ -120,11 +120,9 @@
<Compile Include="2D\Utils\FastColour.cs" />
<Compile Include="2D\Utils\TextAtlas.cs" />
<Compile Include="2D\Widgets\HotbarWidget.cs" />
<Compile Include="2D\Widgets\Chat\AltTextInputWidget.Types.cs" />
<Compile Include="2D\Widgets\Chat\ChatInputWidget.cs" />
<Compile Include="2D\Widgets\Chat\AltTextInputWidget.cs" />
<Compile Include="2D\Widgets\Chat\SpecialInputWidget.cs" />
<Compile Include="2D\Widgets\Chat\TextGroupWidget.cs" />
<Compile Include="2D\Widgets\Chat\TextGroupWidget.Formatter.cs" />
<Compile Include="2D\Widgets\InputWidget.cs" />
<Compile Include="2D\Widgets\Menu\MenuInputValidator.cs" />
<Compile Include="2D\Widgets\Menu\MenuInputWidget.cs" />

View file

@ -5,17 +5,17 @@
#include "TerrainAtlas.h"
#include "Player.h"
TextureLoc Block_TopTex[Block_CpeCount] = { 0, 1, 0, 2, 16, 4, 15, 17, 14, 14,
TextureLoc Block_TopTex[BLOCK_CPE_COUNT] = { 0, 1, 0, 2, 16, 4, 15, 17, 14, 14,
30, 30, 18, 19, 32, 33, 34, 21, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 24, 23, 6, 6, 7, 9, 4,
36, 37, 16, 11, 25, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 26, 53, 52, };
TextureLoc Block_SideTex[Block_CpeCount] = { 0, 1, 3, 2, 16, 4, 15, 17, 14, 14,
TextureLoc Block_SideTex[BLOCK_CPE_COUNT] = { 0, 1, 3, 2, 16, 4, 15, 17, 14, 14,
30, 30, 18, 19, 32, 33, 34, 20, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 40, 39, 5, 5, 7, 8, 35,
36, 37, 16, 11, 41, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 42, 53, 52, };
TextureLoc Block_BottomTex[Block_CpeCount] = { 0, 1, 2, 2, 16, 4, 15, 17, 14, 14,
TextureLoc Block_BottomTex[BLOCK_CPE_COUNT] = { 0, 1, 2, 2, 16, 4, 15, 17, 14, 14,
30, 30, 18, 19, 32, 33, 34, 21, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 56, 55, 6, 6, 7, 10, 4,
36, 37, 16, 11, 57, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 58, 53, 52 };
@ -33,7 +33,7 @@ void Block_Init(void) {
}
Int32 block;
for (block = BlockID_Air; block < Block_Count; block++) {
for (block = BlockID_Air; block < BLOCK_COUNT; block++) {
Block_ResetProps((BlockID)block);
}
Block_UpdateCullingAll();
@ -41,7 +41,7 @@ void Block_Init(void) {
void Block_SetDefaultPerms(void) {
Int32 block;
for (block = BlockID_Air; block <= Block_MaxDefined; block++) {
for (block = BlockID_Air; block <= BLOCK_MAX_DEFINED; block++) {
Block_CanPlace[block] = true;
Block_CanDelete[block] = true;
}
@ -101,12 +101,12 @@ String Block_DefaultName(BlockID block) {
#if USE16_BIT
if (block >= 256) return "ID " + block;
#endif
if (block >= Block_CpeCount) {
if (block >= BLOCK_CPE_COUNT) {
String invalid = String_FromConstant("Invalid");
return invalid;
}
String blockNames = String_FromConstant(Block_RawNames);
String blockNames = String_FromConstant(BLOCK_RAW_NAMES);
/* Find start and end of this particular block name. */
Int32 start = 0, i;
for (i = 0; i < block; i++) {
@ -146,7 +146,7 @@ void Block_ResetProps(BlockID block) {
Block_CalcRenderBounds(block);
Block_LightOffset[block] = Block_CalcLightOffset(block);
if (block >= Block_CpeCount) {
if (block >= BLOCK_CPE_COUNT) {
#if USE16_BIT
/* give some random texture ids */
Block_SetTex((block * 10 + (block % 7) + 20) % 80, Face_YMax, block);
@ -166,7 +166,7 @@ void Block_ResetProps(BlockID block) {
Int32 Block_FindID(STRING_TRANSIENT String* name) {
Int32 block;
for (block = BlockID_Air; block < Block_Count; block++) {
for (block = BlockID_Air; block < BLOCK_COUNT; block++) {
if (String_CaselessEquals(&Block_Name[block], name)) return block;
}
return -1;
@ -258,7 +258,7 @@ UInt8 Block_CalcLightOffset(BlockID block) {
void Block_RecalculateSpriteBB(void) {
Int32 block;
for (block = BlockID_Air; block < Block_Count; block++) {
for (block = BlockID_Air; block < BLOCK_COUNT; block++) {
if (Block_Draw[block] != DrawType_Sprite) continue;
Block_RecalculateBB((BlockID)block);
@ -381,11 +381,11 @@ void Block_CalcCulling(BlockID block, BlockID other) {
void Block_UpdateCullingAll(void) {
Int32 block, neighbour;
for (block = BlockID_Air; block < Block_Count; block++)
for (block = BlockID_Air; block < BLOCK_COUNT; block++)
Block_CanStretch[block] = 0x3F;
for (block = BlockID_Air; block < Block_Count; block++) {
for (neighbour = BlockID_Air; neighbour < Block_Count; neighbour++) {
for (block = BlockID_Air; block < BLOCK_COUNT; block++) {
for (neighbour = BlockID_Air; neighbour < BLOCK_COUNT; neighbour++) {
Block_CalcCulling((BlockID)block, (BlockID)neighbour);
}
}
@ -395,7 +395,7 @@ void Block_UpdateCulling(BlockID block) {
Block_CanStretch[block] = 0x3F;
Int32 other;
for (other = BlockID_Air; other < Block_Count; other++) {
for (other = BlockID_Air; other < BLOCK_COUNT; other++) {
Block_CalcCulling(block, (BlockID)other);
Block_CalcCulling((BlockID)other, block);
}
@ -427,8 +427,8 @@ bool Block_IsHidden(BlockID block, BlockID other) {
void Block_SetHidden(BlockID block, BlockID other, Face face, bool value) {
value = Block_IsHidden(block, other) && Block_FaceOccluded(block, other, face) && value;
Int32 bit = value ? 1 : 0;
Block_Hidden[block * Block_Count + other] &= (UInt8)~(1 << face);
Block_Hidden[block * Block_Count + other] |= (UInt8)(bit << face);
Block_Hidden[block * BLOCK_COUNT + other] &= (UInt8)~(1 << face);
Block_Hidden[block * BLOCK_COUNT + other] |= (UInt8)(bit << face);
}
bool Block_IsFaceHidden(BlockID block, BlockID other, Face face) {

View file

@ -60,43 +60,43 @@ typedef UInt8 CollideType;
#define CollideType_LiquidLava 6
/* Array of block names. */
UInt8 Block_NamesBuffer[String_BufferSize(STRING_SIZE) * Block_Count];
UInt8 Block_NamesBuffer[String_BufferSize(STRING_SIZE) * BLOCK_COUNT];
/* Index of given block name. */
#define Block_NamePtr(i) &Block_NamesBuffer[String_BufferSize(STRING_SIZE) * i]
/* Gets whether the given block stops sunlight. */
bool Block_BlocksLight[Block_Count];
bool Block_BlocksLight[BLOCK_COUNT];
/* Gets whether the given block should draw all its faces in a full white colour. */
bool Block_FullBright[Block_Count];
bool Block_FullBright[BLOCK_COUNT];
/* Gets the name of the given block, or 'Invalid' if the block is not defined. */
String Block_Name[Block_Count];
String Block_Name[BLOCK_COUNT];
/* Gets the custom fog colour that should be used when the player is standing within this block.
Note that this is only used for exponential fog mode. */
PackedCol Block_FogColour[Block_Count];
PackedCol Block_FogColour[BLOCK_COUNT];
/* Gets the fog density for the given block.
A value of 0 means this block does not apply fog.*/
Real32 Block_FogDensity[Block_Count];
Real32 Block_FogDensity[BLOCK_COUNT];
/* Gets the basic collision type for the given block. */
CollideType Block_Collide[Block_Count];
CollideType Block_Collide[BLOCK_COUNT];
/* Gets the action performed when colliding with the given block. */
CollideType Block_ExtendedCollide[Block_Count];
CollideType Block_ExtendedCollide[BLOCK_COUNT];
/* Speed modifier when colliding (or standing on for solid collide type) with the given block. */
Real32 Block_SpeedMultiplier[Block_Count];
Real32 Block_SpeedMultiplier[BLOCK_COUNT];
/* Light offset of each block, as bitflags of 1 per face. */
UInt8 Block_LightOffset[Block_Count];
UInt8 Block_LightOffset[BLOCK_COUNT];
/* Gets the DrawType for the given block. */
DrawType Block_Draw[Block_Count];
DrawType Block_Draw[BLOCK_COUNT];
/* Gets whether the given block has an opaque draw type and is also a full tile block.
Full tile block means Min of (0, 0, 0) and max of (1, 1, 1).*/
bool Block_FullOpaque[Block_Count];
UInt32 DefinedCustomBlocks[Block_Count >> 5];
bool Block_FullOpaque[BLOCK_COUNT];
UInt32 DefinedCustomBlocks[BLOCK_COUNT >> 5];
/* Gets the dig sound ID for the given block. */
SoundType Block_DigSounds[Block_Count];
SoundType Block_DigSounds[BLOCK_COUNT];
/* Gets the step sound ID for the given block. */
SoundType Block_StepSounds[Block_Count];
SoundType Block_StepSounds[BLOCK_COUNT];
/* Gets whether the given block has a tinting colour applied to it when rendered.
The tinting colour used is the block's fog colour. */
bool Block_Tinted[Block_Count];
bool Block_Tinted[BLOCK_COUNT];
#define Block_Tint(col, block)\
if (Block_Tinted[block]) {\
@ -108,26 +108,26 @@ if (Block_Tinted[block]) {\
/* Min corner of a block. */
Vector3 Block_MinBB[Block_Count];
Vector3 Block_MinBB[BLOCK_COUNT];
/* Max corner of a block. */
Vector3 Block_MaxBB[Block_Count];
Vector3 Block_MaxBB[BLOCK_COUNT];
/* Rendered min corner of a block. */
Vector3 Block_RenderMinBB[Block_Count];
Vector3 Block_RenderMinBB[BLOCK_COUNT];
/* Rendered max corner of a block. */
Vector3 Block_RenderMaxBB[Block_Count];
Vector3 Block_RenderMaxBB[BLOCK_COUNT];
#define Block_TexturesCount Block_Count * Face_Count
#define Block_TexturesCount BLOCK_COUNT * Face_Count
/* Raw texture ids of each face of each block. */
TextureLoc Block_Textures[Block_TexturesCount];
/* Returns whether player is allowed to place the given block. */
bool Block_CanPlace[Block_Count];
bool Block_CanPlace[BLOCK_COUNT];
/* Returns whether player is allowed to delete the given block. */
bool Block_CanDelete[Block_Count];
bool Block_CanDelete[BLOCK_COUNT];
/* Gets a bit flags of faces hidden of two neighbouring blocks. */
UInt8 Block_Hidden[Block_Count * Block_Count];
UInt8 Block_Hidden[BLOCK_COUNT * BLOCK_COUNT];
/* Gets a bit flags of which faces of a block can stretch with greedy meshing. */
UInt8 Block_CanStretch[Block_Count];
UInt8 Block_CanStretch[BLOCK_COUNT];
/* Recalculates the initial properties and culling states for all blocks. */
void Block_Reset(void);

View file

@ -75,29 +75,26 @@
#define BlockID_StoneBrick 65
/* Max block ID used in original classic */
#define Block_MaxOriginal BlockID_Obsidian
#define BLOCK_MAX_ORIGINAL BlockID_Obsidian
/* Number of blocks in original classic. */
#define Block_OriginalCount (Block_MaxOriginal + 1)
#define BLOCK_ORIGINAL_COUNT (BLOCK_MAX_ORIGINAL + 1)
/* Max block ID used in original classic plus CPE blocks. */
#define Block_MaxCpe BlockID_StoneBrick
#define BLOCK_MAX_CPE BlockID_StoneBrick
/* Number of blocks in original classic plus CPE blocks. */
#define Block_CpeCount (Block_MaxCpe + 1)
#define BLOCK_CPE_COUNT (BLOCK_MAX_CPE + 1)
#if USE16_BIT
#define Block_MaxDefined 0xFFF
#define BLOCK_MAX_DEFINED 0xFFF
#else
#define Block_MaxDefined 0xFF
#define BLOCK_MAX_DEFINED 0xFF
#endif
#define Block_RawNames "Air Stone Grass Dirt Cobblestone Wood Sapling Bedrock Water StillWater Lava"\
#define BLOCK_RAW_NAMES "Air Stone Grass Dirt Cobblestone Wood Sapling Bedrock Water StillWater Lava"\
" StillLava Sand Gravel GoldOre IronOre CoalOre Log Leaves Sponge Glass Red Orange Yellow Lime Green Teal"\
" Aqua Cyan Blue Indigo Violet Magenta Pink Black Gray White Dandelion Rose BrownMushroom RedMushroom Gold"\
" Iron DoubleSlab Slab Brick TNT Bookshelf MossyRocks Obsidian CobblestoneSlab Rope Sandstone Snow Fire LightPink"\
" ForestGreen Brown DeepBlue Turquoise Ice CeramicTile Magma Pillar Crate StoneBrick"
#define Block_Count (Block_MaxDefined + 1)
#define BlockID_Invalid Block_MaxDefined
#define BLOCK_COUNT (BLOCK_MAX_DEFINED + 1)
#define BlockID_Invalid BLOCK_MAX_DEFINED
#endif

View file

@ -2,7 +2,12 @@
Int32 Math_Floor(Real32 value) {
Int32 valueI = (Int32)value;
return value < valueI ? valueI - 1 : valueI;
return valueI > value ? valueI - 1 : valueI;
}
Int32 Math_Ceil(Real32 value) {
Int32 valueI = (Int32)value;
return valueI < value ? valueI + 1 : valueI;
}
Int32 Math_Log2(Int32 value) {
@ -46,4 +51,13 @@ Int32 Math_NextPowOf2(Int32 value) {
bool Math_IsPowOf2(Int32 value) {
return value != 0 && (value & (value - 1)) == 0;
}
Int32 Math_AccumulateWheelDelta(Real32* accmulator, Real32 delta) {
/* Some mice may use deltas of say (0.2, 0.2, 0.2, 0.2, 0.2) */
/* We must use rounding at final step, not at every intermediate step. */
*accmulator += delta;
Int32 steps = (Int32)accmulator;
*accmulator -= steps;
return steps;
}

View file

@ -31,6 +31,8 @@
/* Integer floor of a floating-point value. */
Int32 Math_Floor(Real32 value);
/* Integer ceiling of a floating-point value. */
Int32 Math_Ceil(Real32 value);
/* Log base 2 of given value. */
Int32 Math_Log2(Int32 value);
/* Performs rounding upwards integer division.*/
@ -48,6 +50,8 @@ Int32 Math_NextPowOf2(Int32 value);
/* Returns whether the given value is a power of 2. */
bool Math_IsPowOf2(Int32 value);
Int32 Math_AccumulateWheelDelta(Real32* accmulator, Real32 delta);
/* Returns the number of vertices needed to subdivide a quad. */
#define Math_CountVertices(axis1Len, axis2Len, axisSize) (Math_CeilDiv(axis1Len, axisSize) * Math_CeilDiv(axis2Len, axisSize) * 4)

View file

@ -19,7 +19,7 @@ void Map_ReadBlocks(Stream* stream) {
#define LVL_CUSTOMTILE ((BlockID)163)
#define LVL_CHUNKSIZE 16
UInt8 Lvl_table[256 - Block_CpeCount] = { 0, 0, 0, 0, 39, 36, 36, 10, 46, 21, 22,
UInt8 Lvl_table[256 - BLOCK_CPE_COUNT] = { 0, 0, 0, 0, 39, 36, 36, 10, 46, 21, 22,
22, 22, 22, 4, 0, 22, 21, 0, 22, 23, 24, 22, 26, 27, 28, 30, 31, 32, 33,
34, 35, 36, 22, 20, 49, 45, 1, 4, 0, 9, 11, 4, 19, 5, 17, 10, 49, 20, 1,
18, 12, 5, 25, 46, 44, 17, 49, 20, 1, 18, 12, 5, 25, 36, 34, 0, 9, 11, 46,
@ -53,10 +53,10 @@ void Lvl_ReadCustomBlocks(Stream* stream) {
void Lvl_ConvertPhysicsBlocks(void) {
UInt8 conv[256];
Int32 i;
for (i = 0; i < Block_CpeCount; i++)
for (i = 0; i < BLOCK_CPE_COUNT; i++)
conv[i] = (UInt8)i;
for (i = Block_CpeCount; i < 256; i++)
conv[i] = Lvl_table[i - Block_CpeCount];
for (i = BLOCK_CPE_COUNT; i < 256; i++)
conv[i] = Lvl_table[i - BLOCK_CPE_COUNT];
Int32 alignedBlocksSize = World_BlocksSize & ~3;
/* Bulk convert 4 blocks at once */

View file

@ -452,7 +452,7 @@ void Physics_HandleCobblestoneSlab(Int32 index, BlockID block) {
}
UInt8 physics_blocksTnt[Block_CpeCount] = {
UInt8 physics_blocksTnt[BLOCK_CPE_COUNT] = {
0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
@ -475,7 +475,7 @@ void Physics_Explode(Int32 x, Int32 y, Int32 z, Int32 power) {
index = World_Pack(xx, yy, zz);
BlockID block = World_Blocks[index];
if (block < Block_CpeCount && physics_blocksTnt[block]) continue;
if (block < BLOCK_CPE_COUNT && physics_blocksTnt[block]) continue;
Game_UpdateBlock(xx, yy, zz, BlockID_Air);
Physics_ActivateNeighbours(xx, yy, zz, index);

View file

@ -8,10 +8,10 @@
*/
typedef void (*PhysicsHandler)(Int32 index, BlockID block);
PhysicsHandler Physics_OnActivate[Block_Count];
PhysicsHandler Physics_OnRandomTick[Block_Count];
PhysicsHandler Physics_OnPlace[Block_Count];
PhysicsHandler Physics_OnDelete[Block_Count];
PhysicsHandler Physics_OnActivate[BLOCK_COUNT];
PhysicsHandler Physics_OnRandomTick[BLOCK_COUNT];
PhysicsHandler Physics_OnPlace[BLOCK_COUNT];
PhysicsHandler Physics_OnDelete[BLOCK_COUNT];
bool Physics_Enabled;
void Physics_SetEnabled(bool enabled);

View file

@ -1,6 +1,10 @@
#include "Widgets.h"
#include "GraphicsAPI.h"
#include "Drawer2D.h"
#include "GraphicsCommon.h"
#include "ExtMath.h"
#include "Funcs.h"
#include "Window.h"
void Widget_SetLocation(Widget* widget, Anchor horAnchor, Anchor verAnchor, Int32 xOffset, Int32 yOffset) {
widget->HorAnchor = horAnchor; widget->VerAnchor = verAnchor;
@ -69,4 +73,122 @@ void TextWidget_SetText(TextWidget* widget, STRING_TRANSIENT String* text) {
elem->Reposition(elem);
widget->Texture.X = elem->X; widget->Texture.Y = elem->Y;
}
}
}
#define TABLEWIDGET_MAX_ROWS_DISPLAYED 8
#define SCROLLWIDGET_WIDTH 22
#define SCROLLWIDGET_BORDER 2
#define SCROLLWIDGET_NUBS_WIDTH 3
PackedCol ScrollWidget_BackCol = { 10, 10, 10, 220 };
PackedCol ScrollWidget_BarCol = { 100, 100, 100, 220 };
PackedCol ScrollWidget_HoverCol = { 122, 122, 122, 220 };
void ScrollbarWidget_Init(GuiElement* elem) { }
void ScrollbarWidget_Free(GuiElement* elem) { }
Real32 ScrollbarWidget_GetScale(ScrollbarWidget* widget) {
Real32 rows = (Real32)widget->TotalRows;
return (widget->Base.Height - SCROLLWIDGET_BORDER * 2) / rows;
}
void ScrollbarWidget_GetScrollbarCoords(ScrollbarWidget* widget, Int32* y, Int32* height) {
Real32 scale = ScrollbarWidget_GetScale(widget);
*y = Math_Ceil(widget->ScrollY * scale) + SCROLLWIDGET_BORDER;
*height = Math_Ceil(TABLEWIDGET_MAX_ROWS_DISPLAYED * scale);
*height = min(*y + *height, widget->Base.Height - SCROLLWIDGET_BORDER) - *y;
}
void ScrollbarWidget_Render(GuiElement* elem, Real64 delta) {
ScrollbarWidget* widget = (ScrollbarWidget*)elem;
Widget* elemW = &widget->Base;
Int32 x = elemW->X, width = elemW->Width;
GfxCommon_Draw2DFlat(x, elemW->Y, width, elemW->Height, ScrollWidget_BackCol);
Int32 y, height;
ScrollbarWidget_GetScrollbarCoords(widget, &y, &height);
x += SCROLLWIDGET_BORDER; y += elemW->Y;
width -= SCROLLWIDGET_BORDER * 2;
Point2D mouse = Window_GetDesktopCursorPos();
bool hovered = mouse.Y >= y && mouse.Y < (y + height) && mouse.X >= x && mouse.X < (x + width);
PackedCol barCol = hovered ? ScrollWidget_HoverCol : ScrollWidget_BarCol;
GfxCommon_Draw2DFlat(x, y, width, height, barCol);
if (height < 20) return;
x += SCROLLWIDGET_NUBS_WIDTH; y += (height / 2);
width -= SCROLLWIDGET_NUBS_WIDTH * 2;
GfxCommon_Draw2DFlat(x, y - 1 - 4, width, SCROLLWIDGET_BORDER, ScrollWidget_BackCol);
GfxCommon_Draw2DFlat(x, y - 1, width, SCROLLWIDGET_BORDER, ScrollWidget_BackCol);
GfxCommon_Draw2DFlat(x, y - 1 + 4, width, SCROLLWIDGET_BORDER, ScrollWidget_BackCol);
}
bool ScrollbarWidget_HandlesMouseScroll(GuiElement* elem, Real32 delta) {
ScrollbarWidget* widget = (ScrollbarWidget*)elem;
Int32 steps = Math_AccumulateWheelDelta(&widget->ScrollingAcc, delta);
widget->ScrollY -= steps;
ScrollbarWidget_ClampScrollY(widget);
return true;
}
bool ScrollbarWidget_HandlesMouseMove(GuiElement* elem, Int32 x, Int32 y) {
ScrollbarWidget* widget = (ScrollbarWidget*)elem;
if (widget->DraggingMouse) {
y -= widget->Base.Y;
Real32 scale = ScrollbarWidget_GetScale(widget);
widget->ScrollY = (Int32)((y - widget->MouseOffset) / scale);
ScrollbarWidget_ClampScrollY(widget);
return true;
}
return false;
}
bool ScrollbarWidget_HandlesMouseClick(GuiElement* elem, Int32 x, Int32 y, MouseButton btn) {
ScrollbarWidget* widget = (ScrollbarWidget*)elem;
if (widget->DraggingMouse) return true;
if (btn != MouseButton_Left) return false;
if (x < widget->Base.X || x >= widget->Base.X + widget->Base.Width) return false;
y -= widget->Base.Y;
Int32 curY, height;
ScrollbarWidget_GetScrollbarCoords(widget, &curY, &height);
if (y < curY) {
widget->ScrollY -= TABLEWIDGET_MAX_ROWS_DISPLAYED;
} else if (y >= curY + height) {
widget->ScrollY += TABLEWIDGET_MAX_ROWS_DISPLAYED;
} else {
widget->DraggingMouse = true;
widget->MouseOffset = y - curY;
}
ScrollbarWidget_ClampScrollY(widget);
return true;
}
bool ScrollbarWidget_HandlesMouseUp(GuiElement* elem, Int32 x, Int32 y, MouseButton btn) {
ScrollbarWidget* widget = (ScrollbarWidget*)elem;
widget->DraggingMouse = false;
widget->MouseOffset = 0;
return true;
}
void ScrollbarWidget_Create(ScrollbarWidget* widget) {
Widget_Init(widget);
widget->Base.Width = SCROLLWIDGET_WIDTH;
widget->Base.Base.Init = ScrollbarWidget_Init;
widget->Base.Base.Render = ScrollbarWidget_Render;
widget->Base.Base.Free = ScrollbarWidget_Free;
widget->Base.Base.HandlesMouseUp = ScrollbarWidget_HandlesMouseUp;
widget->TotalRows = 0;
widget->ScrollY = 0;
widget->ScrollingAcc = 0.0f;
widget->DraggingMouse = false;
widget->MouseOffset = 0;
}
void ScrollbarWidget_ClampScrollY(ScrollbarWidget* widget) {
Int32 maxRows = widget->TotalRows - TABLEWIDGET_MAX_ROWS_DISPLAYED;
if (widget->ScrollY >= maxRows) widget->ScrollY = maxRows;
if (widget->ScrollY < 0) widget->ScrollY = 0;
}

View file

@ -4,6 +4,8 @@
#include "Texture.h"
#include "PackedCol.h"
#include "String.h"
#include "BlockID.h"
#include "Constants.h"
void Widget_SetLocation(Widget* widget, Anchor horAnchor, Anchor verAnchor, Int32 xOffset, Int32 yOffset);
@ -26,18 +28,158 @@ typedef void (*ButtonWidget_GetValue)(STRING_TRANSIENT String* raw);
typedef struct ButtonWidget_ {
Widget Base;
Texture Texture;
String Text;
Int32 DefaultHeight;
void* Font;
String OptName;
ButtonWidget_GetValue GetValue;
ButtonWidget_SetValue SetValue;
Int32 MinWidth, MinHeight;
Int32 MinWidth, MinHeight, Metadata;
} ButtonWidget;
void ButtonWidget_Create(ButtonWidget* widget, STRING_TRANSIENT String* text, Int32 minWidth, void* font, Gui_MouseHandler onClick);
void ButtonWidget_SetText(ButtonWidget* widget, STRING_TRANSIENT String* text);
typedef struct ScrollbarWidget_ {
Widget Base;
Int32 TotalRows, ScrollY;
Real32 ScrollingAcc;
bool DraggingMouse;
Int32 MouseOffset;
} ScrollbarWidget;
void ScrollbarWidget_Create(ScrollbarWidget* widget);
void ScrollbarWidget_ClampScrollY(ScrollbarWidget* widget);
typedef struct HotbarWidget_ {
Texture SelTex, BackTex;
Real32 BarHeight, SelBlockSize, ElemSize;
Real32 BarXOffset, BorderSize;
bool AltHandled;
} HotbarWidget;
void HotbarWidget_Create(HotbarWidget* widget);
typedef struct TableWidget_ {
Widget Base;
Int32 ElementsCount, ElementsPerRow, RowsCount;
void* Font;
bool PendingClose;
Int32 SelectedIndex, BlockSize;
Real32 SelBlockExpand;
GfxResourceID VB;
BlockID Elements[BLOCK_COUNT];
ScrollbarWidget Scroll;
Texture BlockInfoTexture;
} TableWidget;
void TableWidget_Create(TableWidget* widget);
void TableWidget_SetBlockTo(TableWidget* widget, BlockID block);
void TableWidget_OnInventoryChanged(TableWidget* widget);
typedef void (*SpecialInputAppendFunc)(UInt8 c);
typedef struct SpecialInputTab_ {
String Title;
Size2D TitleSize;
String Contents;
Int32 ItemsPerRow;
Int32 CharsPerItem;
} SpecialInputTab;
void SpecialInputTab_Init(String title, Int32 itemsPerRow, Int32 charsPerItem, String contents);
typedef struct SpecialInputWidget_ {
Widget Base;
Texture texture;
void* Font;
SpecialInputAppendFunc AppendFunc;
Size2D ElementSize;
SpecialInputTab Tabs[5];
} SpecialInputWidget;
void SpecialInputWidget_Create(SpecialInputWidget* widget, void* font, SpecialInputAppendFunc appendFunc);
void SpecialInputWidget_UpdateColours(SpecialInputWidget* widget);
void SpecialInputWidget_SetActive(SpecialInputWidget* widget, bool active);
void SpecialInputWidget_Redraw(SpecialInputWidget* widget);
#define INPUTWIDGET_MAX_LINES 4
struct InputWidget_;
/* Remakes the raw texture containing all the chat lines. Also updates the dimensions of the widget. */
typedef struct InputWidget_ {
Widget Base;
void* Font;
Int32 MaxLines;
Int32 MaxCharsPerLine;
Int32 Padding;
Gui_Void RemakeTexture; /* Remakes the raw texture containing all the chat lines. Also updates dimensions. */
Gui_Void OnPressedEnter; /* Invoked when the user presses enter. */
String Text;
String Lines[INPUTWIDGET_MAX_LINES]; /* raw text of each line */
Size2D LineSizes[INPUTWIDGET_MAX_LINES]; /* size of each line in pixels */
Texture InputTex;
String Prefix;
Int32 PrefixWidth, PrefixHeight;
Texture PrefixTex;
Int32 CaretX, CaretY; /* Coordinates of caret in lines */
Int32 CaretWidth, CaretHeight;
Int32 CaretPos; /* Position of caret, -1 for at end of string. */
bool ShowCaret;
PackedCol CaretCol;
Texture CaretTex;
Real64 CaretAccumulator;
} InputWidget;
void InputWidget_Create(InputWidget* widget, void* font);
/* Calculates the sizes of each line in the text buffer. */
void InputWidget_CalculateLineSizes(InputWidget* widget);
/* Calculates the location and size of the caret character. */
void InputWidget_UpdateCaret(InputWidget* widget);
/* Clears all the characters from the text buffer. Deletes the native texture. */
void InputWidget_Clear(InputWidget* widget);
/* Appends a sequence of characters to current text buffer. May recreate the native texture. */
void InputWidget_Append(InputWidget* widget, STRING_TRANSIENT String* text);
/* Appends a single character to current text buffer. May recreate the native texture. */
void InputWidget_Append(InputWidget* widget, UInt8 c);
/* "part1" "> part2" type urls */
#define LINK_FLAG_CONTINUE 2
/* used for internally combining "part1" and "part2" */
#define LINK_FLAG_APPEND 4
/* used to signify that part2 is a separate url from part1 */
#define LINK_FLAG_NEWLINK 8
/* min size of link is 'http:// ' */
#define LINK_MAX_PER_LINE (STRING_SIZE / 8)
typedef struct LinkData_ {
Rectangle2D Bounds[LINK_MAX_PER_LINE];
String Parts[LINK_MAX_PER_LINE];
String Urls[LINK_MAX_PER_LINE];
UInt8 LinkFlags, LinksCount;
} LinkData;
#define TEXTGROUPWIDGET_MAX_LINES 30
typedef struct TextGroupWidget_ {
Widget Base;
Int32 LinesCount, DefaultHeight;
void* Font;
void* UnderlineFont;
bool PlaceholderHeight[TEXTGROUPWIDGET_MAX_LINES];
String Lines[TEXTGROUPWIDGET_MAX_LINES];
LinkData LinkDatas[TEXTGROUPWIDGET_MAX_LINES];
Texture Textures[TEXTGROUPWIDGET_MAX_LINES];
} TextGroupWidget;
void TextGroupWidget_Create(TextGroupWidget* widget, Int32 linesCount, void* font, void* underlineFont);
void TextGroupWidget_SetUsePlaceHolder(TextGroupWidget* widget, Int32 index, bool placeHolder);
void TextGroupWidget_PushUpAndReplaceLast(TextGroupWidget* widget, STRING_TRANSIENT String* text);
Int32 TextGroupWidget_GetUsedHeight();
void TextGroupWidget_GetSelected(TextGroupWidget* widget, String* dstText, Int32 mouseX, Int32 mouseY);
void TextGroupWidget_SetText(TextGroupWidget* widget, int index, String* text);
#endif