Downloads containing DialogueBox.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: NpcBunnies Cranky Mutator N/A Download file

File preview

const string DB_PREFERENCES_FILE = "DialogueBoxPreferences.asdat";
const int DB_ACTION_NONE = 0;
const int DB_ACTION_CLOSE = -1;
const int DB_ACTION_TAB = -2;
const int DB_ACTION_SCROLL_UP = -3;
const int DB_ACTION_SCROLL_DOWN = -4;
const int DB_FOCUS_OFFSET_Y = -10;

class DbResult
{
	bool activated = false;
	bool closeRequested = false;
	bool tabChanged = false;
	bool positionChanged = false;
	int action = DB_ACTION_NONE;
	int a = 0;
	int b = 0;
	int tab = 0;
}

class DbControl
{
	int x;
	int y;
	int w;
	int h;
	int action;
	int a;
	int b;
	string text;
	bool enabled = true;
}

class DbWindow
{
	string id;
	string title;
	string preferencesFile = "DialogueBoxPreferences.asdat";
	int x;
	int y;
	int w;
	int h;
	int pad = 10;
	int titleHeight = 18;
	int focusIndex = 0;
	int activeTab = 0;
	int lastClickTick = -9999;
	int lastKeyTick = -9999;
	bool mouseWasDown = false;
	bool dragging = false;
	int dragOffsetX = 0;
	int dragOffsetY = 0;
	bool closeButton = true;
	bool draggable = false;
	bool titleBar = true;
	int backgroundColor = 32;
	int titleColor = 72;
	int focusColor = 15;
	array<DbControl> controls;
}

array<string> dbPreferenceIds;
array<int> dbPreferenceX;
array<int> dbPreferenceY;
bool dbPreferencesLoaded = false;
string dbPreferencesLoadedFile = "";

int DbPreferenceIndex(const string &in id)
{
	for(uint i = 0; i < dbPreferenceIds.length; i++)
		if(dbPreferenceIds[i] == id) return int(i);
	return -1;
}

void DbLoadPreferences(const string &in filename)
{
	if(dbPreferencesLoaded && dbPreferencesLoadedFile == filename) return;
	dbPreferencesLoaded = true;
	dbPreferencesLoadedFile = filename;
	dbPreferenceIds.resize(0);
	dbPreferenceX.resize(0);
	dbPreferenceY.resize(0);
	jjSTREAM load(filename);
	string line;
	while(load.getLine(line))
	{
		array<string> parts = line.split(" ");
		if(parts.length >= 3)
		{
			dbPreferenceIds.insertLast(parts[0]);
			dbPreferenceX.insertLast(parseInt(parts[1]));
			dbPreferenceY.insertLast(parseInt(parts[2]));
		}
	}
}

void DbSavePreferences(const string &in filename)
{
	jjSTREAM save;
	for(uint i = 0; i < dbPreferenceIds.length; i++)
		save.write(dbPreferenceIds[i] + " " + dbPreferenceX[i] + " " + dbPreferenceY[i] + "\n");
	save.save(filename);
}

void DbApplySavedPosition(DbWindow@ window)
{
	DbLoadPreferences(window.preferencesFile);
	int index = DbPreferenceIndex(window.id);
	if(index >= 0)
	{
		window.x = dbPreferenceX[index];
		window.y = dbPreferenceY[index];
	}
	DbClampToScreen(window);
}

void DbRememberPosition(DbWindow@ window)
{
	DbLoadPreferences(window.preferencesFile);
	int index = DbPreferenceIndex(window.id);
	if(index < 0)
	{
		dbPreferenceIds.insertLast(window.id);
		dbPreferenceX.insertLast(window.x);
		dbPreferenceY.insertLast(window.y);
	}
	else
	{
		dbPreferenceX[index] = window.x;
		dbPreferenceY[index] = window.y;
	}
	DbSavePreferences(window.preferencesFile);
}

void DbClampToScreen(DbWindow@ window)
{
	if(window.x + window.w > jjResolutionWidth) window.x = jjResolutionWidth - window.w;
	if(window.y + window.h > jjResolutionHeight) window.y = jjResolutionHeight - window.h;
	if(window.x < 0) window.x = 0;
	if(window.y < 0) window.y = 0;
}

