Browse Source

Created a textbox component.

David Piuva 3 years ago
parent
commit
7375b51365

+ 22 - 1
Source/DFPSR/api/fontAPI.cpp

@@ -1,6 +1,6 @@
 // zlib open source license
 //
-// Copyright (c) 2020 David Forsgren Piuva
+// Copyright (c) 2020 to 2022 David Forsgren Piuva
 // 
 // This software is provided 'as-is', without any express or implied
 // warranty. In no event will the authors be held liable for any damages
@@ -62,6 +62,20 @@ int32_t font_getSize(const RasterFont font) {
 	return font->size;
 }
 
+int32_t font_getSpacing(const RasterFont font) {
+	if (!font_exists(font)) {
+		throwError("font_getSpacing: font must exist!");
+	}
+	return font->spacing;
+}
+
+int32_t font_getTabWidth(const RasterFont font) {
+	if (!font_exists(font)) {
+		throwError("font_getTabWidth: font must exist!");
+	}
+	return font->tabWidth;
+}
+
 int32_t font_getCharacterWidth(const RasterFont font, DsrChar unicodeValue) {
 	if (!font_exists(font)) {
 		throwError("font_getCharacterWidth: font must exist!");
@@ -69,6 +83,13 @@ int32_t font_getCharacterWidth(const RasterFont font, DsrChar unicodeValue) {
 	return font->getCharacterWidth(unicodeValue);
 }
 
+int32_t font_getMonospaceWidth(const RasterFont font) {
+	if (!font_exists(font)) {
+		throwError("font_getMonospaceWidth: font must exist!");
+	}
+	return font->widest + font->spacing;
+}
+
 int32_t font_getLineWidth(const RasterFont font, const ReadableString& content) {
 	if (!font_exists(font)) {
 		throwError("font_getLineWidth: font must exist!");

+ 11 - 2
Source/DFPSR/api/fontAPI.h

@@ -1,6 +1,6 @@
 // zlib open source license
 //
-// Copyright (c) 2020 David Forsgren Piuva
+// Copyright (c) 2020 to 2022 David Forsgren Piuva
 // 
 // This software is provided 'as-is', without any express or implied
 // warranty. In no event will the authors be held liable for any damages
@@ -48,13 +48,22 @@ namespace dsr {
 	String font_getName(const RasterFont font);
 	// Pre-condition: font must exist
 	// Post-condition:
-	//   Returns the font's size, which is the height of an individual character image before cropping
+	//   Returns the font's size in pixels, which is the height of an individual character image before cropping
 	//   If ceated using font_createLatinOne then it's image_getHeight(atlas) / 16
 	int32_t font_getSize(const RasterFont font);
 	// Pre-condition: font must exist
+	// Post-condition: Returns the font's empty space between characters in pixels.
+	int32_t font_getSpacing(const RasterFont font);
+	// Pre-condition: font must exist
+	// Post-condition: Returns the font's maximum tab width in pixels, which is the alignment the write location will jump to when reading a tab.
+	int32_t font_getTabWidth(const RasterFont font);
+	// Pre-condition: font must exist
 	// Post-condition: Returns the width of a character including spacing in pixels
 	int32_t font_getCharacterWidth(const RasterFont font, DsrChar unicodeValue);
 	// Pre-condition: font must exist
+	// Post-condition: Returns the width of the widest character including spacing in pixels
+	int32_t font_getMonospaceWidth(const RasterFont font);
+	// Pre-condition: font must exist
 	// Post-condition: Returns the total length of content in pixels while ignoring line-breaks
 	int32_t font_getLineWidth(const RasterFont font, const ReadableString& content);
 	// Pre-condition: font and target must exist

+ 28 - 27
Source/DFPSR/font/Font.cpp

@@ -1,6 +1,6 @@
 // zlib open source license
 //
-// Copyright (c) 2018 to 2019 David Forsgren Piuva
+// Copyright (c) 2018 to 2022 David Forsgren Piuva
 // 
 // This software is provided 'as-is', without any express or implied
 // warranty. In no event will the authors be held liable for any damages
@@ -33,7 +33,7 @@ RasterCharacter::RasterCharacter(const ImageU8& image, DsrChar unicodeValue, int
 
 RasterFontImpl::RasterFontImpl(const String& name, int32_t size, int32_t spacing, int32_t spaceWidth)
  : name(name), size(size), spacing(spacing), spaceWidth(spaceWidth), tabWidth(spaceWidth * 4) {
-	for (int i = 0; i < 65536; i++) {
+	for (int32_t i = 0; i < 65536; i++) {
 		this->indices[i] = -1;
 	}
 }
@@ -47,11 +47,13 @@ std::shared_ptr<RasterFontImpl> RasterFontImpl::createLatinOne(const String& nam
 	return result;
 }
 
-void RasterFontImpl::registerCharacter(const ImageU8& image, DsrChar unicodeValue, int32_t offsetY) {
+void RasterFontImpl::registerCharacter(const ImageU8& characterImage, DsrChar unicodeValue, int32_t offsetY) {
 	if (this->indices[unicodeValue] == -1) {
 		// Add the unicode character
-		this->characters.pushConstruct(image, unicodeValue, offsetY);
-		// Add to latin-1 table if inside the range
+		this->characters.pushConstruct(characterImage, unicodeValue, offsetY);
+		int32_t width = image_getWidth(characterImage);
+		if (this->widest < width) this->widest = width;
+		// Add to table if inside the range
 		if (unicodeValue < 65536) {
 			this->indices[unicodeValue] = this->characters.length() - 1;
 		}
@@ -88,8 +90,7 @@ void RasterFontImpl::registerLatinOne16x16(const ImageU8& atlas) {
 			IRect croppedRegion = getCharacterBound(atlas, searchRegion);
 			if (croppedRegion.hasArea()) {
 				int32_t offsetY = croppedRegion.top() - searchRegion.top();
-				ImageU8 fullImage = image_getSubImage(atlas, croppedRegion);
-				this->registerCharacter(fullImage, y * 16 + x, offsetY);
+				this->registerCharacter(image_getSubImage(atlas, croppedRegion), y * 16 + x, offsetY);
 			}
 		}
 	}
@@ -124,21 +125,21 @@ int32_t RasterFontImpl::printCharacter(ImageRgbaU8& target, DsrChar unicodeValue
 }
 
 // Lets the print coordinate x jump to the next tab stop starting from the left origin
-static void tabJump(int &x, int leftOrigin, int tabWidth) {
+static int64_t tabJump(int64_t oldLocation, int64_t leftOrigin, int64_t tabWidth) {
 	// Get the pixel location relative to the origin
-	int localX = x - leftOrigin;
+	int64_t localX = oldLocation - leftOrigin;
 	// Get the remaining pixels until the next tab stop
 	// If modulo returns zero at a tab stop, it will jump to the next with a full tab width
-	int remainder = tabWidth - (localX % tabWidth);
-	x += remainder;
+	int64_t remainder = tabWidth - (localX % tabWidth);
+	return oldLocation + remainder;
 }
 
 void RasterFontImpl::printLine(ImageRgbaU8& target, const ReadableString& content, const IVector2D& location, const ColorRgbaI32& color) const {
 	IVector2D currentLocation = location;
-	for (int i = 0; i < string_length(content); i++) {
+	for (int64_t i = 0; i < string_length(content); i++) {
 		DsrChar code = content[i];
-		if (code == 9) { // Tab
-			tabJump(currentLocation.x, location.x, this->tabWidth);
+		if (code == U'\t') {
+			currentLocation.x = tabJump(currentLocation.x, location.x, this->tabWidth);
 		} else {
 			// TODO: Would right to left printing of Arabic text be too advanced to have in the core framework?
 			currentLocation.x += this->printCharacter(target, code, currentLocation, color);
@@ -147,16 +148,16 @@ void RasterFontImpl::printLine(ImageRgbaU8& target, const ReadableString& conten
 }
 
 void RasterFontImpl::printMultiLine(ImageRgbaU8& target, const ReadableString& content, const IRect& bound, const ColorRgbaI32& color) const {
-	int y = bound.top(); // The upper vertical location of the currently printed row in pixels.
-	int lineWidth = 0; // The size of the currently scanned row, to make sure that it can be printed.
-	int rowStartIndex = 0; // The start of the current row or the unprinted remainder that didn't fit inside the bound.
-	int lastWordBreak = 0; // The last scanned location where the current row could've been broken off.
+	int64_t y = bound.top(); // The upper vertical location of the currently printed row in pixels.
+	int64_t lineWidth = 0; // The size of the currently scanned row, to make sure that it can be printed.
+	int64_t rowStartIndex = 0; // The start of the current row or the unprinted remainder that didn't fit inside the bound.
+	int64_t lastWordBreak = 0; // The last scanned location where the current row could've been broken off.
 	bool wordStarted = false; // True iff the physical line after word wrapping has scanned the beginning of a word.
 	if (bound.height() < this->size) {
 		// Not enough height to print anything
 		return;
 	}
-	for (int i = 0; i < string_length(content); i++) {
+	for (int64_t i = 0; i < string_length(content); i++) {
 		DsrChar code = content[i];
 		if (code == 10) {
 			// Print the completed line
@@ -167,7 +168,7 @@ void RasterFontImpl::printMultiLine(ImageRgbaU8& target, const ReadableString& c
 			lastWordBreak = rowStartIndex;
 			wordStarted = false;
 		} else {
-			int newCharWidth = this->getCharacterWidth(code);
+			int32_t newCharWidth = this->getCharacterWidth(code);
 			if (code == ' ' || code == 9) { // Space or tab
 				if (wordStarted) {
 					lastWordBreak = i;
@@ -176,13 +177,13 @@ void RasterFontImpl::printMultiLine(ImageRgbaU8& target, const ReadableString& c
 			} else {
 				wordStarted = true;
 				if (lineWidth + newCharWidth >= bound.width()) {
-					int splitIndex = lastWordBreak;
+					int64_t splitIndex = lastWordBreak;
 					if (lastWordBreak == rowStartIndex) {
 						// The word is too big to be printed as a whole
 						splitIndex = i;
 					}
 					ReadableString partialLine = string_exclusiveRange(content, rowStartIndex, splitIndex);
-					int partialLength = this->getLineWidth(partialLine);
+					int64_t partialLength = this->getLineWidth(partialLine);
 					if (partialLength <= bound.width()) {
 						this->printLine(target, partialLine, IVector2D(bound.left(), y), color);
 					}
@@ -199,7 +200,7 @@ void RasterFontImpl::printMultiLine(ImageRgbaU8& target, const ReadableString& c
 				}
 			}
 			if (code == 9) { // Tab
-				tabJump(lineWidth, bound.left(), this->tabWidth);
+				lineWidth = tabJump(lineWidth, bound.left(), this->tabWidth);
 			} else {
 				lineWidth += newCharWidth;
 			}
@@ -208,12 +209,12 @@ void RasterFontImpl::printMultiLine(ImageRgbaU8& target, const ReadableString& c
 	this->printLine(target, string_from(content, rowStartIndex), IVector2D(bound.left(), y), color);
 }
 
-int32_t RasterFontImpl::getLineWidth(const ReadableString& content) const {
-	int32_t result = 0;
-	for (int i = 0; i < string_length(content); i++) {
+int64_t RasterFontImpl::getLineWidth(const ReadableString& content) const {
+	int64_t result = 0;
+	for (int64_t i = 0; i < string_length(content); i++) {
 		DsrChar code = content[i];
 		if (code == 9) { // Tab
-			tabJump(result, 0, this->tabWidth);
+			result = tabJump(result, 0, this->tabWidth);
 		} else {
 			result += this->getCharacterWidth(code);
 		}

+ 4 - 3
Source/DFPSR/font/Font.h

@@ -1,6 +1,6 @@
 // zlib open source license
 //
-// Copyright (c) 2018 to 2019 David Forsgren Piuva
+// Copyright (c) 2018 to 2022 David Forsgren Piuva
 // 
 // This software is provided 'as-is', without any express or implied
 // warranty. In no event will the authors be held liable for any damages
@@ -56,10 +56,11 @@ public:
 	int32_t spacing = 0; // The extra pixels between each character
 	int32_t spaceWidth = 0; // The size of a whole space character including spacing
 	int32_t tabWidth = 0; // The size of a whole tab including spacing
+	int32_t widest = 0; // The maximum character width excluding spacing
 	// A list of character images with their unicode keys
 	List<RasterCharacter> characters;
 	// TODO: A way to map all UTF-32 characters
-	// Indices to characters for UTF16
+	// Indices to characters within the 16-bit range
 	//  indices[x] = -1 for non-existing character codes
 	//  The indices[0..255] contains the Latin-1 subset
 	int32_t indices[65536];
@@ -78,7 +79,7 @@ public:
 	int32_t getCharacterWidth(DsrChar unicodeValue) const;
 	// Returns the total length of characters in pixels as if printing content
 	// If multiple lines exists it will simply keep adding to the total by ignoring line-breaks
-	int32_t getLineWidth(const ReadableString& content) const;
+	int64_t getLineWidth(const ReadableString& content) const;
 	// Prints a character and returns the horizontal stride in pixels
 	int32_t printCharacter(ImageRgbaU8& target, DsrChar unicodeValue, const IVector2D& location, const ColorRgbaI32& color) const;
 	// Prints a whole line of text from location

+ 2 - 0
Source/DFPSR/gui/DsrWindow.cpp

@@ -27,6 +27,7 @@
 #include "components/Panel.h"
 #include "components/Button.h"
 #include "components/ListBox.h"
+#include "components/TextBox.h"
 #include "components/Label.h"
 #include "components/Picture.h"
 // <<<< Include new components here
@@ -45,6 +46,7 @@ void dsr::gui_initialize() {
 		REGISTER_PERSISTENT_CLASS(Panel)
 		REGISTER_PERSISTENT_CLASS(Button)
 		REGISTER_PERSISTENT_CLASS(ListBox)
+		REGISTER_PERSISTENT_CLASS(TextBox)
 		REGISTER_PERSISTENT_CLASS(Label)
 		REGISTER_PERSISTENT_CLASS(Picture)
 		// <<<< Register new components here

+ 11 - 1
Source/DFPSR/gui/InputEvent.cpp

@@ -1,6 +1,6 @@
 // zlib open source license
 //
-// Copyright (c) 2018 to 2019 David Forsgren Piuva
+// Copyright (c) 2018 to 2022 David Forsgren Piuva
 // 
 // This software is provided 'as-is', without any express or implied
 // warranty. In no event will the authors be held liable for any damages
@@ -158,6 +158,16 @@ inline String dsr::getName(DsrKey v) {
 		return U"Y";
 	} else if (v == DsrKey_Z) {
 		return U"Z";
+	} else if (v == DsrKey_Insert) {
+		return U"Insert";
+	} else if (v == DsrKey_Home) {
+		return U"Home";
+	} else if (v == DsrKey_End) {
+		return U"End";
+	} else if (v == DsrKey_PageUp) {
+		return U"PageUp";
+	} else if (v == DsrKey_PageDown) {
+		return U"PageDown";
 	} else {
 		return U"Invalid virtual key code";
 	}

+ 3 - 3
Source/DFPSR/gui/InputEvent.h

@@ -1,6 +1,6 @@
 // zlib open source license
 //
-// Copyright (c) 2018 to 2019 David Forsgren Piuva
+// Copyright (c) 2018 to 2022 David Forsgren Piuva
 // 
 // This software is provided 'as-is', without any express or implied
 // warranty. In no event will the authors be held liable for any damages
@@ -43,9 +43,9 @@ enum class KeyboardEventType { KeyDown, KeyUp, KeyType };
 //   * DsrKey_F1 to DsrKey_F12 are guaranteed to be in an increasing serial order (so that "key - (DsrKey_F1 - 1)" is the key's number)
 //   * DsrKey_A to DsrKey_Z are guaranteed to be in an increasing serial order
 enum DsrKey {
-	DsrKey_LeftArrow, DsrKey_RightArrow, DsrKey_UpArrow, DsrKey_DownArrow,
+	DsrKey_LeftArrow, DsrKey_RightArrow, DsrKey_UpArrow, DsrKey_DownArrow, DsrKey_PageUp, DsrKey_PageDown,
 	DsrKey_LeftControl, DsrKey_RightControl, DsrKey_LeftShift, DsrKey_RightShift, DsrKey_LeftAlt, DsrKey_RightAlt,
-	DsrKey_Escape, DsrKey_Pause, DsrKey_Space, DsrKey_Tab, DsrKey_Return, DsrKey_BackSpace, DsrKey_Delete,
+	DsrKey_Escape, DsrKey_Pause, DsrKey_Space, DsrKey_Tab, DsrKey_Return, DsrKey_BackSpace, DsrKey_Delete, DsrKey_Insert, DsrKey_Home, DsrKey_End,
 	DsrKey_0, DsrKey_1, DsrKey_2, DsrKey_3, DsrKey_4, DsrKey_5, DsrKey_6, DsrKey_7, DsrKey_8, DsrKey_9,
 	DsrKey_F1, DsrKey_F2, DsrKey_F3, DsrKey_F4, DsrKey_F5, DsrKey_F6, DsrKey_F7, DsrKey_F8, DsrKey_F9, DsrKey_F10, DsrKey_F11, DsrKey_F12,
 	DsrKey_A, DsrKey_B, DsrKey_C, DsrKey_D, DsrKey_E, DsrKey_F, DsrKey_G, DsrKey_H, DsrKey_I, DsrKey_J, DsrKey_K, DsrKey_L, DsrKey_M,

+ 7 - 3
Source/DFPSR/gui/VisualComponent.cpp

@@ -385,14 +385,18 @@ bool VisualComponent::isFocused() {
 	}
 }
 
-MediaResult VisualComponent::generateImage(MediaMethod &method, int width, int height, int red, int green, int blue, int pressed) {
-	return method.callUsingKeywords([this, &method, width, height, red, green, blue, pressed](MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName){
+MediaResult VisualComponent::generateImage(MediaMethod &method, int width, int height, int red, int green, int blue, int pressed, int focused, int hover) {
+	return method.callUsingKeywords([this, &method, width, height, red, green, blue, pressed, focused, hover](MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName){
 		if (string_caseInsensitiveMatch(argumentName, U"width")) {
 			machine_setInputByIndex(machine, methodIndex, inputIndex, width);
 		} else if (string_caseInsensitiveMatch(argumentName, U"height")) {
 			machine_setInputByIndex(machine, methodIndex, inputIndex, height);
 		} else if (string_caseInsensitiveMatch(argumentName, U"pressed")) {
 			machine_setInputByIndex(machine, methodIndex, inputIndex, pressed);
+		} else if (string_caseInsensitiveMatch(argumentName, U"focused")) {
+			machine_setInputByIndex(machine, methodIndex, inputIndex, focused);
+		} else if (string_caseInsensitiveMatch(argumentName, U"hover")) {
+			machine_setInputByIndex(machine, methodIndex, inputIndex, hover);
 		} else if (string_caseInsensitiveMatch(argumentName, U"red")) {
 			machine_setInputByIndex(machine, methodIndex, inputIndex, red);
 		} else if (string_caseInsensitiveMatch(argumentName, U"green")) {
@@ -404,7 +408,7 @@ MediaResult VisualComponent::generateImage(MediaMethod &method, int width, int h
 		} else {
 			// TODO: Ask the theme for the argument using a specified style class for variations between different types of buttons, checkboxes, panels, et cetera.
 			//       Throw an exception if the theme did not provide an input argument to its own media function.
-			throwError(U"Unhandled setting \"", argumentName, U"\" requested by ", machine_getMethodName(machine, methodIndex), U" in the visual theme!\n");
+			throwError(U"Unhandled setting \"", argumentName, U"\" requested by the media method \"", machine_getMethodName(machine, methodIndex), U"\" in the visual theme!\n");
 		}
 	});
 }

+ 1 - 1
Source/DFPSR/gui/VisualComponent.h

@@ -219,7 +219,7 @@ public:
 	//   The root component is considered focused if none of its children are focused.
 	bool isFocused();
 	// A reusable method for calling the media machine that allow providing additional variables as style flags.
-	MediaResult generateImage(MediaMethod &method, int width, int height, int red, int green, int blue, int pressed = 0);
+	MediaResult generateImage(MediaMethod &method, int width, int height, int red, int green, int blue, int pressed = 0, int focused = 0, int hover = 0);
 };
 
 }

+ 37 - 32
Source/DFPSR/gui/VisualTheme.cpp

@@ -36,7 +36,8 @@ namespace dsr {
 //   Copy, modify and compile with theme_create to get a custom theme
 static const ReadableString defaultMediaMachineCode =
 UR"QUOTE(
-# Helper methods
+# Drawing a rounded rectangle to the alpha channel and a smaller rectangle reduced by the border argument for RGB channels.
+#   This method for drawing edges works with alpha filtering enabled.
 BEGIN: generate_rounded_rectangle
 	# Dimensions of the result image.
 	INPUT: FixedPoint, width
@@ -76,6 +77,24 @@ BEGIN: generate_rounded_rectangle
 	RECTANGLE: resultImage, border, radius, w4, h3, 255
 END:
 
+# Can be call directly to draw a component, or internally to add more effects.
+# Black edges are created by default initializing the background to zeroes and drawing the inside smaller.
+#   This method for drawing edges does not work if the resulting colorImage is drawn with alpha filtering, because setting alpha to zero means transparent.
+BEGIN: HardRectangle
+	INPUT: FixedPoint, width
+	INPUT: FixedPoint, height
+	INPUT: FixedPoint, red
+	INPUT: FixedPoint, green
+	INPUT: FixedPoint, blue
+	INPUT: FixedPoint, border
+	OUTPUT: ImageRgbaU8, colorImage
+	CREATE: colorImage, width, height
+	ADD: b2<FixedPoint>, border, border
+	SUB: w2<FixedPoint>, width, b2
+	SUB: h2<FixedPoint>, height, b2
+	RECTANGLE: colorImage, border, border, w2, h2, red, green, blue, 255
+END:
+
 BEGIN: generate_rounded_button
 	INPUT: FixedPoint, width
 	INPUT: FixedPoint, height
@@ -118,21 +137,6 @@ BEGIN: Button
 	CALL: generate_rounded_button, colorImage, width, height, red, green, blue, pressed, border, rounding
 END:
 
-BEGIN: ListBox
-	INPUT: FixedPoint, width
-	INPUT: FixedPoint, height
-	INPUT: FixedPoint, red
-	INPUT: FixedPoint, green
-	INPUT: FixedPoint, blue
-	INPUT: FixedPoint, border
-	OUTPUT: ImageRgbaU8, colorImage
-	CREATE: colorImage, width, height
-	ADD: b2<FixedPoint>, border, border
-	SUB: w2<FixedPoint>, width, b2
-	SUB: h2<FixedPoint>, height, b2
-	RECTANGLE: colorImage, border, border, w2, h2, red, green, blue, 255
-END:
-
 BEGIN: VerticalScrollList
 	INPUT: FixedPoint, width
 	INPUT: FixedPoint, height
@@ -146,19 +150,21 @@ BEGIN: VerticalScrollList
 	PACK_RGBA: colorImage, 0, 0, 0, visImage
 END:
 
-BEGIN: Panel
+BEGIN: TextBox
 	INPUT: FixedPoint, width
 	INPUT: FixedPoint, height
 	INPUT: FixedPoint, red
 	INPUT: FixedPoint, green
 	INPUT: FixedPoint, blue
 	INPUT: FixedPoint, border
+	INPUT: FixedPoint, focused
 	OUTPUT: ImageRgbaU8, colorImage
-	CREATE: colorImage, width, height
-	ADD: b2<FixedPoint>, border, border
-	SUB: w2<FixedPoint>, width, b2
-	SUB: h2<FixedPoint>, height, b2
-	RECTANGLE: colorImage, border, border, w2, h2, red, green, blue, 255
+	ADD: intensity<FixedPoint>, 4, focused
+	MUL: intensity, intensity, 0.2
+	MUL: red, red, intensity
+	MUL: green, green, intensity
+	MUL: blue, blue, intensity
+	CALL: HardRectangle, colorImage, width, height, red, green, blue, border
 END:
 )QUOTE";
 
@@ -167,12 +173,14 @@ END:
 static const ReadableString defaultStyleSettings =
 UR"QUOTE(
 	border = 2
-	; Fall back on the Button method if a component's class could not be recognized.
 	method = "Button"
+	; Fall back on the Button method if a component's class could not be recognized.
 	[Button]
 		rounding = 12
 	[ListBox]
-		method = "ListBox"
+		method = "HardRectangle"
+	[TextBox]
+		method = "TextBox"
 	[VerticalScrollKnob]
 		rounding = 8
 	[VerticalScrollList]
@@ -183,7 +191,7 @@ UR"QUOTE(
 		rounding = 5
 	[Panel]
 		border = 1
-		method = "Panel"
+		method = "HardRectangle"
 )QUOTE";
 
 template <typename V>
@@ -320,18 +328,15 @@ static bool assignMediaMachineArguments(ClassSettings settings, MediaMachine &ma
 			return true;
 		}
 	}
+	// The media machine currently does not support strings.
 	return false;
 }
 
 bool theme_assignMediaMachineArguments(const VisualTheme &theme, int32_t contextIndex, MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName) {
 	if (!theme.get()) { return false; }
-	// Check in the context first.
-	if (contextIndex > 0 && assignMediaMachineArguments(theme->settings[contextIndex], machine, methodIndex, inputIndex, argumentName)) {
-		return true;
-	} else {
-		// If not found in the context, check in the default settings.
-		return assignMediaMachineArguments(theme->settings[0], machine, methodIndex, inputIndex, argumentName);
-	}
+	// Check in the context first, and then in the default settings.
+	return (contextIndex > 0 && assignMediaMachineArguments(theme->settings[contextIndex], machine, methodIndex, inputIndex, argumentName))
+	                         || assignMediaMachineArguments(theme->settings[0],            machine, methodIndex, inputIndex, argumentName);
 }
 
 }

+ 336 - 0
Source/DFPSR/gui/components/TextBox.cpp

@@ -0,0 +1,336 @@
+// zlib open source license
+//
+// Copyright (c) 2022 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#include "TextBox.h"
+#include <math.h>
+#include <functional>
+
+using namespace dsr;
+
+PERSISTENT_DEFINITION(TextBox)
+
+void TextBox::declareAttributes(StructureDefinition &target) const {
+	VisualComponent::declareAttributes(target);
+	target.declareAttribute(U"Color");
+	target.declareAttribute(U"Text");
+}
+
+Persistent* TextBox::findAttribute(const ReadableString &name) {
+	if (string_caseInsensitiveMatch(name, U"Color")) {
+		return &(this->color);
+	} else if (string_caseInsensitiveMatch(name, U"Text")) {
+		return &(this->text);
+	} else {
+		return VisualComponent::findAttribute(name);
+	}
+}
+
+TextBox::TextBox() {}
+
+bool TextBox::isContainer() const {
+	return false;
+}
+
+// Limit exclusive indices to the text.
+void TextBox::limitSelection() {
+	int64_t textLength = string_length(this->text.value);
+	if (this->selectionStart < 0) this->selectionStart = 0;
+	if (this->beamLocation < 0) this->beamLocation = 0;
+	if (this->selectionStart > textLength) this->selectionStart = textLength;
+	if (this->beamLocation > textLength) this->beamLocation = textLength;
+}
+
+static void tabJump(int64_t &x, int64_t leftOrigin, int64_t tabWidth) {
+	x += tabWidth - ((x - leftOrigin) % tabWidth);
+}
+
+// TODO: Make a separate version for multi-line textboxes.
+// To have a stable tab alignment, the whole text must be given when iterating.
+void iterateCharacters(const ReadableString& text, const RasterFont &font, int64_t originX, std::function<void(int64_t index, DsrChar code, int64_t left, int64_t right)> characterAction) {
+	int64_t right = originX;
+	int64_t tabWidth = font_getTabWidth(font);
+	int64_t monospaceWidth = font_getMonospaceWidth(font);
+	for (int64_t i = 0; i <= string_length(text); i++) {
+		DsrChar code = text[i];
+		int64_t left = right;
+		if (code == U'\t') {
+			tabJump(right, originX, tabWidth);
+		} else {
+			right += monospaceWidth;
+		}
+		characterAction(i, code, left, right);
+	}
+}
+
+int64_t findBeamLocation(const ReadableString& text, const RasterFont &font, int64_t originX, int64_t findPixelX) {
+	int64_t beamIndex = 0;
+	int64_t closestDistance = 1000000000000;
+	iterateCharacters(text, font, originX, [&beamIndex, &closestDistance, findPixelX](int64_t index, DsrChar code, int64_t left, int64_t right) {
+		// TODO: Why is selection not centered? Is it the origin handled differently?
+		int64_t center = (left + right) / 2;
+		int64_t newDistance = std::abs(findPixelX - center);
+		if (newDistance < closestDistance) {
+			beamIndex = index;
+			closestDistance = newDistance;
+		}
+	});
+	return beamIndex;
+}
+
+// Iterate over the whole text once for both selection and characters.
+// Returns the beam's X location in pixels relative to the parent of originX.
+int64_t printMonospace(OrderedImageRgbaU8 &target, const ReadableString& text, const RasterFont &font, bool focused, int64_t originX, int64_t selectionLeft, int64_t selectionRight, int64_t beamIndex, int64_t topY, int64_t bottomY) {
+	int64_t characterHeight = bottomY - topY;
+	int64_t beamPixelX = originX;
+	iterateCharacters(text, font, originX, [&target, &font, &beamPixelX, selectionLeft, selectionRight, beamIndex, topY, characterHeight, focused](int64_t index, DsrChar code, int64_t left, int64_t right) {
+		if (index == beamIndex) beamPixelX = left;
+		if (focused && selectionLeft <= index && index < selectionRight) {
+			draw_rectangle(target, IRect(left, topY, right - left, characterHeight), ColorRgbaI32(0, 0, 100, 255));
+			font_printCharacter(target, font, code, IVector2D(left, topY), ColorRgbaI32(255, 255, 255, 255));
+		} else {
+			font_printCharacter(target, font, code, IVector2D(left, topY), ColorRgbaI32(0, 0, 0, 255));
+		}
+	});
+	return beamPixelX;
+}
+
+// TODO: Reuse scaled background images as a separate layer.
+// TODO: Allow using different colors for beam, selection, selected text, normal text...
+//       Maybe ask a separate color palette for specific things using the specific class of textboxes.
+//       Color palettes can be independent of the media machine, allowing them to be mixed freely with different themes.
+//       Color palettes can be loaded together with the layout to instantly have the requested standard colors by name.
+//       Color palettes can have a standard column order of input to easily pack multiple color themes into the same color palette image.
+//         Just a long list of names for the different X coordinates and the user selects a Y coordinate as the color theme.
+//         New components will have to use existing parts of the palette by keeping the names reusable.
+//       Separate components should be able to override any color for programmability, but default values should refer to the current color palette.
+//         If no color is assigned, the class will give it a standard color from the theme.
+//         Should classes be separate for themes and palettes?
+static OrderedImageRgbaU8 generateBoxImage(TextBox &textBox, MediaMethod imageGenerator, bool focused, int width, int height, ColorRgbI32 backColor, const ReadableString &text, const RasterFont &font) {
+	// Create a scaled image
+	OrderedImageRgbaU8 result;
+ 	textBox.generateImage(imageGenerator, width, height, backColor.red, backColor.green, backColor.blue, 0, focused ? 1 : 0)(result);
+ 	textBox.limitSelection();
+ 	// TODO: Allow moving the viewport to follow longer input.
+ 	// TODO: Allow multi-line textboxes with scrollbars.
+ 	//       The logic of scrollbars must be reused as value allocated objects across components, but with different settings.
+	int64_t halfFontSize = font_getSize(font) / 2;
+	int64_t originX = halfFontSize;
+	int64_t center = image_getHeight(result) / 2;
+	int64_t topY = center - halfFontSize;
+	int64_t bottomY = center + halfFontSize;
+	// Find character indices for left and right sides.
+	int64_t selectionLeft = std::min(textBox.selectionStart, textBox.beamLocation);
+	int64_t selectionRight = std::max(textBox.selectionStart, textBox.beamLocation);
+	bool hasSelection = selectionLeft < selectionRight;
+	// Draw the text with selection and get the beam's pixel location.
+	int64_t beamPixelX = printMonospace(result, text, font, focused, originX, selectionLeft, selectionRight, textBox.beamLocation, topY, bottomY);
+	// Draw a beam if the textbox is focused.
+	if (focused) {
+		int64_t beamWidth = 2;
+		draw_rectangle(result, IRect(beamPixelX - 1, topY - 1, beamWidth, bottomY - topY + 2), hasSelection ? ColorRgbaI32(255, 255, 255, 255) : ColorRgbaI32(0, 0, 0, 255));
+	}
+	return result;
+}
+
+void TextBox::generateGraphics() {
+	int width = this->location.width();
+	int height = this->location.height();
+	if (width < 1) { width = 1; }
+	if (height < 1) { height = 1; }
+	bool currentlyFocused = this->isFocused();
+	if (!this->hasImages || this->drawnAsFocused != currentlyFocused) {
+		completeAssets();
+		this->image = generateBoxImage(*this, this->textBox, currentlyFocused, width, height, this->color.value, this->text.value, this->font);
+		this->hasImages = true;
+		this->drawnAsFocused = currentlyFocused;
+	}
+}
+
+void TextBox::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) {
+	this->generateGraphics();
+	draw_copy(targetImage, this->image, relativeLocation.left(), relativeLocation.top());
+}
+
+void TextBox::receiveMouseEvent(const MouseEvent& event) {
+	int64_t originX = font_getSize(this->font) / 2;
+	int32_t localMouseX = event.position.x - this->location.left();
+	if (event.mouseEventType == MouseEventType::MouseDown) {
+		this->mousePressed = true;
+		int64_t newBeamIndex = findBeamLocation(this->text.value, this->font, originX, localMouseX);
+		if (newBeamIndex != this->selectionStart || newBeamIndex != this->beamLocation) {
+			this->selectionStart = newBeamIndex;
+			this->beamLocation = newBeamIndex;
+			this->hasImages = false;
+		}
+	} else if (this->mousePressed && event.mouseEventType == MouseEventType::MouseMove) {
+		if (this->mousePressed) {
+			int64_t newBeamIndex = findBeamLocation(this->text.value, this->font, originX, localMouseX);
+			if (newBeamIndex != this->beamLocation) {
+				this->beamLocation = newBeamIndex;
+				this->hasImages = false;
+			}
+		}
+	} else if (this->mousePressed && event.mouseEventType == MouseEventType::MouseUp) {
+		this->mousePressed = false;
+	}
+	VisualComponent::receiveMouseEvent(event);
+}
+
+void TextBox::replaceSelection(const ReadableString replacingText) {
+	int64_t selectionLeft = std::min(this->selectionStart, this->beamLocation);
+	int64_t selectionRight = std::max(this->selectionStart, this->beamLocation);
+	this->text.value = string_combine(string_before(this->text.value, selectionLeft), replacingText, string_from(this->text.value, selectionRight));
+	// Place beam on the right side of the replacement without selecting anything
+	this->selectionStart = selectionLeft + string_length(replacingText);
+	this->beamLocation = selectionStart;
+	this->hasImages = false;
+}
+
+void TextBox::replaceSelection(DsrChar replacingCharacter) {
+	int64_t selectionLeft = std::min(this->selectionStart, this->beamLocation);
+	int64_t selectionRight = std::max(this->selectionStart, this->beamLocation);
+	String newText = string_before(this->text.value, selectionLeft);
+	string_appendChar(newText, replacingCharacter);
+	string_append(newText, string_from(this->text.value, selectionRight));
+	this->text.value = newText;
+	// Place beam on the right side of the replacement without selecting anything
+	this->selectionStart = selectionLeft + 1;
+	this->beamLocation = selectionStart;
+	this->hasImages = false;
+}
+
+void TextBox::placeBeam(int64_t index, bool removeSelection) {
+	this->beamLocation = index;
+	if (removeSelection) {
+		this->selectionStart = index;
+	}
+	this->hasImages = false;
+}
+
+static const uint32_t combinationKey_leftShift = 1 << 0;
+static const uint32_t combinationKey_rightShift = 1 << 1;
+static const uint32_t combinationKey_shift = combinationKey_leftShift | combinationKey_rightShift;
+static const uint32_t combinationKey_leftControl = 1 << 2;
+static const uint32_t combinationKey_rightControl = 1 << 3;
+static const uint32_t combinationKey_control = combinationKey_leftControl | combinationKey_rightControl;
+
+// TODO: Copy and paste using a clipboard.
+void TextBox::receiveKeyboardEvent(const KeyboardEvent& event) {
+	// Insert and scroll-lock is not supported.
+	if (event.keyboardEventType == KeyboardEventType::KeyDown) {
+		if (event.dsrKey == DsrKey_LeftShift) {
+			this->combinationKeys |= combinationKey_leftShift;
+		} else if (event.dsrKey == DsrKey_RightShift) {
+			this->combinationKeys |= combinationKey_rightShift;
+		} else if (event.dsrKey == DsrKey_LeftControl) {
+			this->combinationKeys |= combinationKey_leftControl;
+		} else if (event.dsrKey == DsrKey_RightControl) {
+			this->combinationKeys |= combinationKey_rightControl;
+		}
+	} else if (event.keyboardEventType == KeyboardEventType::KeyUp) {
+		if (event.dsrKey == DsrKey_LeftShift) {
+			this->combinationKeys &= ~combinationKey_leftShift;
+		} else if (event.dsrKey == DsrKey_RightShift) {
+			this->combinationKeys &= ~combinationKey_rightShift;
+		} else if (event.dsrKey == DsrKey_LeftControl) {
+			this->combinationKeys &= ~combinationKey_leftControl;
+		} else if (event.dsrKey == DsrKey_RightControl) {
+			this->combinationKeys &= ~combinationKey_rightControl;
+		}
+	} else if (event.keyboardEventType == KeyboardEventType::KeyType) {
+		int64_t textLength = string_length(this->text.value);
+		bool selected = this->selectionStart != this->beamLocation;
+		bool printable = event.character == U'\t' || (31 < event.character && event.character < 127) || 159 < event.character;
+		bool canGoLeft = textLength > 0 && this->beamLocation > 0;
+		bool canGoRight = textLength > 0 && this->beamLocation < textLength;
+		bool holdShift = this->combinationKeys & combinationKey_shift;
+		bool holdControl = this->combinationKeys & combinationKey_control;
+		bool removeSelection = !holdShift;
+		if (selected && (event.dsrKey == DsrKey_BackSpace || event.dsrKey == DsrKey_Delete)) {
+			// Remove selection
+			this->replaceSelection(U"");
+		} else if (event.dsrKey == DsrKey_BackSpace && canGoLeft) {
+			// Erase left of beam
+			this->beamLocation--;
+			this->replaceSelection(U"");
+		} else if (event.dsrKey == DsrKey_Delete && canGoRight) {
+			// Erase right of beam
+			this->beamLocation++;
+			this->replaceSelection(U"");
+		} else if (event.dsrKey == DsrKey_Home || (event.dsrKey == DsrKey_LeftArrow && holdControl)) {
+			// Move to the start using Home or Ctrl + LeftArrow
+			this->placeBeam(0, removeSelection);
+		} else if (event.dsrKey == DsrKey_End || (event.dsrKey == DsrKey_RightArrow && holdControl)) {
+			// Move to the end using End or Ctrl + RightArrow
+			this->placeBeam(textLength, removeSelection);
+		} else if (event.dsrKey == DsrKey_LeftArrow && canGoLeft) {
+			// Move left using LeftArrow
+			this->placeBeam(this->beamLocation - 1, removeSelection);
+		} else if (event.dsrKey == DsrKey_RightArrow && canGoRight) {
+			// Move right using RightArrow
+			this->placeBeam(this->beamLocation + 1, removeSelection);
+		} else if (printable) {
+			this->replaceSelection(event.character);
+		}
+		//printText(U"KeyType char=", event.character, " key=", event.dsrKey, U"\n");
+	}
+	VisualComponent::receiveKeyboardEvent(event);
+}
+
+bool TextBox::pointIsInside(const IVector2D& pixelPosition) {
+	this->generateGraphics();
+	// Get the point relative to the component instead of its direct container
+	IVector2D localPoint = pixelPosition - this->location.upperLeft();
+	// Sample opacity at the location
+	return dsr::image_readPixel_border(this->image, localPoint.x, localPoint.y).alpha > 127;
+}
+
+void TextBox::changedTheme(VisualTheme newTheme) {
+	this->textBox = theme_getScalableImage(newTheme, U"TextBox");
+	this->hasImages = false;
+}
+
+void TextBox::completeAssets() {
+	if (this->textBox.methodIndex == -1) {
+		this->textBox = theme_getScalableImage(theme_getDefault(), U"TextBox");
+	}
+	if (this->font.get() == nullptr) {
+		this->font = font_getDefault();
+	}
+}
+
+void TextBox::changedLocation(const IRect &oldLocation, const IRect &newLocation) {
+	// If the component has changed dimensions then redraw the image
+	if (oldLocation.size() != newLocation.size()) {
+		this->hasImages = false;
+	}
+}
+
+void TextBox::changedAttribute(const ReadableString &name) {
+	if (!string_caseInsensitiveMatch(name, U"Visible")) {
+		this->hasImages = false;
+		if (string_caseInsensitiveMatch(name, U"Text")) {
+			this->limitSelection();
+		}
+	}
+}

+ 79 - 0
Source/DFPSR/gui/components/TextBox.h

@@ -0,0 +1,79 @@
+// zlib open source license
+//
+// Copyright (c) 2022 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#ifndef DFPSR_GUI_COMPONENT_TEXTBOX
+#define DFPSR_GUI_COMPONENT_TEXTBOX
+
+#include "../VisualComponent.h"
+#include "../../api/fontAPI.h"
+
+namespace dsr {
+
+class TextBox : public VisualComponent {
+PERSISTENT_DECLARATION(TextBox)
+public:
+	// Attributes
+	PersistentColor color;
+	PersistentString text;
+	void declareAttributes(StructureDefinition &target) const override;
+	Persistent* findAttribute(const ReadableString &name) override;
+	// Temporary
+	bool mousePressed = false;
+	uint32_t combinationKeys = 0;
+	// Selection goes from selectionStart to beamLocation using bi-directional exclusive character indices.
+	//   Empty with selectionStart = beamLocation.
+	//   From the left with selectionStart < beamLocation.
+	//   From the right with beamLocation < selectionStart.
+	int64_t selectionStart = 0;
+	int64_t beamLocation = 0;
+	void limitSelection();
+	void replaceSelection(const ReadableString replacingText);
+	void replaceSelection(DsrChar replacingCharacter);
+	void placeBeam(int64_t index, bool removeSelection);
+private:
+	// Given from the style
+	MediaMethod textBox;
+	RasterFont font;
+	void completeAssets();
+	void generateGraphics();
+	// Generated
+	bool hasImages = false;
+	bool drawnAsFocused = false;
+	OrderedImageRgbaU8 image;
+public:
+	TextBox();
+public:
+	bool isContainer() const override;
+	void drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) override;
+	void receiveMouseEvent(const MouseEvent& event) override;
+	void receiveKeyboardEvent(const KeyboardEvent& event) override;
+	bool pointIsInside(const IVector2D& pixelPosition) override;
+	void changedTheme(VisualTheme newTheme) override;
+	void changedLocation(const IRect &oldLocation, const IRect &newLocation) override;
+	void changedAttribute(const ReadableString &name) override;
+};
+
+}
+
+#endif
+

+ 5 - 1
Source/DFPSR/machine/VirtualMachine.cpp

@@ -256,7 +256,11 @@ void VirtualMachine::addCallInstructions(const List<String>& arguments) {
 	}
 	// TODO: Allow calling methods that aren't defined yet.
 	int currentMethodIndex = this->methods.length() - 1;
-	int calledMethodIndex = findMethod(string_removeOuterWhiteSpace(arguments[0]));
+	ReadableString methodName = string_removeOuterWhiteSpace(arguments[0]);
+	int calledMethodIndex = findMethod(methodName);
+	if (calledMethodIndex == -1) {
+		throwError(U"Tried to make an internal call to the method \"", methodName, U"\", which was not previously defined in the virtual machine! Make sure that the name is spelled correctly and the method is defined above the caller.\n");
+	}
 	// Check the total number of arguments
 	Method* calledMethod = &this->methods[calledMethodIndex];
 	if (arguments.length() - 1 != calledMethod->outputCount + calledMethod->inputCount) {

+ 3 - 1
Source/SDK/guiExample/main.cpp

@@ -10,6 +10,7 @@ Window window;
 Component buttonClear;
 Component buttonAdd;
 Component myListBox;
+Component textElement;
 
 DSR_MAIN_CALLER(dsrMain)
 void dsrMain(List<String> args) {
@@ -33,6 +34,7 @@ void dsrMain(List<String> args) {
 	buttonClear = window_findComponentByName(window, U"buttonClear");
 	buttonAdd = window_findComponentByName(window, U"buttonAdd");
 	myListBox = window_findComponentByName(window, U"myListBox");
+	textElement = window_findComponentByName(window, U"textElement");
 
 	// Connect components with actions
 	component_setPressedEvent(buttonClear, []() {
@@ -41,7 +43,7 @@ void dsrMain(List<String> args) {
 	});
 	component_setPressedEvent(buttonAdd, []() {
 		// Add to list
-		component_call(myListBox, U"PushElement", U"New item");
+		component_call(myListBox, U"PushElement", component_getProperty_string(textElement, U"Text", false));
 	});
 	component_setKeyDownEvent(myListBox, [](const KeyboardEvent& event) {
 		if (event.dsrKey == DsrKey_Delete) {

+ 22 - 12
Source/SDK/guiExample/media/interface.lof

@@ -6,20 +6,20 @@
 		Bottom = +50
 		Solid = 1
 		Color = 180,180,180
-		Begin : Button
-			Name = "buttonClear"
+		Begin : TextBox
+			Name = "textElement"
 			Left = +5
+			Right = 100%-105
 			Top = +5
-			Right = +95
 			Bottom = +45
-			Color = 160,150,150
-			Text = "Clear"
+			Color = 200,200,200
+			Text = "Element"
 		End
 		Begin : Button
 			Name = "buttonAdd"
-			Left = +105
+			Left = 100%-100
+			Right = 100%-5
 			Top = +5
-			Right = +195
 			Bottom = +45
 			Color = 150,160,150
 			Text = "Add"
@@ -32,20 +32,30 @@
 		Color = 100,100,100
 		Begin : ListBox
 			Name = "myListBox"
-			right = 10%+100
+			Right = 10%+100
+			Bottom = 100%-50
 			Color = 180,180,180
 			List = "Testing", "1", "2", "3", "One Two Three"
 			SelectedIndex = 2
 		End
+		Begin : Button
+			Name = "buttonClear"
+			Left = +5
+			Right = 10%+100
+			Top = 100%-45
+			Bottom = 100%-5
+			Color = 160,150,150
+			Text = "Clear"
+		End
 		Begin : Label
 			Name = "myLabel"
 			Text = "Testing multi-line label.\nABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789\n	indented\n  aligned\n\nEnd of test!"
 			Color = 0,50,0
 			Opacity = 128
-			left = 10%+120
-			right = 100%-20
-			top = 20
-			bottom = 100%-20
+			Left = 10%+120
+			Right = 100%-20
+			Top = 20
+			Bottom = 100%-20
 		End
 		Begin : Picture
 			Name = "linkedPicture"

+ 11 - 5
Source/windowManagers/Win32Window.cpp

@@ -344,6 +344,16 @@ static dsr::DsrKey getDsrKey(WPARAM keyCode) {
 		result = dsr::DsrKey_Y;
 	} else if (keyCode == 0x5A) {
 		result = dsr::DsrKey_Z;
+	} else if (keyCode == 0x2D) {
+		result = dsr::DsrKey_Insert;
+	} else if (keyCode == 0x24) {
+		result = dsr::DsrKey_Home;
+	} else if (keyCode == 0x23) {
+		result = dsr::DsrKey_End;
+	} else if (keyCode == 0x21) {
+		result = dsr::DsrKey_PageUp;
+	} else if (keyCode == 0x22) {
+		result = dsr::DsrKey_PageDown;
 	}
 	return result;
 }
@@ -429,11 +439,7 @@ static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam,
 					parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyDown, character, dsrKey));
 				}
 				// Press typing with repeat
-				// Caps lock, shift, control and insert is not something you type characters with
-				// TODO: Add more characters to the exclusion list or find a function for this filter somewhere
-				if (character != 16 && character != 17 && character != 20 && character != 37 && character != 45) {
-					parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyType, character, dsrKey));
-				}
+				parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyType, character, dsrKey));
 			} else { // message == WM_KEYUP
 				// Physical key up
 				parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyUp, character, dsrKey));

+ 10 - 0
Source/windowManagers/X11Window.cpp

@@ -454,6 +454,16 @@ static dsr::DsrKey getDsrKey(KeySym keyCode) {
 		result = dsr::DsrKey_Y;
 	} else if (keyCode == XK_z || keyCode == XK_Z) {
 		result = dsr::DsrKey_Z;
+	} else if (keyCode == XK_Insert) {
+		result = dsr::DsrKey_Insert;
+	} else if (keyCode == XK_Home) {
+		result = dsr::DsrKey_Home;
+	} else if (keyCode == XK_End) {
+		result = dsr::DsrKey_End;
+	} else if (keyCode == XK_Page_Up) {
+		result = dsr::DsrKey_PageUp;
+	} else if (keyCode == XK_Page_Down) {
+		result = dsr::DsrKey_PageDown;
 	}
 	return result;
 }