void DbBegin(DbWindow@ window, const string &in id, const string &in title, int x, int y, int w, int h)
{
	if(window.preferencesFile == "") window.preferencesFile = DB_PREFERENCES_FILE;
	bool sameWindow = window.id == id;
	window.id = id;
	window.title = title;
	window.w = w;
	window.h = h;
	window.controls.resize(0);
	if(sameWindow)
	{
		DbClampToScreen(window);
	}
	else
	{
		window.x = x;
		window.y = y;
		DbApplySavedPosition(window);
	}
}

void DbClearControls(DbWindow@ window)
{
	window.controls.resize(0);
}

void DbAddControl(DbWindow@ window, int x, int y, int w, int h, int action, int a, int b, const string &in text, bool enabled = true)
{
	DbControl control;
	control.x = x;
	control.y = y;
	control.w = w;
	control.h = h;
	control.action = action;
	control.a = a;
	control.b = b;
	control.text = text;
	control.enabled = enabled;
	window.controls.insertLast(control);
}

array<string> DbWrappedSmallLines(const string &in text, int maxPixels)
{
	array<string> lines;
	if(maxPixels <= 0) return lines;
	array<string> paragraphs = text.split("\n");
	for(uint p = 0; p < paragraphs.length; p++)
	{
		if(paragraphs[p] == "")
		{
			lines.insertLast("");
			continue;
		}
		array<string> words = paragraphs[p].split(" ");
		string line = "";
		for(uint i = 0; i < words.length; i++)
		{
			string remaining = words[i];
			while(remaining != "" && jjGetStringWidth(remaining, STRING::SMALL, STRING::NORMAL) > maxPixels)
			{
				int take = int(remaining.length());
				while(take > 1 && jjGetStringWidth(remaining.substr(0, take), STRING::SMALL, STRING::NORMAL) > maxPixels)
					take--;
				if(line != "")
				{
					lines.insertLast(line);
					line = "";
				}
				lines.insertLast(remaining.substr(0, take));
				remaining = remaining.substr(take);
			}
			if(remaining == "") continue;
			string candidate = line == "" ? remaining : line + " " + remaining;
			if(jjGetStringWidth(candidate, STRING::SMALL, STRING::NORMAL) <= maxPixels) line = candidate;
			else
			{
				if(line != "") lines.insertLast(line);
				line = remaining;
			}
		}
		if(line != "") lines.insertLast(line);
	}
	if(lines.length == 0) lines.insertLast("");
	return lines;
}

int DbWrappedSmallLineCount(const string &in text, int maxPixels)
{
	array<string> lines = DbWrappedSmallLines(text, maxPixels);
	return int(lines.length);
}

int DbVisibleSmallLines(int pixelHeight, int lineHeight = 14)
{
	int lines = pixelHeight / lineHeight;
	return lines < 1 ? 1 : lines;
}

int DbClampSmallScroll(const string &in text, int maxPixels, int pixelHeight, int scrollLine, int lineHeight = 14)
{
	int maxScroll = DbWrappedSmallLineCount(text, maxPixels) - DbVisibleSmallLines(pixelHeight, lineHeight);
	if(maxScroll < 0) maxScroll = 0;
	if(scrollLine < 0) return 0;
	if(scrollLine > maxScroll) return maxScroll;
	return scrollLine;
}

int DbDrawWrappedSmallScrolled(jjCANVAS@ canvas, int x, int y, const string &in text, int maxPixels, int maxY, int scrollLine, int lineHeight = 14)
{
	array<string> lines = DbWrappedSmallLines(text, maxPixels);
	for(uint i = 0; i < lines.length; i++)
	{
		if(int(i) < scrollLine) continue;
		if(y + lineHeight > maxY) break;
		canvas.drawString(x, y, lines[i], STRING::SMALL, STRING::NORMAL);
		y += lineHeight;
	}
	return y;
}

int DbDrawWrappedSmall(jjCANVAS@ canvas, int x, int y, const string &in text, int maxPixels, int maxY, int lineHeight = 14)
{
	return DbDrawWrappedSmallScrolled(canvas, x, y, text, maxPixels, maxY, 0, lineHeight);
}

int DbDrawWrappedSmallInBox(jjCANVAS@ canvas, int x, int y, const string &in text, int maxPixels, int pixelHeight, int lineHeight = 14)
{
	return DbDrawWrappedSmall(canvas, x, y, text, maxPixels, y + pixelHeight, lineHeight);
}

int DbDrawScrollableSmallText(jjCANVAS@ canvas, DbWindow@ window, int x, int y, int w, int maxY, const string &in text, int scrollLine, bool scrollButtons = true, int scrollUpAction = DB_ACTION_SCROLL_UP, int scrollDownAction = DB_ACTION_SCROLL_DOWN, int lineHeight = 14)
{
	int textW = scrollButtons ? w - 62 : w;
	if(textW < 20) textW = w;
	int visibleHeight = maxY - y;
	if(visibleHeight < lineHeight) visibleHeight = lineHeight;
	int maxScroll = DbWrappedSmallLineCount(text, textW) - DbVisibleSmallLines(visibleHeight, lineHeight);
	if(maxScroll < 0) maxScroll = 0;
	if(scrollLine < 0) scrollLine = 0;
	if(scrollLine > maxScroll) scrollLine = maxScroll;
	if(scrollButtons && maxScroll > 0)
	{
		if(scrollLine > 0) DbDrawButton(canvas, window, x + w - 46, y, 42, 14, scrollUpAction, 0, -1, "UP");
		if(scrollLine < maxScroll) DbDrawButton(canvas, window, x + w - 58, maxY - 12, 54, 14, scrollDownAction, 0, 1, "DOWN");
	}
	DbDrawWrappedSmallScrolled(canvas, x, y, text, textW, maxY, scrollLine, lineHeight);
	return scrollLine;
}

int DbHandleScrollResult(DbResult@ result, int scrollLine, int scrollUpAction = DB_ACTION_SCROLL_UP, int scrollDownAction = DB_ACTION_SCROLL_DOWN)
{
	if(result.action == scrollUpAction)
	{
		scrollLine--;
		return scrollLine;
	}
	if(result.action == scrollDownAction)
	{
		scrollLine++;
		return scrollLine;
	}
	return scrollLine;
}

bool DbIsScrollResult(DbResult@ result, int scrollUpAction = DB_ACTION_SCROLL_UP, int scrollDownAction = DB_ACTION_SCROLL_DOWN)
{
	return result.action == scrollUpAction || result.action == scrollDownAction;
}

// EDITABLE boxes

int DbClampTextCursor(const string &in text, int cursor)
{
	if(cursor < 0) return 0;
	if(cursor > int(text.length)) return int(text.length);
	return cursor;
}

string DbInsertTextAtCursor(const string &in text, int cursor, const string &in insert)
{
	cursor = DbClampTextCursor(text, cursor);
	return text.substr(0, cursor) + insert + text.substr(cursor);
}

string DbBackspaceTextAtCursor(const string &in text, int cursor)
{
	cursor = DbClampTextCursor(text, cursor);
	if(cursor <= 0) return text;
	return text.substr(0, cursor - 1) + text.substr(cursor);
}

int DbMoveTextCursor(const string &in text, int cursor, int delta)
{
	return DbClampTextCursor(text, cursor + delta);
}

int DbEditableCursorLine(const string &in text, int maxPixels, int cursor)
{
	array<string> lines = DbWrappedSmallLines(text, maxPixels);
	cursor = DbClampTextCursor(text, cursor);
	int remaining = cursor;
	for(uint i = 0; i < lines.length; i++)
	{
		int lineLen = int(lines[i].length);
		if(remaining <= lineLen) return int(i);
		remaining -= lineLen + 1;
	}
	return int(lines.length) - 1;
}

int DbClosestCursorInLine(const string &in line, int targetPixels)
{
	int best = 0;
	int bestDistance = 99999;
	for(int i = 0; i <= int(line.length); i++)
	{
		int width = jjGetStringWidth(line.substr(0, i), STRING::SMALL, STRING::NORMAL);
		int distance = width > targetPixels ? width - targetPixels : targetPixels - width;
		if(distance < bestDistance)
		{
			bestDistance = distance;
			best = i;
		}
	}
	return best;
}

int DbMoveTextCursorVertical(const string &in text, int maxPixels, int cursor, int delta)
{
	array<string> lines = DbWrappedSmallLines(text, maxPixels);
	if(lines.length == 0) return 0;
	cursor = DbClampTextCursor(text, cursor);
	int remaining = cursor;
	int currentLine = 0;
	int currentColumn = 0;
	for(uint i = 0; i < lines.length; i++)
	{
		int lineLen = int(lines[i].length);
		if(remaining <= lineLen || i + 1 == lines.length)
		{
			currentLine = int(i);
			currentColumn = remaining < lineLen ? remaining : lineLen;
			break;
		}
		remaining -= lineLen + 1;
	}
	int targetLine = currentLine + delta;
	if(targetLine < 0) targetLine = 0;
	if(targetLine >= int(lines.length)) targetLine = int(lines.length) - 1;
	int targetPixels = jjGetStringWidth(lines[currentLine].substr(0, currentColumn), STRING::SMALL, STRING::NORMAL);
	int targetColumn = DbClosestCursorInLine(lines[targetLine], targetPixels);
	int targetCursor = 0;
	for(int i = 0; i < targetLine; i++)
		targetCursor += int(lines[i].length) + 1;
	targetCursor += targetColumn;
	return DbClampTextCursor(text, targetCursor);
}

int DbClampEditableScrollToCursor(const string &in text, int maxPixels, int pixelHeight, int scrollLine, int cursor, int lineHeight = 14)
{
	int cursorLine = DbEditableCursorLine(text, maxPixels, cursor);
	int visibleLines = DbVisibleSmallLines(pixelHeight, lineHeight);
	if(cursorLine < scrollLine) scrollLine = cursorLine;
	if(cursorLine >= scrollLine + visibleLines) scrollLine = cursorLine - visibleLines + 1;
	return DbClampSmallScroll(text, maxPixels, pixelHeight, scrollLine, lineHeight);
}

int DbSmallSpaceWidth()
{
	int width = jjGetStringWidth("x x", STRING::SMALL, STRING::NORMAL) - jjGetStringWidth("xx", STRING::SMALL, STRING::NORMAL);
	return width > 0 ? width : 4;
}

int DbSmallTextWidthPreserveTrailingSpaces(const string &in text)
{
	int trailingSpaces = 0;
	for(int i = int(text.length) - 1; i >= 0 && text[i] == 32; i--)
		trailingSpaces++;
	if(trailingSpaces <= 0) return jjGetStringWidth(text, STRING::SMALL, STRING::NORMAL);
	return jjGetStringWidth(text.substr(0, int(text.length) - trailingSpaces), STRING::SMALL, STRING::NORMAL) + trailingSpaces * DbSmallSpaceWidth();
}

void DbDrawEditableSmallCursor(jjCANVAS@ canvas, int x, int y, const string &in text, int maxPixels, int maxY, int cursor, int scrollLine, int lineHeight = 14, int color = 80)
{
	array<string> lines = DbWrappedSmallLines(text, maxPixels);
	cursor = DbClampTextCursor(text, cursor);
	int remaining = cursor;
	for(uint i = 0; i < lines.length; i++)
	{
		int lineLen = int(lines[i].length);
		if(remaining <= lineLen || i + 1 == lines.length)
		{
			if(int(i) < scrollLine) return;
			int cursorY = y + (int(i) - scrollLine) * lineHeight - 5;
			if(cursorY + lineHeight > maxY) return;
			int cursorChars = remaining < lineLen ? remaining : lineLen;
			int extraSpaces = remaining > lineLen ? remaining - lineLen : 0;
			int cursorX = x + DbSmallTextWidthPreserveTrailingSpaces(lines[i].substr(0, cursorChars)) + extraSpaces * DbSmallSpaceWidth();
			canvas.drawRectangle(cursorX, cursorY - 1, 2, lineHeight, color, SPRITE::NORMAL);
			return;
		}
		remaining -= lineLen + 1;
		if(remaining < 0) remaining = 0;
	}
	canvas.drawRectangle(x, y, 2, lineHeight, color, SPRITE::NORMAL);
}

string DbClipSmall(const string &in text, int maxPixels)
{
	return text;
}

void DbDrawFrame(jjCANVAS@ canvas, DbWindow@ window)
{
	canvas.drawRectangle(window.x, window.y, window.w, window.h, 0, SPRITE::SHADOW);
	canvas.drawRectangle(window.x + 2, window.y + 2, window.w - 4, window.h - 4, window.backgroundColor, SPRITE::TRANSLUCENT);
	if(window.titleBar)
	{
		canvas.drawRectangle(window.x + 2, window.y + 2, window.w - 4, window.titleHeight - 3, window.titleColor, SPRITE::NORMAL);
		canvas.drawString(window.x + window.pad, window.y + 10, window.title, STRING::SMALL, STRING::NORMAL);
	}
	else canvas.drawString(window.x + window.pad, window.y + window.pad, window.title, STRING::MEDIUM, STRING::NORMAL);
	if(window.closeButton)
		DbDrawButton(canvas, window, window.x + window.w - 24, window.y + (window.titleBar ? 8 : window.pad), 18, 15, DB_ACTION_CLOSE, 0, 0, "X");
}

void DbDrawFocus(jjCANVAS@ canvas, DbWindow@ window, int focusOffsetY = DB_FOCUS_OFFSET_Y, int focusHeight = 0)
{
	DbNormalizeFocus(window);
	if(window.focusIndex < 0 || window.focusIndex >= int(window.controls.length)) return;
	DbControl control = window.controls[window.focusIndex];
	if(!control.enabled) return;
	int h = focusHeight > 0 ? focusHeight : control.h;
	canvas.drawRectangle(control.x - 3, control.y + focusOffsetY, control.w + 6, h, window.focusColor, SPRITE::TRANSLUCENT);
}

void DbDrawButton(jjCANVAS@ canvas, DbWindow@ window, int x, int y, int w, int h, int action, int a, int b, const string &in text, bool enabled = true, bool selected = false)
{
	string shown = enabled ? text : "||" + text;
	int drawH = h;
	int wrappedH = DbWrappedSmallLineCount(shown, w) * 14;
	if(wrappedH > drawH) drawH = wrappedH;
	DbAddControl(window, x, y, w, drawH, action, a, b, text, enabled);
	if(selected)
		canvas.drawRectangle(x - 3, y + DB_FOCUS_OFFSET_Y, w + 6, drawH, 24, SPRITE::TRANSLUCENT);
	DbDrawWrappedSmall(canvas, x, y, shown, w, y + drawH);
}

void DbDrawTab(jjCANVAS@ canvas, DbWindow@ window, int x, int y, int w, int tab, const string &in text, bool enabled = true)
{
	string shown = window.activeTab == tab ? "|||" + text : (enabled ? "||||" + text : "||" + text);
	DbDrawButton(canvas, window, x, y, w, 16, DB_ACTION_TAB, tab, 0, shown, enabled, window.activeTab == tab);
}

void DbDrawStepper(jjCANVAS@ canvas, DbWindow@ window, int x, int y, int textW, int action, int a, int prevB, int nextB, const string &in text)
{
	DbDrawButton(canvas, window, x, y, 16, 16, action, a, prevB, "<");
	DbDrawWrappedSmall(canvas, x + 22, y, text, textW, y + DbWrappedSmallLineCount(text, textW) * 14);
	DbDrawButton(canvas, window, x + 26 + textW, y, 16, 16, action, a, nextB, ">");
}

void DbContentRect(DbWindow@ window, int &out x, int &out y, int &out w, int &out h)
{
	x = window.x + window.pad;
	y = window.y + (window.titleBar ? window.titleHeight + window.pad : window.pad + 20);
	w = window.w - window.pad * 2;
	h = window.h - (y - window.y) - window.pad;
}

bool DbPointIn(int tx, int ty, int x, int y, int w, int h)
{
	return tx >= x && tx <= x + w && ty >= y && ty <= y + h;
}

int DbHitControl(DbWindow@ window, int tx, int ty)
{
	for(int i = int(window.controls.length) - 1; i >= 0; i--)
	{
		DbControl control = window.controls[i];
		if(control.enabled && DbPointIn(tx, ty, control.x, control.y, control.w, control.h)) return i;
	}
	return -1;
}

void DbActivateControl(DbWindow@ window, int index, DbResult@ result)
{
	if(index < 0 || index >= int(window.controls.length)) return;
	DbControl control = window.controls[index];
	if(!control.enabled) return;
	result.activated = true;
	result.action = control.action;
	result.a = control.a;
	result.b = control.b;
	if(control.action == DB_ACTION_CLOSE) result.closeRequested = true;
	else if(control.action == DB_ACTION_TAB)
	{
		window.activeTab = control.a;
		result.tabChanged = true;
		result.tab = control.a;
	}
}

bool DbProcessMouse(DbWindow@ window, DbResult@ result)
{
	bool mouseDown = jjKey[0x01];
	int tx = jjMouseX;
	int ty = jjMouseY + 8;
	bool clicked = mouseDown && !window.mouseWasDown && jjGameTicks > window.lastClickTick + 10;
	if(!mouseDown)
	{
		if(window.dragging)
		{
			result.positionChanged = true;
			DbRememberPosition(window);
		}
		window.mouseWasDown = false;
		window.dragging = false;
	}
	else window.mouseWasDown = true;
	if(window.dragging)
	{
		window.x = tx - window.dragOffsetX;
		window.y = ty - window.dragOffsetY;
		DbClampToScreen(window);
		result.positionChanged = true;
		return true;
	}
	if(!clicked) return false;
	int controlIndex = DbHitControl(window, tx, ty);
	if(controlIndex >= 0)
	{
		window.focusIndex = controlIndex;
		window.lastClickTick = jjGameTicks;
		DbActivateControl(window, controlIndex, result);
		return true;
	}
	if(window.draggable && window.titleBar && DbPointIn(tx, ty, window.x + 2, window.y + 2, window.w - 4, window.titleHeight - 3))
	{
		window.dragging = true;
		window.dragOffsetX = tx - window.x;
		window.dragOffsetY = ty - window.y;
		window.lastClickTick = jjGameTicks;
		return true;
	}
	return false;
}

void DbNormalizeFocus(DbWindow@ window)
{
	int count = int(window.controls.length);
	if(count <= 0) { window.focusIndex = 0; return; }
	while(window.focusIndex < 0) window.focusIndex += count;
	while(window.focusIndex >= count) window.focusIndex -= count;
	for(int attempts = 0; attempts < count && !window.controls[window.focusIndex].enabled; attempts++)
	{
		window.focusIndex++;
		if(window.focusIndex >= count) window.focusIndex = 0;
	}
}

void DbMoveFocus(DbWindow@ window, int delta)
{
	if(window.controls.length == 0) return;
	window.focusIndex += delta;
	DbNormalizeFocus(window);
}

bool DbProcessKeyboard(DbWindow@ window, jjPLAYER@ play, DbResult@ result, int repeatTicks = 10)
{
	if(jjGameTicks - window.lastKeyTick < repeatTicks) return false;
	if(play.keyFire || play.keySelect || jjKey[0x0D])
	{
		window.lastKeyTick = jjGameTicks;
		DbNormalizeFocus(window);
		DbActivateControl(window, window.focusIndex, result);
		return true;
	}
	if(play.keyUp || play.keyLeft || jjKey[0x26] || jjKey[0x25])
	{
		window.lastKeyTick = jjGameTicks;
		DbMoveFocus(window, -1);
		return true;
	}
	if(play.keyDown || play.keyRight || jjKey[0x28] || jjKey[0x27])
	{
		window.lastKeyTick = jjGameTicks;
		DbMoveFocus(window, 1);
		return true;
	}
	return false;
}

void DbResetResult(DbResult@ result)
{
	result.activated = false;
	result.closeRequested = false;
	result.tabChanged = false;
	result.positionChanged = false;
	result.action = DB_ACTION_NONE;
	result.a = 0;
	result.b = 0;
	result.tab = 0;
}